pi-usereq 0.6.0 → 0.10.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 (774) hide show
  1. package/.gitignore +2 -0
  2. package/CHANGELOG.md +44 -0
  3. package/README.md +1 -1
  4. package/docs/pi.dev/coding-agent-docs/custom-provider.md +42 -1
  5. package/docs/pi.dev/coding-agent-docs/extensions.md +302 -28
  6. package/docs/pi.dev/coding-agent-docs/keybindings.md +23 -1
  7. package/docs/pi.dev/coding-agent-docs/models.md +3 -0
  8. package/docs/pi.dev/coding-agent-docs/packages.md +2 -2
  9. package/docs/pi.dev/coding-agent-docs/prompt-templates.md +21 -0
  10. package/docs/pi.dev/coding-agent-docs/providers.md +1 -0
  11. package/docs/pi.dev/coding-agent-docs/rpc.md +36 -6
  12. package/docs/pi.dev/coding-agent-docs/sdk.md +30 -5
  13. package/docs/pi.dev/coding-agent-docs/session.md +1 -1
  14. package/docs/pi.dev/coding-agent-docs/settings.md +2 -1
  15. package/docs/pi.dev/coding-agent-docs/skills.md +12 -12
  16. package/docs/pi.dev/coding-agent-docs/tree.md +2 -0
  17. package/docs/pi.dev/coding-agent-docs/tui.md +31 -0
  18. package/docs/pi.dev/mom-docs/new.md +4 -4
  19. package/package.json +1 -1
  20. package/pi-usereq/docs/REFERENCES.md +2658 -2035
  21. package/pi-usereq/docs/REQUIREMENTS.md +345 -191
  22. package/pi-usereq/docs/WORKFLOW.md +387 -363
  23. package/pi.dev-src/pi-mono/agent/src/agent-loop.ts +683 -0
  24. package/pi.dev-src/pi-mono/agent/src/agent.ts +543 -0
  25. package/pi.dev-src/pi-mono/agent/src/index.ts +8 -0
  26. package/pi.dev-src/pi-mono/agent/src/proxy.ts +367 -0
  27. package/pi.dev-src/pi-mono/agent/src/types.ts +364 -0
  28. package/pi.dev-src/pi-mono/agent/test/agent-loop.test.ts +1181 -0
  29. package/pi.dev-src/pi-mono/agent/test/agent.test.ts +463 -0
  30. package/pi.dev-src/pi-mono/agent/test/e2e.test.ts +404 -0
  31. package/pi.dev-src/pi-mono/agent/test/utils/calculate.ts +32 -0
  32. package/pi.dev-src/pi-mono/agent/test/utils/get-current-time.ts +46 -0
  33. package/pi.dev-src/pi-mono/agent/vitest.config.ts +9 -0
  34. package/pi.dev-src/pi-mono/ai/bedrock-provider.d.ts +1 -0
  35. package/pi.dev-src/pi-mono/ai/bedrock-provider.js +1 -0
  36. package/pi.dev-src/pi-mono/ai/scripts/generate-models.ts +1635 -0
  37. package/pi.dev-src/pi-mono/ai/scripts/generate-test-image.ts +34 -0
  38. package/pi.dev-src/pi-mono/ai/src/api-registry.ts +98 -0
  39. package/pi.dev-src/pi-mono/ai/src/bedrock-provider.ts +6 -0
  40. package/pi.dev-src/pi-mono/ai/src/cli.ts +133 -0
  41. package/pi.dev-src/pi-mono/ai/src/env-api-keys.ts +134 -0
  42. package/pi.dev-src/pi-mono/ai/src/index.ts +35 -0
  43. package/pi.dev-src/pi-mono/ai/src/models.generated.ts +14928 -0
  44. package/pi.dev-src/pi-mono/ai/src/models.ts +82 -0
  45. package/pi.dev-src/pi-mono/ai/src/oauth.ts +1 -0
  46. package/pi.dev-src/pi-mono/ai/src/providers/amazon-bedrock.ts +929 -0
  47. package/pi.dev-src/pi-mono/ai/src/providers/anthropic.ts +1125 -0
  48. package/pi.dev-src/pi-mono/ai/src/providers/azure-openai-responses.ts +253 -0
  49. package/pi.dev-src/pi-mono/ai/src/providers/faux.ts +499 -0
  50. package/pi.dev-src/pi-mono/ai/src/providers/github-copilot-headers.ts +37 -0
  51. package/pi.dev-src/pi-mono/ai/src/providers/google-gemini-cli.ts +996 -0
  52. package/pi.dev-src/pi-mono/ai/src/providers/google-shared.ts +354 -0
  53. package/pi.dev-src/pi-mono/ai/src/providers/google-vertex.ts +543 -0
  54. package/pi.dev-src/pi-mono/ai/src/providers/google.ts +500 -0
  55. package/pi.dev-src/pi-mono/ai/src/providers/mistral.ts +629 -0
  56. package/pi.dev-src/pi-mono/ai/src/providers/openai-codex-responses.ts +984 -0
  57. package/pi.dev-src/pi-mono/ai/src/providers/openai-completions.ts +1063 -0
  58. package/pi.dev-src/pi-mono/ai/src/providers/openai-responses-shared.ts +539 -0
  59. package/pi.dev-src/pi-mono/ai/src/providers/openai-responses.ts +264 -0
  60. package/pi.dev-src/pi-mono/ai/src/providers/register-builtins.ts +433 -0
  61. package/pi.dev-src/pi-mono/ai/src/providers/simple-options.ts +47 -0
  62. package/pi.dev-src/pi-mono/ai/src/providers/transform-messages.ts +220 -0
  63. package/pi.dev-src/pi-mono/ai/src/stream.ts +59 -0
  64. package/pi.dev-src/pi-mono/ai/src/types.ts +417 -0
  65. package/pi.dev-src/pi-mono/ai/src/utils/event-stream.ts +87 -0
  66. package/pi.dev-src/pi-mono/ai/src/utils/hash.ts +13 -0
  67. package/pi.dev-src/pi-mono/ai/src/utils/headers.ts +7 -0
  68. package/pi.dev-src/pi-mono/ai/src/utils/json-parse.ts +124 -0
  69. package/pi.dev-src/pi-mono/ai/src/utils/overflow.ts +138 -0
  70. package/pi.dev-src/pi-mono/ai/src/utils/sanitize-unicode.ts +25 -0
  71. package/pi.dev-src/pi-mono/ai/src/utils/typebox-helpers.ts +24 -0
  72. package/pi.dev-src/pi-mono/ai/src/utils/validation.ts +324 -0
  73. package/pi.dev-src/pi-mono/ai/test/abort.test.ts +260 -0
  74. package/pi.dev-src/pi-mono/ai/test/anthropic-oauth.test.ts +99 -0
  75. package/pi.dev-src/pi-mono/ai/test/anthropic-opus-4-7-smoke.test.ts +72 -0
  76. package/pi.dev-src/pi-mono/ai/test/anthropic-sse-parsing.test.ts +113 -0
  77. package/pi.dev-src/pi-mono/ai/test/anthropic-thinking-disable.test.ts +152 -0
  78. package/pi.dev-src/pi-mono/ai/test/anthropic-tool-name-normalization.test.ts +205 -0
  79. package/pi.dev-src/pi-mono/ai/test/azure-utils.ts +28 -0
  80. package/pi.dev-src/pi-mono/ai/test/bedrock-endpoint-resolution.test.ts +131 -0
  81. package/pi.dev-src/pi-mono/ai/test/bedrock-models.test.ts +66 -0
  82. package/pi.dev-src/pi-mono/ai/test/bedrock-thinking-payload.test.ts +107 -0
  83. package/pi.dev-src/pi-mono/ai/test/bedrock-utils.ts +18 -0
  84. package/pi.dev-src/pi-mono/ai/test/cache-retention.test.ts +335 -0
  85. package/pi.dev-src/pi-mono/ai/test/context-overflow.test.ts +714 -0
  86. package/pi.dev-src/pi-mono/ai/test/cross-provider-handoff.test.ts +458 -0
  87. package/pi.dev-src/pi-mono/ai/test/data/red-circle.png +0 -0
  88. package/pi.dev-src/pi-mono/ai/test/empty.test.ts +742 -0
  89. package/pi.dev-src/pi-mono/ai/test/faux-provider.test.ts +597 -0
  90. package/pi.dev-src/pi-mono/ai/test/fireworks-models.test.ts +49 -0
  91. package/pi.dev-src/pi-mono/ai/test/github-copilot-anthropic.test.ts +108 -0
  92. package/pi.dev-src/pi-mono/ai/test/github-copilot-oauth.test.ts +196 -0
  93. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-claude-thinking-header.test.ts +103 -0
  94. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-empty-stream.test.ts +108 -0
  95. package/pi.dev-src/pi-mono/ai/test/google-gemini-cli-retry-delay.test.ts +53 -0
  96. package/pi.dev-src/pi-mono/ai/test/google-shared-convert-tools.test.ts +187 -0
  97. package/pi.dev-src/pi-mono/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts +172 -0
  98. package/pi.dev-src/pi-mono/ai/test/google-shared-image-tool-result-routing.test.ts +124 -0
  99. package/pi.dev-src/pi-mono/ai/test/google-thinking-disable.test.ts +183 -0
  100. package/pi.dev-src/pi-mono/ai/test/google-thinking-signature.test.ts +38 -0
  101. package/pi.dev-src/pi-mono/ai/test/google-tool-call-missing-args.test.ts +105 -0
  102. package/pi.dev-src/pi-mono/ai/test/google-vertex-api-key-resolution.test.ts +144 -0
  103. package/pi.dev-src/pi-mono/ai/test/image-tool-result.test.ts +481 -0
  104. package/pi.dev-src/pi-mono/ai/test/interleaved-thinking.test.ts +145 -0
  105. package/pi.dev-src/pi-mono/ai/test/lazy-module-load.test.ts +102 -0
  106. package/pi.dev-src/pi-mono/ai/test/mistral-reasoning-mode.test.ts +66 -0
  107. package/pi.dev-src/pi-mono/ai/test/mistral-tool-schema.test.ts +61 -0
  108. package/pi.dev-src/pi-mono/ai/test/oauth.ts +89 -0
  109. package/pi.dev-src/pi-mono/ai/test/openai-codex-cache-affinity-e2e.test.ts +36 -0
  110. package/pi.dev-src/pi-mono/ai/test/openai-codex-stream.test.ts +686 -0
  111. package/pi.dev-src/pi-mono/ai/test/openai-completions-cache-control-format.test.ts +189 -0
  112. package/pi.dev-src/pi-mono/ai/test/openai-completions-prompt-cache.test.ts +198 -0
  113. package/pi.dev-src/pi-mono/ai/test/openai-completions-thinking-as-text.test.ts +214 -0
  114. package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-choice.test.ts +560 -0
  115. package/pi.dev-src/pi-mono/ai/test/openai-completions-tool-result-images.test.ts +101 -0
  116. package/pi.dev-src/pi-mono/ai/test/openai-responses-cache-affinity-e2e.test.ts +32 -0
  117. package/pi.dev-src/pi-mono/ai/test/openai-responses-copilot-provider.test.ts +128 -0
  118. package/pi.dev-src/pi-mono/ai/test/openai-responses-foreign-toolcall-id.test.ts +66 -0
  119. package/pi.dev-src/pi-mono/ai/test/openai-responses-partial-json-cleanup.test.ts +101 -0
  120. package/pi.dev-src/pi-mono/ai/test/openai-responses-reasoning-replay-e2e.test.ts +292 -0
  121. package/pi.dev-src/pi-mono/ai/test/openai-responses-tool-result-images.test.ts +200 -0
  122. package/pi.dev-src/pi-mono/ai/test/openrouter-cache-write-repro.test.ts +78 -0
  123. package/pi.dev-src/pi-mono/ai/test/overflow.test.ts +64 -0
  124. package/pi.dev-src/pi-mono/ai/test/responseid.test.ts +145 -0
  125. package/pi.dev-src/pi-mono/ai/test/stream.test.ts +1405 -0
  126. package/pi.dev-src/pi-mono/ai/test/supports-xhigh.test.ts +34 -0
  127. package/pi.dev-src/pi-mono/ai/test/tokens.test.ts +304 -0
  128. package/pi.dev-src/pi-mono/ai/test/tool-call-id-normalization.test.ts +291 -0
  129. package/pi.dev-src/pi-mono/ai/test/tool-call-without-result.test.ts +313 -0
  130. package/pi.dev-src/pi-mono/ai/test/total-tokens.test.ts +695 -0
  131. package/pi.dev-src/pi-mono/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts +191 -0
  132. package/pi.dev-src/pi-mono/ai/test/unicode-surrogate.test.ts +754 -0
  133. package/pi.dev-src/pi-mono/ai/test/validation.test.ts +116 -0
  134. package/pi.dev-src/pi-mono/ai/test/xhigh.test.ts +71 -0
  135. package/pi.dev-src/pi-mono/ai/test/zen.test.ts +25 -0
  136. package/pi.dev-src/pi-mono/ai/vitest.config.ts +9 -0
  137. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/antigravity-image-gen.ts +418 -0
  138. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/auto-commit-on-exit.ts +49 -0
  139. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bash-spawn-hook.ts +30 -0
  140. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/bookmark.ts +50 -0
  141. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/built-in-tool-renderer.ts +249 -0
  142. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/claude-rules.ts +86 -0
  143. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/commands.ts +72 -0
  144. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/confirm-destructive.ts +59 -0
  145. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-compaction.ts +127 -0
  146. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-footer.ts +64 -0
  147. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-header.ts +73 -0
  148. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  149. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  150. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  151. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  152. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
  153. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dirty-repo-guard.ts +56 -0
  154. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/build.sh +152 -0
  155. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  156. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-component.ts +132 -0
  157. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  158. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  159. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/index.ts +74 -0
  160. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  161. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/SKILL.md +8 -0
  162. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.json +79 -0
  163. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/dynamic.md +5 -0
  164. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-resources/index.ts +15 -0
  165. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/dynamic-tools.ts +74 -0
  166. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/event-bus.ts +43 -0
  167. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/file-trigger.ts +41 -0
  168. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/git-checkpoint.ts +53 -0
  169. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/github-issue-autocomplete.ts +185 -0
  170. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/handoff.ts +153 -0
  171. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hello.ts +26 -0
  172. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/hidden-thinking-label.ts +53 -0
  173. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/inline-bash.ts +94 -0
  174. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/input-transform.ts +43 -0
  175. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/interactive-shell.ts +196 -0
  176. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/mac-system-theme.ts +47 -0
  177. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/message-renderer.ts +59 -0
  178. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/minimal-mode.ts +426 -0
  179. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/modal-editor.ts +85 -0
  180. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/model-status.ts +31 -0
  181. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/notify.ts +55 -0
  182. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-qa-tests.ts +1348 -0
  183. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/overlay-test.ts +150 -0
  184. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/permission-gate.ts +34 -0
  185. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/pirate.ts +47 -0
  186. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/index.ts +340 -0
  187. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/plan-mode/utils.ts +168 -0
  188. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/preset.ts +430 -0
  189. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/prompt-customizer.ts +97 -0
  190. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/protected-paths.ts +30 -0
  191. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/provider-payload.ts +18 -0
  192. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/qna.ts +122 -0
  193. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/question.ts +264 -0
  194. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/questionnaire.ts +427 -0
  195. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rainbow-editor.ts +88 -0
  196. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/reload-runtime.ts +37 -0
  197. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/rpc-demo.ts +118 -0
  198. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/index.ts +321 -0
  199. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/sandbox/package-lock.json +92 -0
  200. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/send-user-message.ts +97 -0
  201. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/session-name.ts +27 -0
  202. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/shutdown-command.ts +63 -0
  203. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/snake.ts +343 -0
  204. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/space-invaders.ts +560 -0
  205. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/ssh.ts +220 -0
  206. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/status-line.ts +32 -0
  207. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/structured-output.ts +65 -0
  208. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/planner.md +37 -0
  209. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/reviewer.md +35 -0
  210. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/scout.md +50 -0
  211. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents/worker.md +24 -0
  212. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/agents.ts +126 -0
  213. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/index.ts +987 -0
  214. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  215. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/implement.md +10 -0
  216. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  217. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/summarize.ts +206 -0
  218. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/system-prompt-header.ts +17 -0
  219. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tic-tac-toe.ts +1008 -0
  220. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/timed-confirm.ts +70 -0
  221. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/titlebar-spinner.ts +58 -0
  222. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/todo.ts +297 -0
  223. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tool-override.ts +144 -0
  224. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/tools.ts +141 -0
  225. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/trigger-compact.ts +50 -0
  226. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/truncated-tool.ts +195 -0
  227. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/widget-placement.ts +9 -0
  228. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/index.ts +32 -0
  229. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/with-deps/package-lock.json +31 -0
  230. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-indicator.ts +123 -0
  231. package/pi.dev-src/pi-mono/coding-agent/examples/extensions/working-message-test.ts +25 -0
  232. package/pi.dev-src/pi-mono/coding-agent/examples/rpc-extension-ui.ts +632 -0
  233. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/01-minimal.ts +22 -0
  234. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/02-custom-model.ts +49 -0
  235. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/03-custom-prompt.ts +62 -0
  236. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/04-skills.ts +55 -0
  237. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/05-tools.ts +44 -0
  238. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/06-extensions.ts +90 -0
  239. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/07-context-files.ts +42 -0
  240. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/08-prompt-templates.ts +51 -0
  241. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  242. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/10-settings.ts +53 -0
  243. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/11-sessions.ts +48 -0
  244. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/12-full-control.ts +73 -0
  245. package/pi.dev-src/pi-mono/coding-agent/examples/sdk/13-session-runtime.ts +67 -0
  246. package/pi.dev-src/pi-mono/coding-agent/scripts/migrate-sessions.sh +93 -0
  247. package/pi.dev-src/pi-mono/coding-agent/src/bun/cli.ts +6 -0
  248. package/pi.dev-src/pi-mono/coding-agent/src/bun/register-bedrock.ts +4 -0
  249. package/pi.dev-src/pi-mono/coding-agent/src/cli/args.ts +335 -0
  250. package/pi.dev-src/pi-mono/coding-agent/src/cli/config-selector.ts +52 -0
  251. package/pi.dev-src/pi-mono/coding-agent/src/cli/file-processor.ts +100 -0
  252. package/pi.dev-src/pi-mono/coding-agent/src/cli/initial-message.ts +43 -0
  253. package/pi.dev-src/pi-mono/coding-agent/src/cli/list-models.ts +110 -0
  254. package/pi.dev-src/pi-mono/coding-agent/src/cli/session-picker.ts +52 -0
  255. package/pi.dev-src/pi-mono/coding-agent/src/cli.ts +17 -0
  256. package/pi.dev-src/pi-mono/coding-agent/src/config.ts +261 -0
  257. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-runtime.ts +394 -0
  258. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session-services.ts +196 -0
  259. package/pi.dev-src/pi-mono/coding-agent/src/core/agent-session.ts +3096 -0
  260. package/pi.dev-src/pi-mono/coding-agent/src/core/auth-storage.ts +493 -0
  261. package/pi.dev-src/pi-mono/coding-agent/src/core/bash-executor.ts +154 -0
  262. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/branch-summarization.ts +355 -0
  263. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/compaction.ts +839 -0
  264. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/index.ts +7 -0
  265. package/pi.dev-src/pi-mono/coding-agent/src/core/compaction/utils.ts +170 -0
  266. package/pi.dev-src/pi-mono/coding-agent/src/core/defaults.ts +3 -0
  267. package/pi.dev-src/pi-mono/coding-agent/src/core/diagnostics.ts +15 -0
  268. package/pi.dev-src/pi-mono/coding-agent/src/core/event-bus.ts +33 -0
  269. package/pi.dev-src/pi-mono/coding-agent/src/core/exec.ts +107 -0
  270. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/ansi-to-html.ts +258 -0
  271. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/index.ts +314 -0
  272. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.css +1017 -0
  273. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.html +55 -0
  274. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/template.js +1770 -0
  275. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/tool-renderer.ts +158 -0
  276. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/highlight.min.js +1213 -0
  277. package/pi.dev-src/pi-mono/coding-agent/src/core/export-html/vendor/marked.min.js +6 -0
  278. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/index.ts +171 -0
  279. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/loader.ts +606 -0
  280. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/runner.ts +1019 -0
  281. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/types.ts +1542 -0
  282. package/pi.dev-src/pi-mono/coding-agent/src/core/extensions/wrapper.ts +30 -0
  283. package/pi.dev-src/pi-mono/coding-agent/src/core/footer-data-provider.ts +354 -0
  284. package/pi.dev-src/pi-mono/coding-agent/src/core/index.ts +77 -0
  285. package/pi.dev-src/pi-mono/coding-agent/src/core/keybindings.ts +370 -0
  286. package/pi.dev-src/pi-mono/coding-agent/src/core/messages.ts +195 -0
  287. package/pi.dev-src/pi-mono/coding-agent/src/core/model-registry.ts +858 -0
  288. package/pi.dev-src/pi-mono/coding-agent/src/core/model-resolver.ts +629 -0
  289. package/pi.dev-src/pi-mono/coding-agent/src/core/output-guard.ts +74 -0
  290. package/pi.dev-src/pi-mono/coding-agent/src/core/package-manager.ts +2408 -0
  291. package/pi.dev-src/pi-mono/coding-agent/src/core/prompt-templates.ts +296 -0
  292. package/pi.dev-src/pi-mono/coding-agent/src/core/resolve-config-value.ts +142 -0
  293. package/pi.dev-src/pi-mono/coding-agent/src/core/resource-loader.ts +917 -0
  294. package/pi.dev-src/pi-mono/coding-agent/src/core/sdk.ts +380 -0
  295. package/pi.dev-src/pi-mono/coding-agent/src/core/session-cwd.ts +59 -0
  296. package/pi.dev-src/pi-mono/coding-agent/src/core/session-manager.ts +1425 -0
  297. package/pi.dev-src/pi-mono/coding-agent/src/core/settings-manager.ts +999 -0
  298. package/pi.dev-src/pi-mono/coding-agent/src/core/skills.ts +508 -0
  299. package/pi.dev-src/pi-mono/coding-agent/src/core/slash-commands.ts +39 -0
  300. package/pi.dev-src/pi-mono/coding-agent/src/core/source-info.ts +40 -0
  301. package/pi.dev-src/pi-mono/coding-agent/src/core/system-prompt.ts +172 -0
  302. package/pi.dev-src/pi-mono/coding-agent/src/core/telemetry.ts +13 -0
  303. package/pi.dev-src/pi-mono/coding-agent/src/core/timings.ts +31 -0
  304. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/bash.ts +448 -0
  305. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit-diff.ts +445 -0
  306. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/edit.ts +487 -0
  307. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/file-mutation-queue.ts +39 -0
  308. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/find.ts +370 -0
  309. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/grep.ts +384 -0
  310. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/index.ts +196 -0
  311. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/ls.ts +229 -0
  312. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/path-utils.ts +94 -0
  313. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/read.ts +273 -0
  314. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/render-utils.ts +64 -0
  315. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/tool-definition-wrapper.ts +45 -0
  316. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/truncate.ts +265 -0
  317. package/pi.dev-src/pi-mono/coding-agent/src/core/tools/write.ts +281 -0
  318. package/pi.dev-src/pi-mono/coding-agent/src/index.ts +352 -0
  319. package/pi.dev-src/pi-mono/coding-agent/src/main.ts +731 -0
  320. package/pi.dev-src/pi-mono/coding-agent/src/migrations.ts +314 -0
  321. package/pi.dev-src/pi-mono/coding-agent/src/modes/index.ts +9 -0
  322. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/assets/clankolas.png +0 -0
  323. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/armin.ts +382 -0
  324. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/assistant-message.ts +147 -0
  325. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bash-execution.ts +218 -0
  326. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/bordered-loader.ts +68 -0
  327. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/branch-summary-message.ts +58 -0
  328. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/compaction-summary-message.ts +59 -0
  329. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/config-selector.ts +592 -0
  330. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/countdown-timer.ts +38 -0
  331. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-editor.ts +80 -0
  332. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/custom-message.ts +99 -0
  333. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/daxnuts.ts +164 -0
  334. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/diff.ts +147 -0
  335. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/dynamic-border.ts +25 -0
  336. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/earendil-announcement.ts +53 -0
  337. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-editor.ts +147 -0
  338. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-input.ts +87 -0
  339. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/extension-selector.ts +107 -0
  340. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/footer.ts +220 -0
  341. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/index.ts +32 -0
  342. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/keybinding-hints.ts +24 -0
  343. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/login-dialog.ts +178 -0
  344. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/model-selector.ts +338 -0
  345. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/oauth-selector.ts +121 -0
  346. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/scoped-models-selector.ts +355 -0
  347. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector-search.ts +194 -0
  348. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/session-selector.ts +1026 -0
  349. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/settings-selector.ts +456 -0
  350. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/show-images-selector.ts +50 -0
  351. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/skill-invocation-message.ts +55 -0
  352. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/theme-selector.ts +67 -0
  353. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/thinking-selector.ts +74 -0
  354. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tool-execution.ts +352 -0
  355. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/tree-selector.ts +1246 -0
  356. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message-selector.ts +155 -0
  357. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/user-message.ts +35 -0
  358. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/components/visual-truncate.ts +50 -0
  359. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/interactive-mode.ts +5155 -0
  360. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/dark.json +85 -0
  361. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/light.json +84 -0
  362. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme-schema.json +335 -0
  363. package/pi.dev-src/pi-mono/coding-agent/src/modes/interactive/theme/theme.ts +1141 -0
  364. package/pi.dev-src/pi-mono/coding-agent/src/modes/print-mode.ts +158 -0
  365. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/jsonl.ts +58 -0
  366. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-client.ts +515 -0
  367. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-mode.ts +745 -0
  368. package/pi.dev-src/pi-mono/coding-agent/src/modes/rpc/rpc-types.ts +264 -0
  369. package/pi.dev-src/pi-mono/coding-agent/src/package-manager-cli.ts +270 -0
  370. package/pi.dev-src/pi-mono/coding-agent/src/utils/changelog.ts +99 -0
  371. package/pi.dev-src/pi-mono/coding-agent/src/utils/child-process.ts +86 -0
  372. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-image.ts +300 -0
  373. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard-native.ts +22 -0
  374. package/pi.dev-src/pi-mono/coding-agent/src/utils/clipboard.ts +81 -0
  375. package/pi.dev-src/pi-mono/coding-agent/src/utils/exif-orientation.ts +183 -0
  376. package/pi.dev-src/pi-mono/coding-agent/src/utils/frontmatter.ts +39 -0
  377. package/pi.dev-src/pi-mono/coding-agent/src/utils/fs-watch.ts +30 -0
  378. package/pi.dev-src/pi-mono/coding-agent/src/utils/git.ts +192 -0
  379. package/pi.dev-src/pi-mono/coding-agent/src/utils/image-convert.ts +41 -0
  380. package/pi.dev-src/pi-mono/coding-agent/src/utils/image-resize.ts +176 -0
  381. package/pi.dev-src/pi-mono/coding-agent/src/utils/mime.ts +30 -0
  382. package/pi.dev-src/pi-mono/coding-agent/src/utils/paths.ts +20 -0
  383. package/pi.dev-src/pi-mono/coding-agent/src/utils/photon.ts +139 -0
  384. package/pi.dev-src/pi-mono/coding-agent/src/utils/shell.ts +210 -0
  385. package/pi.dev-src/pi-mono/coding-agent/src/utils/sleep.ts +18 -0
  386. package/pi.dev-src/pi-mono/coding-agent/src/utils/tools-manager.ts +287 -0
  387. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-auto-compaction-queue.test.ts +437 -0
  388. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-branching.test.ts +156 -0
  389. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-compaction.test.ts +208 -0
  390. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-concurrent.test.ts +618 -0
  391. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-provider.test.ts +117 -0
  392. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-dynamic-tools.test.ts +182 -0
  393. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-retry.test.ts +318 -0
  394. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-runtime-events.test.ts +208 -0
  395. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-stats.test.ts +143 -0
  396. package/pi.dev-src/pi-mono/coding-agent/test/agent-session-tree-navigation.test.ts +323 -0
  397. package/pi.dev-src/pi-mono/coding-agent/test/args.test.ts +328 -0
  398. package/pi.dev-src/pi-mono/coding-agent/test/assistant-message.test.ts +57 -0
  399. package/pi.dev-src/pi-mono/coding-agent/test/auth-storage.test.ts +462 -0
  400. package/pi.dev-src/pi-mono/coding-agent/test/bash-close-hang-windows.test.ts +126 -0
  401. package/pi.dev-src/pi-mono/coding-agent/test/bash-execution-width.test.ts +80 -0
  402. package/pi.dev-src/pi-mono/coding-agent/test/block-images.test.ts +120 -0
  403. package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image-bmp-conversion.test.ts +88 -0
  404. package/pi.dev-src/pi-mono/coding-agent/test/clipboard-image.test.ts +136 -0
  405. package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions-example.test.ts +66 -0
  406. package/pi.dev-src/pi-mono/coding-agent/test/compaction-extensions.test.ts +415 -0
  407. package/pi.dev-src/pi-mono/coding-agent/test/compaction-serialization.test.ts +79 -0
  408. package/pi.dev-src/pi-mono/coding-agent/test/compaction-summary-reasoning.test.ts +118 -0
  409. package/pi.dev-src/pi-mono/coding-agent/test/compaction-thinking-model.test.ts +223 -0
  410. package/pi.dev-src/pi-mono/coding-agent/test/compaction.test.ts +550 -0
  411. package/pi.dev-src/pi-mono/coding-agent/test/config.test.ts +30 -0
  412. package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-legacy-input.test.ts +116 -0
  413. package/pi.dev-src/pi-mono/coding-agent/test/edit-tool-no-full-redraw.test.ts +235 -0
  414. package/pi.dev-src/pi-mono/coding-agent/test/export-html-whitespace.test.ts +12 -0
  415. package/pi.dev-src/pi-mono/coding-agent/test/export-html-xss.test.ts +28 -0
  416. package/pi.dev-src/pi-mono/coding-agent/test/extensions-discovery.test.ts +463 -0
  417. package/pi.dev-src/pi-mono/coding-agent/test/extensions-input-event.test.ts +112 -0
  418. package/pi.dev-src/pi-mono/coding-agent/test/extensions-runner.test.ts +805 -0
  419. package/pi.dev-src/pi-mono/coding-agent/test/file-mutation-queue.test.ts +163 -0
  420. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/assistant-message-with-thinking-code.json +33 -0
  421. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/before-compaction.jsonl +1003 -0
  422. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-agent/.gitkeep +0 -0
  423. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/empty-cwd/.gitkeep +0 -0
  424. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/large-session.jsonl +1019 -0
  425. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/consecutive-hyphens/SKILL.md +8 -0
  426. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/disable-model-invocation/SKILL.md +9 -0
  427. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-name-chars/SKILL.md +8 -0
  428. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/invalid-yaml/SKILL.md +8 -0
  429. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/long-name/SKILL.md +8 -0
  430. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/missing-description/SKILL.md +7 -0
  431. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/multiline-description/SKILL.md +11 -0
  432. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/name-mismatch/SKILL.md +8 -0
  433. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/nested/child-skill/SKILL.md +8 -0
  434. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/no-frontmatter/SKILL.md +3 -0
  435. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/SKILL.md +3 -0
  436. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/root-skill-preferred/nested-child/SKILL.md +3 -0
  437. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/unknown-field/SKILL.md +10 -0
  438. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills/valid-skill/SKILL.md +8 -0
  439. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/first/calendar/SKILL.md +8 -0
  440. package/pi.dev-src/pi-mono/coding-agent/test/fixtures/skills-collision/second/calendar/SKILL.md +8 -0
  441. package/pi.dev-src/pi-mono/coding-agent/test/footer-data-provider.test.ts +265 -0
  442. package/pi.dev-src/pi-mono/coding-agent/test/footer-width.test.ts +115 -0
  443. package/pi.dev-src/pi-mono/coding-agent/test/frontmatter.test.ts +60 -0
  444. package/pi.dev-src/pi-mono/coding-agent/test/git-ssh-url.test.ts +78 -0
  445. package/pi.dev-src/pi-mono/coding-agent/test/git-update.test.ts +411 -0
  446. package/pi.dev-src/pi-mono/coding-agent/test/image-processing.test.ts +145 -0
  447. package/pi.dev-src/pi-mono/coding-agent/test/image-resize-callers.test.ts +53 -0
  448. package/pi.dev-src/pi-mono/coding-agent/test/initial-message.test.ts +48 -0
  449. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-anthropic-warning.test.ts +73 -0
  450. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-clone-command.test.ts +77 -0
  451. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-compaction.test.ts +57 -0
  452. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-import-command.test.ts +144 -0
  453. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-status.test.ts +635 -0
  454. package/pi.dev-src/pi-mono/coding-agent/test/interactive-mode-suspend.test.ts +149 -0
  455. package/pi.dev-src/pi-mono/coding-agent/test/keybindings-migration.test.ts +88 -0
  456. package/pi.dev-src/pi-mono/coding-agent/test/model-registry.test.ts +1209 -0
  457. package/pi.dev-src/pi-mono/coding-agent/test/model-resolver.test.ts +436 -0
  458. package/pi.dev-src/pi-mono/coding-agent/test/package-command-paths.test.ts +144 -0
  459. package/pi.dev-src/pi-mono/coding-agent/test/package-manager-ssh.test.ts +97 -0
  460. package/pi.dev-src/pi-mono/coding-agent/test/package-manager.test.ts +1795 -0
  461. package/pi.dev-src/pi-mono/coding-agent/test/path-utils.test.ts +162 -0
  462. package/pi.dev-src/pi-mono/coding-agent/test/plan-mode-utils.test.ts +261 -0
  463. package/pi.dev-src/pi-mono/coding-agent/test/print-mode.test.ts +140 -0
  464. package/pi.dev-src/pi-mono/coding-agent/test/prompt-templates.test.ts +515 -0
  465. package/pi.dev-src/pi-mono/coding-agent/test/resource-loader.test.ts +576 -0
  466. package/pi.dev-src/pi-mono/coding-agent/test/rpc-client-clone.test.ts +29 -0
  467. package/pi.dev-src/pi-mono/coding-agent/test/rpc-example.ts +86 -0
  468. package/pi.dev-src/pi-mono/coding-agent/test/rpc-jsonl.test.ts +65 -0
  469. package/pi.dev-src/pi-mono/coding-agent/test/rpc-prompt-response-semantics.test.ts +285 -0
  470. package/pi.dev-src/pi-mono/coding-agent/test/rpc.test.ts +321 -0
  471. package/pi.dev-src/pi-mono/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts +364 -0
  472. package/pi.dev-src/pi-mono/coding-agent/test/sdk-openrouter-attribution.test.ts +181 -0
  473. package/pi.dev-src/pi-mono/coding-agent/test/sdk-session-manager.test.ts +95 -0
  474. package/pi.dev-src/pi-mono/coding-agent/test/sdk-skills.test.ts +109 -0
  475. package/pi.dev-src/pi-mono/coding-agent/test/session-cwd.test.ts +91 -0
  476. package/pi.dev-src/pi-mono/coding-agent/test/session-info-modified-timestamp.test.ts +83 -0
  477. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/build-context.test.ts +268 -0
  478. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/custom-session-id.test.ts +109 -0
  479. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/file-operations.test.ts +208 -0
  480. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/labels.test.ts +198 -0
  481. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/migration.test.ts +78 -0
  482. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/save-entry.test.ts +55 -0
  483. package/pi.dev-src/pi-mono/coding-agent/test/session-manager/tree-traversal.test.ts +533 -0
  484. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-path-delete.test.ts +315 -0
  485. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-rename.test.ts +111 -0
  486. package/pi.dev-src/pi-mono/coding-agent/test/session-selector-search.test.ts +195 -0
  487. package/pi.dev-src/pi-mono/coding-agent/test/settings-manager-bug.test.ts +147 -0
  488. package/pi.dev-src/pi-mono/coding-agent/test/settings-manager.test.ts +319 -0
  489. package/pi.dev-src/pi-mono/coding-agent/test/skills.test.ts +432 -0
  490. package/pi.dev-src/pi-mono/coding-agent/test/stdout-cleanliness.test.ts +103 -0
  491. package/pi.dev-src/pi-mono/coding-agent/test/streaming-render-debug.ts +97 -0
  492. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-bash-persistence.test.ts +242 -0
  493. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-compaction.test.ts +343 -0
  494. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-model-extension.test.ts +325 -0
  495. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-prompt.test.ts +323 -0
  496. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-queue.test.ts +422 -0
  497. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-retry-events.test.ts +358 -0
  498. package/pi.dev-src/pi-mono/coding-agent/test/suite/agent-session-runtime.test.ts +553 -0
  499. package/pi.dev-src/pi-mono/coding-agent/test/suite/harness.ts +208 -0
  500. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2023-queued-slash-command-followup.test.ts +80 -0
  501. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2753-reload-stale-resource-settings.test.ts +100 -0
  502. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2781-skill-collision-precedence.test.ts +120 -0
  503. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts +106 -0
  504. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.ts +94 -0
  505. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/2860-replaced-session-context.test.ts +274 -0
  506. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3217-scoped-model-order.test.ts +104 -0
  507. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts +72 -0
  508. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts +83 -0
  509. package/pi.dev-src/pi-mono/coding-agent/test/suite/regressions/3317-network-connection-lost-retry.test.ts +33 -0
  510. package/pi.dev-src/pi-mono/coding-agent/test/system-prompt.test.ts +102 -0
  511. package/pi.dev-src/pi-mono/coding-agent/test/test-harness.test.ts +321 -0
  512. package/pi.dev-src/pi-mono/coding-agent/test/test-harness.ts +446 -0
  513. package/pi.dev-src/pi-mono/coding-agent/test/test-theme-colors.ts +246 -0
  514. package/pi.dev-src/pi-mono/coding-agent/test/theme-export.test.ts +104 -0
  515. package/pi.dev-src/pi-mono/coding-agent/test/tool-execution-component.test.ts +331 -0
  516. package/pi.dev-src/pi-mono/coding-agent/test/tools.test.ts +889 -0
  517. package/pi.dev-src/pi-mono/coding-agent/test/tree-selector.test.ts +655 -0
  518. package/pi.dev-src/pi-mono/coding-agent/test/trigger-compact-extension.test.ts +57 -0
  519. package/pi.dev-src/pi-mono/coding-agent/test/truncate-to-width.test.ts +81 -0
  520. package/pi.dev-src/pi-mono/coding-agent/test/user-message.test.ts +25 -0
  521. package/pi.dev-src/pi-mono/coding-agent/test/utilities.ts +316 -0
  522. package/pi.dev-src/pi-mono/coding-agent/tsconfig.examples.json +16 -0
  523. package/pi.dev-src/pi-mono/coding-agent/vitest.config.ts +26 -0
  524. package/pi.dev-src/pi-mono/mom/dev.sh +30 -0
  525. package/pi.dev-src/pi-mono/mom/docker.sh +95 -0
  526. package/pi.dev-src/pi-mono/mom/scripts/migrate-timestamps.ts +121 -0
  527. package/pi.dev-src/pi-mono/mom/src/agent.ts +882 -0
  528. package/pi.dev-src/pi-mono/mom/src/context.ts +180 -0
  529. package/pi.dev-src/pi-mono/mom/src/download.ts +117 -0
  530. package/pi.dev-src/pi-mono/mom/src/events.ts +442 -0
  531. package/pi.dev-src/pi-mono/mom/src/fs-watch.ts +30 -0
  532. package/pi.dev-src/pi-mono/mom/src/log.ts +271 -0
  533. package/pi.dev-src/pi-mono/mom/src/main.ts +367 -0
  534. package/pi.dev-src/pi-mono/mom/src/sandbox.ts +221 -0
  535. package/pi.dev-src/pi-mono/mom/src/slack.ts +623 -0
  536. package/pi.dev-src/pi-mono/mom/src/store.ts +234 -0
  537. package/pi.dev-src/pi-mono/mom/src/tools/attach.ts +47 -0
  538. package/pi.dev-src/pi-mono/mom/src/tools/bash.ts +97 -0
  539. package/pi.dev-src/pi-mono/mom/src/tools/edit.ts +165 -0
  540. package/pi.dev-src/pi-mono/mom/src/tools/index.ts +19 -0
  541. package/pi.dev-src/pi-mono/mom/src/tools/read.ts +159 -0
  542. package/pi.dev-src/pi-mono/mom/src/tools/truncate.ts +236 -0
  543. package/pi.dev-src/pi-mono/mom/src/tools/write.ts +45 -0
  544. package/pi.dev-src/pi-mono/mom/test/events.test.ts +50 -0
  545. package/pi.dev-src/pi-mono/pods/scripts/model_run.sh +83 -0
  546. package/pi.dev-src/pi-mono/pods/scripts/pod_setup.sh +336 -0
  547. package/pi.dev-src/pi-mono/pods/src/cli.ts +360 -0
  548. package/pi.dev-src/pi-mono/pods/src/commands/models.ts +753 -0
  549. package/pi.dev-src/pi-mono/pods/src/commands/pods.ts +205 -0
  550. package/pi.dev-src/pi-mono/pods/src/commands/prompt.ts +84 -0
  551. package/pi.dev-src/pi-mono/pods/src/config.ts +80 -0
  552. package/pi.dev-src/pi-mono/pods/src/index.ts +2 -0
  553. package/pi.dev-src/pi-mono/pods/src/model-configs.ts +111 -0
  554. package/pi.dev-src/pi-mono/pods/src/models.json +295 -0
  555. package/pi.dev-src/pi-mono/pods/src/ssh.ts +151 -0
  556. package/pi.dev-src/pi-mono/pods/src/types.ts +27 -0
  557. package/pi.dev-src/pi-mono/tui/src/autocomplete.ts +783 -0
  558. package/pi.dev-src/pi-mono/tui/src/components/box.ts +137 -0
  559. package/pi.dev-src/pi-mono/tui/src/components/cancellable-loader.ts +40 -0
  560. package/pi.dev-src/pi-mono/tui/src/components/editor.ts +2280 -0
  561. package/pi.dev-src/pi-mono/tui/src/components/image.ts +104 -0
  562. package/pi.dev-src/pi-mono/tui/src/components/input.ts +503 -0
  563. package/pi.dev-src/pi-mono/tui/src/components/loader.ts +86 -0
  564. package/pi.dev-src/pi-mono/tui/src/components/markdown.ts +852 -0
  565. package/pi.dev-src/pi-mono/tui/src/components/select-list.ts +229 -0
  566. package/pi.dev-src/pi-mono/tui/src/components/settings-list.ts +250 -0
  567. package/pi.dev-src/pi-mono/tui/src/components/spacer.ts +28 -0
  568. package/pi.dev-src/pi-mono/tui/src/components/text.ts +106 -0
  569. package/pi.dev-src/pi-mono/tui/src/components/truncated-text.ts +65 -0
  570. package/pi.dev-src/pi-mono/tui/src/editor-component.ts +74 -0
  571. package/pi.dev-src/pi-mono/tui/src/fuzzy.ts +133 -0
  572. package/pi.dev-src/pi-mono/tui/src/index.ts +106 -0
  573. package/pi.dev-src/pi-mono/tui/src/keybindings.ts +244 -0
  574. package/pi.dev-src/pi-mono/tui/src/keys.ts +1400 -0
  575. package/pi.dev-src/pi-mono/tui/src/kill-ring.ts +46 -0
  576. package/pi.dev-src/pi-mono/tui/src/stdin-buffer.ts +386 -0
  577. package/pi.dev-src/pi-mono/tui/src/terminal-image.ts +414 -0
  578. package/pi.dev-src/pi-mono/tui/src/terminal.ts +373 -0
  579. package/pi.dev-src/pi-mono/tui/src/tui.ts +1243 -0
  580. package/pi.dev-src/pi-mono/tui/src/undo-stack.ts +28 -0
  581. package/pi.dev-src/pi-mono/tui/src/utils.ts +1085 -0
  582. package/pi.dev-src/pi-mono/tui/test/autocomplete.test.ts +542 -0
  583. package/pi.dev-src/pi-mono/tui/test/bug-regression-isimageline-startswith-bug.test.ts +237 -0
  584. package/pi.dev-src/pi-mono/tui/test/chat-simple.ts +129 -0
  585. package/pi.dev-src/pi-mono/tui/test/editor.test.ts +3812 -0
  586. package/pi.dev-src/pi-mono/tui/test/fuzzy.test.ts +98 -0
  587. package/pi.dev-src/pi-mono/tui/test/image-test.ts +56 -0
  588. package/pi.dev-src/pi-mono/tui/test/input.test.ts +580 -0
  589. package/pi.dev-src/pi-mono/tui/test/key-tester.ts +105 -0
  590. package/pi.dev-src/pi-mono/tui/test/keybindings.test.ts +38 -0
  591. package/pi.dev-src/pi-mono/tui/test/keys.test.ts +614 -0
  592. package/pi.dev-src/pi-mono/tui/test/markdown.test.ts +1236 -0
  593. package/pi.dev-src/pi-mono/tui/test/overlay-non-capturing.test.ts +609 -0
  594. package/pi.dev-src/pi-mono/tui/test/overlay-options.test.ts +538 -0
  595. package/pi.dev-src/pi-mono/tui/test/overlay-short-content.test.ts +56 -0
  596. package/pi.dev-src/pi-mono/tui/test/regression-regional-indicator-width.test.ts +52 -0
  597. package/pi.dev-src/pi-mono/tui/test/select-list.test.ts +116 -0
  598. package/pi.dev-src/pi-mono/tui/test/stdin-buffer.test.ts +422 -0
  599. package/pi.dev-src/pi-mono/tui/test/terminal-image.test.ts +279 -0
  600. package/pi.dev-src/pi-mono/tui/test/test-themes.ts +38 -0
  601. package/pi.dev-src/pi-mono/tui/test/truncate-to-width.test.ts +62 -0
  602. package/pi.dev-src/pi-mono/tui/test/truncated-text.test.ts +129 -0
  603. package/pi.dev-src/pi-mono/tui/test/tui-cell-size-input.test.ts +81 -0
  604. package/pi.dev-src/pi-mono/tui/test/tui-overlay-style-leak.test.ts +72 -0
  605. package/pi.dev-src/pi-mono/tui/test/tui-render.test.ts +509 -0
  606. package/pi.dev-src/pi-mono/tui/test/viewport-overwrite-repro.ts +106 -0
  607. package/pi.dev-src/pi-mono/tui/test/virtual-terminal.ts +218 -0
  608. package/pi.dev-src/pi-mono/tui/test/wrap-ansi.test.ts +207 -0
  609. package/pi.dev-src/pi-mono/tui/vitest.config.ts +7 -0
  610. package/pi.dev-src/pi-mono/web-ui/example/index.html +13 -0
  611. package/pi.dev-src/pi-mono/web-ui/example/src/app.css +1 -0
  612. package/pi.dev-src/pi-mono/web-ui/example/src/custom-messages.ts +99 -0
  613. package/pi.dev-src/pi-mono/web-ui/example/src/main.ts +421 -0
  614. package/pi.dev-src/pi-mono/web-ui/example/tsconfig.json +23 -0
  615. package/pi.dev-src/pi-mono/web-ui/example/vite.config.ts +6 -0
  616. package/pi.dev-src/pi-mono/web-ui/scripts/count-prompt-tokens.ts +88 -0
  617. package/pi.dev-src/pi-mono/web-ui/src/ChatPanel.ts +209 -0
  618. package/pi.dev-src/pi-mono/web-ui/src/app.css +68 -0
  619. package/pi.dev-src/pi-mono/web-ui/src/components/AgentInterface.ts +403 -0
  620. package/pi.dev-src/pi-mono/web-ui/src/components/AttachmentTile.ts +107 -0
  621. package/pi.dev-src/pi-mono/web-ui/src/components/ConsoleBlock.ts +72 -0
  622. package/pi.dev-src/pi-mono/web-ui/src/components/CustomProviderCard.ts +100 -0
  623. package/pi.dev-src/pi-mono/web-ui/src/components/ExpandableSection.ts +46 -0
  624. package/pi.dev-src/pi-mono/web-ui/src/components/Input.ts +113 -0
  625. package/pi.dev-src/pi-mono/web-ui/src/components/MessageEditor.ts +405 -0
  626. package/pi.dev-src/pi-mono/web-ui/src/components/MessageList.ts +98 -0
  627. package/pi.dev-src/pi-mono/web-ui/src/components/Messages.ts +383 -0
  628. package/pi.dev-src/pi-mono/web-ui/src/components/ProviderKeyInput.ts +153 -0
  629. package/pi.dev-src/pi-mono/web-ui/src/components/SandboxedIframe.ts +626 -0
  630. package/pi.dev-src/pi-mono/web-ui/src/components/StreamingMessageContainer.ts +103 -0
  631. package/pi.dev-src/pi-mono/web-ui/src/components/ThinkingBlock.ts +43 -0
  632. package/pi.dev-src/pi-mono/web-ui/src/components/message-renderer-registry.ts +28 -0
  633. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ArtifactsRuntimeProvider.ts +220 -0
  634. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
  635. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
  636. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
  637. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
  638. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
  639. package/pi.dev-src/pi-mono/web-ui/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
  640. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ApiKeyPromptDialog.ts +75 -0
  641. package/pi.dev-src/pi-mono/web-ui/src/dialogs/AttachmentOverlay.ts +636 -0
  642. package/pi.dev-src/pi-mono/web-ui/src/dialogs/CustomProviderDialog.ts +274 -0
  643. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ModelSelector.ts +370 -0
  644. package/pi.dev-src/pi-mono/web-ui/src/dialogs/PersistentStorageDialog.ts +144 -0
  645. package/pi.dev-src/pi-mono/web-ui/src/dialogs/ProvidersModelsTab.ts +212 -0
  646. package/pi.dev-src/pi-mono/web-ui/src/dialogs/SessionListDialog.ts +150 -0
  647. package/pi.dev-src/pi-mono/web-ui/src/dialogs/SettingsDialog.ts +218 -0
  648. package/pi.dev-src/pi-mono/web-ui/src/index.ts +120 -0
  649. package/pi.dev-src/pi-mono/web-ui/src/prompts/prompts.ts +282 -0
  650. package/pi.dev-src/pi-mono/web-ui/src/storage/app-storage.ts +60 -0
  651. package/pi.dev-src/pi-mono/web-ui/src/storage/backends/indexeddb-storage-backend.ts +193 -0
  652. package/pi.dev-src/pi-mono/web-ui/src/storage/store.ts +33 -0
  653. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/custom-providers-store.ts +62 -0
  654. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/provider-keys-store.ts +33 -0
  655. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/sessions-store.ts +136 -0
  656. package/pi.dev-src/pi-mono/web-ui/src/storage/stores/settings-store.ts +34 -0
  657. package/pi.dev-src/pi-mono/web-ui/src/storage/types.ts +206 -0
  658. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactElement.ts +14 -0
  659. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ArtifactPill.ts +26 -0
  660. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/Console.ts +93 -0
  661. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/DocxArtifact.ts +213 -0
  662. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ExcelArtifact.ts +231 -0
  663. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/GenericArtifact.ts +117 -0
  664. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/HtmlArtifact.ts +195 -0
  665. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/ImageArtifact.ts +116 -0
  666. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/MarkdownArtifact.ts +82 -0
  667. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/PdfArtifact.ts +201 -0
  668. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/SvgArtifact.ts +119 -0
  669. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/TextArtifact.ts +148 -0
  670. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts +310 -0
  671. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/artifacts.ts +713 -0
  672. package/pi.dev-src/pi-mono/web-ui/src/tools/artifacts/index.ts +7 -0
  673. package/pi.dev-src/pi-mono/web-ui/src/tools/extract-document.ts +275 -0
  674. package/pi.dev-src/pi-mono/web-ui/src/tools/index.ts +46 -0
  675. package/pi.dev-src/pi-mono/web-ui/src/tools/javascript-repl.ts +293 -0
  676. package/pi.dev-src/pi-mono/web-ui/src/tools/renderer-registry.ts +130 -0
  677. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/BashRenderer.ts +52 -0
  678. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/CalculateRenderer.ts +58 -0
  679. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/DefaultRenderer.ts +103 -0
  680. package/pi.dev-src/pi-mono/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
  681. package/pi.dev-src/pi-mono/web-ui/src/tools/types.ts +15 -0
  682. package/pi.dev-src/pi-mono/web-ui/src/utils/attachment-utils.ts +472 -0
  683. package/pi.dev-src/pi-mono/web-ui/src/utils/auth-token.ts +22 -0
  684. package/pi.dev-src/pi-mono/web-ui/src/utils/format.ts +42 -0
  685. package/pi.dev-src/pi-mono/web-ui/src/utils/i18n.ts +653 -0
  686. package/pi.dev-src/pi-mono/web-ui/src/utils/model-discovery.ts +277 -0
  687. package/pi.dev-src/pi-mono/web-ui/src/utils/proxy-utils.ts +139 -0
  688. package/pi.dev-src/pi-mono/web-ui/src/utils/test-sessions.ts +2357 -0
  689. package/pi.dev-src/pi-mono/web-ui/tsconfig.json +7 -0
  690. package/scripts/lib/extension-debug-harness.ts +3 -10
  691. package/scripts/lib/recording-extension-api.ts +71 -2
  692. package/scripts/pi-usereq-debug.sh +1 -1
  693. package/scripts/tool-args-to-params.ts +4 -15
  694. package/src/cli.ts +24 -68
  695. package/src/core/agent-tool-json.ts +53 -239
  696. package/src/core/compress-payload.ts +4 -74
  697. package/src/core/compress.ts +26 -15
  698. package/src/core/config.ts +546 -65
  699. package/src/core/debug-runtime.ts +542 -0
  700. package/src/core/extension-status.ts +324 -202
  701. package/src/core/find-constructs.ts +3 -3
  702. package/src/core/find-payload.ts +26 -132
  703. package/src/core/path-context.ts +346 -81
  704. package/src/core/pi-notify.ts +585 -315
  705. package/src/core/pi-usereq-tools.ts +57 -19
  706. package/src/core/prompt-command-catalog.ts +46 -0
  707. package/src/core/prompt-command-runtime.ts +1650 -0
  708. package/src/core/prompt-command-state.ts +211 -0
  709. package/src/core/prompts.ts +173 -41
  710. package/src/core/reference-payload.ts +5 -63
  711. package/src/core/resources.ts +61 -5
  712. package/src/core/settings-menu.ts +33 -7
  713. package/src/core/source-analyzer.ts +27 -7
  714. package/src/core/token-counter.ts +61 -79
  715. package/src/core/tool-runner.ts +38 -317
  716. package/src/index.ts +2629 -1220
  717. package/src/resources/images/favicon.svg +21 -0
  718. package/src/resources/images/pi.dev.png +0 -0
  719. package/src/resources/instructions/git_commit.md +16 -0
  720. package/src/resources/instructions/git_read-only.md +2 -0
  721. package/src/resources/prompts/analyze.md +12 -13
  722. package/src/resources/prompts/change.md +21 -59
  723. package/src/resources/prompts/check.md +14 -16
  724. package/src/resources/prompts/cover.md +21 -59
  725. package/src/resources/prompts/create.md +5 -4
  726. package/src/resources/prompts/fix.md +20 -58
  727. package/src/resources/prompts/flowchart.md +14 -49
  728. package/src/resources/prompts/implement.md +14 -50
  729. package/src/resources/prompts/new.md +20 -58
  730. package/src/resources/prompts/readme.md +14 -50
  731. package/src/resources/prompts/recreate.md +14 -45
  732. package/src/resources/prompts/refactor.md +20 -58
  733. package/src/resources/prompts/references.md +7 -43
  734. package/src/resources/prompts/renumber.md +8 -39
  735. package/src/resources/prompts/workflow.md +14 -50
  736. package/src/resources/prompts/write.md +5 -4
  737. package/tests/attended-results-scenarios.ts +30 -136
  738. package/tests/cli-command-option-parity.test.ts +148 -192
  739. package/tests/debug-extension-harness.test.ts +74 -73
  740. package/tests/extension-registration.test.ts +3626 -685
  741. package/tests/fixtures_attended_results/project/compress-line-numbers.json +1 -1
  742. package/tests/fixtures_attended_results/project/compress.json +1 -1
  743. package/tests/fixtures_attended_results/project/find-line-numbers.json +1 -1
  744. package/tests/fixtures_attended_results/project/find.json +1 -1
  745. package/tests/fixtures_attended_results/project/references.json +2 -2
  746. package/tests/fixtures_attended_results/standalone/files-compress/fixture_go.go.json +1 -1
  747. package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_go.go.json +1 -1
  748. package/tests/fixtures_attended_results/standalone/files-find/fixture_go.go.json +1 -1
  749. package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_go.go.json +1 -1
  750. package/tests/fixtures_attended_results/standalone/files-references/fixture_c.c.json +1 -1
  751. package/tests/fixtures_attended_results/standalone/files-references/fixture_cpp.cpp.json +1 -1
  752. package/tests/fixtures_attended_results/standalone/files-references/fixture_csharp.cs.json +1 -1
  753. package/tests/fixtures_attended_results/standalone/files-references/fixture_elixir.ex.json +1 -1
  754. package/tests/fixtures_attended_results/standalone/files-references/fixture_go.go.json +1 -1
  755. package/tests/fixtures_attended_results/standalone/files-references/fixture_haskell.hs.json +1 -1
  756. package/tests/fixtures_attended_results/standalone/files-references/fixture_java.java.json +1 -1
  757. package/tests/fixtures_attended_results/standalone/files-references/fixture_javascript.js.json +1 -1
  758. package/tests/fixtures_attended_results/standalone/files-references/fixture_kotlin.kt.json +1 -1
  759. package/tests/fixtures_attended_results/standalone/files-references/fixture_lua.lua.json +1 -1
  760. package/tests/fixtures_attended_results/standalone/files-references/fixture_perl.pl.json +1 -1
  761. package/tests/fixtures_attended_results/standalone/files-references/fixture_php.php.json +1 -1
  762. package/tests/fixtures_attended_results/standalone/files-references/fixture_python.py.json +1 -1
  763. package/tests/fixtures_attended_results/standalone/files-references/fixture_ruby.rb.json +1 -1
  764. package/tests/fixtures_attended_results/standalone/files-references/fixture_rust.rs.json +1 -1
  765. package/tests/fixtures_attended_results/standalone/files-references/fixture_scala.scala.json +1 -1
  766. package/tests/fixtures_attended_results/standalone/files-references/fixture_shell.sh.json +1 -1
  767. package/tests/fixtures_attended_results/standalone/files-references/fixture_swift.swift.json +1 -1
  768. package/tests/fixtures_attended_results/standalone/files-references/fixture_typescript.ts.json +1 -1
  769. package/tests/fixtures_attended_results/standalone/files-references/fixture_zig.zig.json +1 -1
  770. package/tests/helpers.ts +24 -15
  771. package/tests/oracle-project.test.ts +29 -30
  772. package/tests/oracle-standalone.test.ts +27 -1
  773. package/tests/prompt-rendering.test.ts +40 -9
  774. package/tests/python_oracle_stubs/tiktoken.py +76 -0
@@ -0,0 +1,1650 @@
1
+ /**
2
+ * @file
3
+ * @brief Implements prompt-command preflight and worktree orchestration.
4
+ * @details Centralizes `req-<prompt>` repository validation, prompt-specific required-document checks, slash-command-owned worktree naming and lifecycle handling, session-backed cwd switching plus verification, persisted replacement-session context reuse for non-command lifecycle handlers, matched-success fast-forward merge finalization, and command-side abort cleanup. Runtime is dominated by git subprocess execution plus bounded filesystem and session-file metadata checks. Side effects include active-session replacement, worktree creation and deletion, branch merges, and filesystem reads and writes.
5
+ */
6
+
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { spawnSync } from "node:child_process";
10
+ import { ReqError } from "./errors.js";
11
+ import { classifyPiNotifyOutcome, type PiNotifyOutcome } from "./pi-notify.js";
12
+ import {
13
+ normalizeGitWorktreePrefix,
14
+ resolveEffectiveGitWorktreeEnabled,
15
+ type UseReqConfig,
16
+ } from "./config.js";
17
+ import {
18
+ logDebugPromptEvent,
19
+ logDebugPromptWorkflowEvent,
20
+ type DebugWorkflowState,
21
+ } from "./debug-runtime.js";
22
+ import {
23
+ PROMPT_COMMAND_NAMES,
24
+ type PromptCommandName,
25
+ } from "./prompt-command-catalog.js";
26
+ import {
27
+ isSameOrAncestorPath,
28
+ normalizeRelativeDirContract,
29
+ setRuntimeContextPath,
30
+ setRuntimeGitPath,
31
+ setRuntimeWorktreePathState,
32
+ } from "./path-context.js";
33
+ import { SessionManager } from "@mariozechner/pi-coding-agent";
34
+ import { resolveRuntimeGitPath } from "./runtime-project-paths.js";
35
+ import {
36
+ clearPersistedPromptCommandSessionContext,
37
+ readPersistedPromptCommandSessionContext,
38
+ writePersistedPromptCommandSessionContext,
39
+ } from "./prompt-command-state.js";
40
+
41
+ /**
42
+ * @brief Describes one canonical required-document probe.
43
+ * @details Binds a canonical doc filename to the remediation prompt command surfaced on failure so prompt-specific doc validation can stay deterministic. The interface is compile-time only and introduces no runtime cost.
44
+ */
45
+ export interface PromptRequiredDocSpec {
46
+ fileName: "REQUIREMENTS.md" | "WORKFLOW.md" | "REFERENCES.md";
47
+ promptCommand: "/req-write" | "/req-workflow" | "/req-references";
48
+ }
49
+
50
+ /**
51
+ * @brief Describes one prompt-command execution plan tracked across lifecycle hooks.
52
+ * @details Stores the prompt identity, runtime git root, associated branch name, original project base, execution context path, persisted origin and execution session files, and optional worktree metadata so the extension can switch all cwd surfaces before prompt dispatch and finalize worktree lifecycle after agent end. The interface is compile-time only and introduces no runtime cost.
53
+ */
54
+ export interface PromptCommandExecutionPlan {
55
+ promptName: PromptCommandName;
56
+ promptArgs: string;
57
+ gitPath: string;
58
+ branchName: string;
59
+ basePath: string;
60
+ contextPath: string;
61
+ parentPath: string;
62
+ baseDir: string;
63
+ originalSessionFile: string;
64
+ executionSessionFile: string;
65
+ worktreeDir?: string;
66
+ worktreePath?: string;
67
+ worktreeRootPath?: string;
68
+ }
69
+
70
+ /**
71
+ * @brief Describes one post-create test hook payload for prompt-command worktrees.
72
+ * @details Exposes the git root, generated worktree name, sibling worktree path, and effective execution base so tests can simulate post-create verification failures deterministically. The interface is compile-time only and introduces no runtime cost.
73
+ */
74
+ interface PromptCommandPostCreateHookContext {
75
+ gitPath: string;
76
+ worktreeDir: string;
77
+ worktreeRootPath: string;
78
+ worktreePath: string;
79
+ }
80
+
81
+ /**
82
+ * @brief Represents one synchronous test hook invoked after prompt worktree creation.
83
+ * @details Allows tests to mutate or remove newly created worktree artifacts before verification executes. The alias is compile-time only and introduces no runtime cost.
84
+ */
85
+ type PromptCommandPostCreateHook = (context: PromptCommandPostCreateHookContext) => void;
86
+
87
+ /**
88
+ * @brief Describes optional debug logging context for prompt orchestration helpers.
89
+ * @details Carries the effective project configuration and current workflow state so prompt-runtime helpers can append selected debug entries without depending on extension UI types. The interface is compile-time only and introduces no runtime cost.
90
+ */
91
+ interface PromptCommandDebugOptions {
92
+ config: UseReqConfig;
93
+ workflowState: DebugWorkflowState;
94
+ }
95
+
96
+ /**
97
+ * @brief Describes prompt-delivery options supported by replacement-session callbacks.
98
+ * @details Mirrors the documented `sendUserMessage(...)` delivery modes needed when prompt orchestration targets a replacement session after a slash-command-owned session switch. The interface is compile-time only and introduces no runtime cost.
99
+ */
100
+ interface PromptCommandSessionMessageOptions {
101
+ deliverAs?: "steer" | "followUp";
102
+ }
103
+
104
+ /**
105
+ * @brief Describes the replacement-session callback options accepted by session switching.
106
+ * @details Mirrors the documented pi runtime `withSession(...)` hook so prompt-command orchestration can continue work against the replacement session after the old command context becomes stale. The interface is compile-time only and introduces no runtime cost.
107
+ */
108
+ interface PromptCommandSessionSwitchOptions {
109
+ withSession?: (ctx: PromptCommandActiveContext) => Promise<void>;
110
+ }
111
+
112
+ /**
113
+ * @brief Describes the minimal session-bound surface available after session replacement.
114
+ * @details Extends the shared prompt-command context with `sendUserMessage(...)` so prompt dispatch can target the replacement session without reusing stale pre-switch runtime objects. The interface is compile-time only and introduces no runtime cost.
115
+ */
116
+ interface PromptCommandActiveContext extends PromptCommandSessionContext {
117
+ sendUserMessage?: (
118
+ content: string | Array<{ type: string; text?: string }>,
119
+ options?: PromptCommandSessionMessageOptions,
120
+ ) => Promise<void> | void;
121
+ }
122
+
123
+ /**
124
+ * @brief Describes one serializable session entry copied into a materialized execution-session file.
125
+ * @details Captures the stable tree-entry fields needed to write a JSONL session snapshot for cross-cwd session replacement when the origin session file has not been flushed yet. The interface is compile-time only and introduces no runtime cost.
126
+ */
127
+ interface PromptCommandSessionEntry {
128
+ type: string;
129
+ id: string;
130
+ parentId: string | null;
131
+ timestamp: string;
132
+ [key: string]: unknown;
133
+ }
134
+
135
+ /**
136
+ * @brief Describes the minimal command-context session surface used by prompt orchestration.
137
+ * @details Narrows extension command contexts to the `switchSession(...)` hook, the mutable `cwd` mirror, and the session metadata probes required for cwd verification and session snapshot materialization. The interface is compile-time only and introduces no runtime cost.
138
+ */
139
+ interface PromptCommandSessionContext {
140
+ cwd?: string;
141
+ switchSession?: (
142
+ sessionPath: string,
143
+ options?: PromptCommandSessionSwitchOptions,
144
+ ) => Promise<{ cancelled?: boolean } | void> | { cancelled?: boolean } | void;
145
+ sessionManager?: {
146
+ getBranch?: () => PromptCommandSessionEntry[];
147
+ getCwd?: () => string;
148
+ getSessionDir?: () => string | undefined;
149
+ getSessionFile?: () => string | undefined;
150
+ };
151
+ }
152
+
153
+ /**
154
+ * @brief Describes one error object enriched with a replacement-session context.
155
+ * @details Allows prompt orchestration helpers to preserve the last valid session-bound context across replacement boundaries so callers can continue notifications and status updates after switch-triggered failures. The interface is compile-time only and introduces no runtime cost.
156
+ */
157
+ interface PromptCommandContextError extends Error {
158
+ promptContext?: PromptCommandSessionContext;
159
+ }
160
+
161
+ /**
162
+ * @brief Stores the optional prompt-command post-create test hook.
163
+ * @details The hook is undefined in production and is invoked only by tests that need deterministic post-create verification failures. Access complexity is O(1).
164
+ */
165
+ let promptCommandPostCreateHook: PromptCommandPostCreateHook | undefined;
166
+
167
+ /**
168
+ * @brief Tests whether the current session file remains reusable for prompt-command bootstrap.
169
+ * @details Accepts only persisted session files whose header cwd is readable, still exists on disk, and remains inside the active project base. This rejects stale execution-session files that still point at deleted or sibling worktrees from earlier prompt runs. Runtime is O(p) plus one session-header read. No external state is mutated.
170
+ * @param[in] sessionFile {string | undefined} Candidate current session file.
171
+ * @param[in] projectBase {string} Active project base path.
172
+ * @return {sessionFile is string} `true` when the session file remains reusable for prompt bootstrap.
173
+ */
174
+ function isUsablePromptSessionFile(
175
+ sessionFile: string | undefined,
176
+ projectBase: string,
177
+ ): sessionFile is string {
178
+ if (typeof sessionFile !== "string" || sessionFile === "") {
179
+ return false;
180
+ }
181
+ const normalizedSessionFile = path.resolve(sessionFile);
182
+ if (!fs.existsSync(normalizedSessionFile) || !fs.statSync(normalizedSessionFile).isFile()) {
183
+ return false;
184
+ }
185
+ const persistedSessionCwd = readPromptSessionFileCwd(normalizedSessionFile);
186
+ if (typeof persistedSessionCwd !== "string") {
187
+ return false;
188
+ }
189
+ const normalizedSessionCwd = path.resolve(persistedSessionCwd);
190
+ return fs.existsSync(normalizedSessionCwd)
191
+ && isSameOrAncestorPath(path.resolve(projectBase), normalizedSessionCwd);
192
+ }
193
+
194
+ /**
195
+ * @brief Resolves the session file path used as the origin for prompt-command session switching.
196
+ * @details Reuses the current session file only when its persisted header cwd is still readable, exists on disk, and remains inside the active project base. Otherwise allocates a fresh session file path rooted at the supplied cwd so later worktree switching and restoration never inherit stale deleted-worktree session metadata. Runtime is dominated by one optional session-header read plus optional session-file allocation. Side effects include session-file path allocation when the active session metadata is stale or ephemeral.
197
+ * @param[in] sessionFile {string | undefined} Current active session file when available.
198
+ * @param[in] cwd {string} Working directory that should own the resolved session file.
199
+ * @return {string} Session file path reserved for prompt orchestration.
200
+ * @throws {ReqError} Throws when a session file path cannot be resolved.
201
+ */
202
+ function resolvePromptSessionFile(sessionFile: string | undefined, cwd: string): string {
203
+ if (isUsablePromptSessionFile(sessionFile, cwd)) {
204
+ return path.resolve(sessionFile);
205
+ }
206
+ const sessionManager = SessionManager.create(cwd);
207
+ const createdSessionFile = sessionManager.getSessionFile();
208
+ if (typeof createdSessionFile !== "string" || createdSessionFile === "") {
209
+ throw new ReqError(`ERROR: Unable to prepare persisted session for ${cwd}.`, 1);
210
+ }
211
+ return path.resolve(createdSessionFile);
212
+ }
213
+
214
+ /**
215
+ * @brief Writes one execution-session snapshot file with the target worktree cwd.
216
+ * @details Persists a version-3 JSONL session header whose `cwd` equals the supplied target worktree path, then appends the supplied current-session branch entries unchanged so pi can reopen the replacement session in the correct cwd even when the origin session file has not been flushed yet. Runtime is O(n) in branch-entry count plus serialized byte size. Side effects include directory creation and file overwrite.
217
+ * @param[in] sessionFile {string} Target execution-session file path.
218
+ * @param[in] sessionId {string} Generated execution-session identifier.
219
+ * @param[in] targetCwd {string} Worktree path stored in the session header.
220
+ * @param[in] parentSessionFile {string | undefined} Optional origin session path recorded as `parentSession`.
221
+ * @param[in] branchEntries {PromptCommandSessionEntry[]} Current session branch entries copied into the new session file.
222
+ * @return {void} No return value.
223
+ * @throws {ReqError} Throws when the execution-session snapshot cannot be written.
224
+ * @satisfies REQ-271
225
+ */
226
+ function writePromptExecutionSessionSnapshot(
227
+ sessionFile: string,
228
+ sessionId: string,
229
+ targetCwd: string,
230
+ parentSessionFile: string | undefined,
231
+ branchEntries: PromptCommandSessionEntry[],
232
+ ): void {
233
+ const sessionHeader = {
234
+ type: "session",
235
+ version: 3,
236
+ id: sessionId,
237
+ timestamp: new Date().toISOString(),
238
+ cwd: targetCwd,
239
+ parentSession: parentSessionFile,
240
+ };
241
+ const serializedLines = [
242
+ JSON.stringify(sessionHeader),
243
+ ...branchEntries.map((entry) => JSON.stringify(entry)),
244
+ ];
245
+ try {
246
+ fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
247
+ fs.writeFileSync(sessionFile, `${serializedLines.join("\n")}\n`, "utf8");
248
+ } catch (error) {
249
+ const errorMessage = error instanceof Error ? error.message : String(error);
250
+ throw new ReqError(`ERROR: Unable to materialize execution session for ${targetCwd}: ${errorMessage}.`, 1);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * @brief Creates the persisted session file used for worktree-backed prompt execution.
256
+ * @details Forks the resolved origin session into the target cwd when the origin session file is already persisted. Otherwise allocates a new execution-session path, materializes a JSONL header whose `cwd` equals the target worktree path, and copies the current in-memory session branch so pi can switch into the worktree session with the correct runtime cwd. Runtime is dominated by session-file copy or snapshot-write cost. Side effects include session-file creation under the target cwd session directory.
257
+ * @param[in] sourceSessionFile {string} Origin session file path.
258
+ * @param[in] targetCwd {string} Target working directory stored in the execution-session header.
259
+ * @param[in] sessionDir {string | undefined} Active session directory reused for the forked execution session when available.
260
+ * @param[in] sourceSessionBranch {PromptCommandSessionEntry[] | undefined} Current in-memory session branch copied when the origin session file has not been flushed yet.
261
+ * @return {string} Persisted execution-session file path.
262
+ * @throws {ReqError} Throws when the execution-session file cannot be created.
263
+ * @satisfies REQ-271
264
+ */
265
+ function createPromptExecutionSessionFile(
266
+ sourceSessionFile: string,
267
+ targetCwd: string,
268
+ sessionDir: string | undefined,
269
+ sourceSessionBranch?: PromptCommandSessionEntry[],
270
+ ): string {
271
+ const sessionManager = fs.existsSync(sourceSessionFile)
272
+ ? SessionManager.forkFrom(sourceSessionFile, targetCwd, sessionDir)
273
+ : SessionManager.create(targetCwd, sessionDir);
274
+ const executionSessionFile = sessionManager.getSessionFile();
275
+ if (typeof executionSessionFile !== "string" || executionSessionFile === "") {
276
+ throw new ReqError(`ERROR: Unable to prepare execution session for ${targetCwd}.`, 1);
277
+ }
278
+ if (!fs.existsSync(executionSessionFile)) {
279
+ writePromptExecutionSessionSnapshot(
280
+ executionSessionFile,
281
+ sessionManager.getSessionId(),
282
+ targetCwd,
283
+ sourceSessionFile,
284
+ sourceSessionBranch ?? [],
285
+ );
286
+ }
287
+ return path.resolve(executionSessionFile);
288
+ }
289
+
290
+ /**
291
+ * @brief Reads the current active session cwd from a prompt-command context.
292
+ * @details Returns the session-manager cwd only when the supplied context exposes the documented `getCwd()` probe and the probe remains valid after any prior session replacement. Stale or missing probes degrade to `undefined` so verification paths never reuse invalidated pre-switch session objects. Runtime is O(1). No external state is mutated.
293
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Candidate prompt-command context.
294
+ * @return {string | undefined} Active session cwd when available.
295
+ */
296
+ function getPromptSessionCwd(ctx?: PromptCommandSessionContext): string | undefined {
297
+ try {
298
+ return typeof ctx?.sessionManager?.getCwd === "function"
299
+ ? ctx.sessionManager.getCwd()
300
+ : undefined;
301
+ } catch {
302
+ return undefined;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * @brief Reads the current active session file from a prompt-command context.
308
+ * @details Returns the session-manager file path only when the supplied context exposes the documented `getSessionFile()` probe and the probe remains valid after any prior session replacement. Stale or missing probes degrade to `undefined` so verification paths never reuse invalidated pre-switch session objects. Runtime is O(1). No external state is mutated.
309
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Candidate prompt-command context.
310
+ * @return {string | undefined} Active session file when available.
311
+ */
312
+ function getPromptSessionFile(ctx?: PromptCommandSessionContext): string | undefined {
313
+ try {
314
+ return typeof ctx?.sessionManager?.getSessionFile === "function"
315
+ ? ctx.sessionManager.getSessionFile()
316
+ : undefined;
317
+ } catch {
318
+ return undefined;
319
+ }
320
+ }
321
+
322
+ /**
323
+ * @brief Reads the current context cwd from a prompt-command context.
324
+ * @details Returns the context `cwd` only when the supplied getter remains valid after any prior session replacement. Stale getters degrade to `undefined` so verification paths never depend on invalidated pre-switch command objects. Runtime is O(1). No external state is mutated.
325
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Candidate prompt-command context.
326
+ * @return {string | undefined} Context cwd when available.
327
+ */
328
+ function getPromptContextCwd(ctx?: PromptCommandSessionContext): string | undefined {
329
+ try {
330
+ return ctx?.cwd;
331
+ } catch {
332
+ return undefined;
333
+ }
334
+ }
335
+
336
+ /**
337
+ * @brief Resolves the best available command-capable context for prompt session switching.
338
+ * @details Prefers the caller-supplied context when it still exposes `switchSession(...)`, otherwise falls back to the persisted replacement-session context associated with the execution-session file so lifecycle handlers can complete closure when pi emits non-command event contexts. Runtime is O(1). No external state is mutated.
339
+ * @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan whose execution-session file keys the persisted context.
340
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Caller-supplied prompt-command context.
341
+ * @return {{ context: PromptCommandSessionContext | undefined; source: "provided" | "persisted" | "missing" }} Preferred switch context plus its provenance.
342
+ * @satisfies REQ-272, REQ-276
343
+ */
344
+ function resolvePromptCommandSwitchContext(
345
+ plan: PromptCommandExecutionPlan,
346
+ ctx?: PromptCommandSessionContext,
347
+ ): {
348
+ context: PromptCommandSessionContext | undefined;
349
+ source: "provided" | "persisted" | "missing";
350
+ } {
351
+ if (typeof ctx?.switchSession === "function") {
352
+ return { context: ctx, source: "provided" };
353
+ }
354
+ const persistedContext = readPersistedPromptCommandSessionContext(plan.executionSessionFile) as PromptCommandSessionContext | undefined;
355
+ if (persistedContext !== undefined) {
356
+ return { context: persistedContext, source: "persisted" };
357
+ }
358
+ return { context: ctx, source: "missing" };
359
+ }
360
+
361
+ /**
362
+ * @brief Aligns the host process cwd to one expected prompt-orchestration path.
363
+ * @details Applies `process.chdir(...)` only when the host process is still anchored to a different directory than the active prompt session, then re-reads `process.cwd()` and throws a deterministic error when the mutation fails or does not take effect. Runtime is O(p) in path length plus one optional cwd mutation. Side effect: mutates the host process cwd.
364
+ * @param[in] expectedPath {string} Path that `process.cwd()` must match.
365
+ * @param[in] stageLabel {string} Human-readable verification stage label.
366
+ * @return {void} No return value.
367
+ * @throws {ReqError} Throws when `process.chdir(...)` fails or leaves `process.cwd()` misaligned.
368
+ * @satisfies REQ-257, REQ-272
369
+ */
370
+ function syncPromptCommandProcessCwd(expectedPath: string, stageLabel: string): void {
371
+ const normalizedExpectedPath = path.resolve(expectedPath);
372
+ const observedProcessCwd = path.resolve(process.cwd());
373
+ if (observedProcessCwd !== normalizedExpectedPath) {
374
+ try {
375
+ process.chdir(normalizedExpectedPath);
376
+ } catch (error) {
377
+ const errorMessage = error instanceof Error ? error.message : String(error);
378
+ throw new ReqError(
379
+ `ERROR: ${stageLabel} unable to change process.cwd() to ${normalizedExpectedPath}: ${errorMessage}.`,
380
+ 1,
381
+ );
382
+ }
383
+ }
384
+ if (path.resolve(process.cwd()) !== normalizedExpectedPath) {
385
+ throw new ReqError(
386
+ `ERROR: ${stageLabel} expected ${normalizedExpectedPath} but observed process.cwd()=${path.resolve(process.cwd())}.`,
387
+ 1,
388
+ );
389
+ }
390
+ }
391
+
392
+ /**
393
+ * @brief Reads the persisted working directory recorded in one prompt-command session file header.
394
+ * @details Opens the JSONL session file, parses the first non-empty line as JSON, and returns the `cwd` field when present as a string so session-target verification can rely on live on-disk session state instead of stale handler-scoped `ctx` references. Runtime is O(n) in header size. No external state is mutated.
395
+ * @param[in] sessionFile {string} Absolute session-file path.
396
+ * @return {string | undefined} Persisted session cwd when readable; otherwise undefined.
397
+ */
398
+ function readPromptSessionFileCwd(sessionFile: string): string | undefined {
399
+ let raw: string;
400
+ try {
401
+ raw = fs.readFileSync(sessionFile, "utf8");
402
+ } catch {
403
+ return undefined;
404
+ }
405
+ const newlineIndex = raw.indexOf("\n");
406
+ const firstLine = newlineIndex >= 0 ? raw.slice(0, newlineIndex) : raw;
407
+ const trimmed = firstLine.trim();
408
+ if (trimmed === "") {
409
+ return undefined;
410
+ }
411
+ let parsed: unknown;
412
+ try {
413
+ parsed = JSON.parse(trimmed);
414
+ } catch {
415
+ return undefined;
416
+ }
417
+ if (typeof parsed !== "object" || parsed === null) {
418
+ return undefined;
419
+ }
420
+ const headerCwd = (parsed as { cwd?: unknown }).cwd;
421
+ return typeof headerCwd === "string" ? headerCwd : undefined;
422
+ }
423
+
424
+ /**
425
+ * @brief Verifies that the active session file and cwd surfaces match one expected prompt-orchestration target.
426
+ * @details Re-reads the persisted session-file header when present plus the host `process.cwd()` and throws on the first mismatch so prompt commands abort before prompt dispatch or prompt-end handling whenever session switching leaves execution attached to the wrong cwd. A missing persisted session file is treated as a non-fatal lazy-persistence state because pi's `SessionManager` writes session files on first assistant flush rather than eagerly during `ctx.switchSession(sessionPath)`; when the file is absent, pi aligns its internal session cwd to the live `process.cwd()`, so verifying `process.cwd()` alone is authoritative in that state. Reads of `ctx.cwd`, `ctx.sessionManager.getCwd()`, and `ctx.sessionManager.getSessionFile()` are advisory only because the pi `ctx.switchSession(sessionPath)` SDK contract does not mutate the handler-scoped `ctx` object, so those probes stay bound to the pre-switch session and a divergent value alone never triggers abort; they only surface a mismatch when they disagree with both the persisted header cwd and the live `process.cwd()`. Runtime is O(p) in aggregate path length plus one session-file header read. No external state is mutated.
427
+ * @param[in] expectedSessionFile {string} Session file that must remain active.
428
+ * @param[in] expectedPath {string} Path that every live cwd surface must match.
429
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Candidate prompt-command context retained for advisory reads.
430
+ * @param[in] stageLabel {string} Human-readable verification stage label.
431
+ * @return {void} No return value.
432
+ * @throws {ReqError} Throws when the persisted session-file header cwd diverges from the expected target or when `process.cwd()` diverges from the expected target.
433
+ * @satisfies REQ-257, REQ-272
434
+ */
435
+ function verifyPromptCommandSessionTarget(
436
+ expectedSessionFile: string,
437
+ expectedPath: string,
438
+ ctx: PromptCommandSessionContext | undefined,
439
+ stageLabel: string,
440
+ ): void {
441
+ const normalizedExpectedSessionFile = path.resolve(expectedSessionFile);
442
+ const normalizedExpectedPath = path.resolve(expectedPath);
443
+ const observedProcessCwd = process.cwd();
444
+ const mismatchLines: string[] = [];
445
+ const sessionFileExists = fs.existsSync(normalizedExpectedSessionFile);
446
+ if (sessionFileExists) {
447
+ const persistedHeaderCwd = readPromptSessionFileCwd(normalizedExpectedSessionFile);
448
+ if (typeof persistedHeaderCwd !== "string") {
449
+ mismatchLines.push(`active session file=header-unreadable(${normalizedExpectedSessionFile})`);
450
+ } else if (path.resolve(persistedHeaderCwd) !== normalizedExpectedPath) {
451
+ mismatchLines.push(`active-session cwd=${path.resolve(persistedHeaderCwd)}`);
452
+ }
453
+ }
454
+ if (path.resolve(observedProcessCwd) !== normalizedExpectedPath) {
455
+ mismatchLines.push(`process.cwd()=${path.resolve(observedProcessCwd)}`);
456
+ }
457
+ if (mismatchLines.length === 0) {
458
+ return;
459
+ }
460
+ const advisorySessionFile = getPromptSessionFile(ctx);
461
+ const advisorySessionCwd = getPromptSessionCwd(ctx);
462
+ const advisoryContextCwd = getPromptContextCwd(ctx);
463
+ if (typeof advisorySessionFile === "string" && path.resolve(advisorySessionFile) !== normalizedExpectedSessionFile) {
464
+ mismatchLines.push(`ctx.sessionManager.getSessionFile()=${path.resolve(advisorySessionFile)}`);
465
+ }
466
+ if (typeof advisorySessionCwd === "string" && path.resolve(advisorySessionCwd) !== normalizedExpectedPath) {
467
+ mismatchLines.push(`ctx.sessionManager.getCwd()=${path.resolve(advisorySessionCwd)}`);
468
+ }
469
+ if (typeof advisoryContextCwd === "string" && path.resolve(advisoryContextCwd) !== normalizedExpectedPath) {
470
+ mismatchLines.push(`ctx.cwd=${path.resolve(advisoryContextCwd)}`);
471
+ }
472
+ if (!sessionFileExists) {
473
+ mismatchLines.push(`active session file=pending-persistence(${normalizedExpectedSessionFile})`);
474
+ }
475
+ throw new ReqError(
476
+ `ERROR: ${stageLabel} expected session ${normalizedExpectedSessionFile} at ${normalizedExpectedPath} but observed ${mismatchLines.join(", ")}.`,
477
+ 1,
478
+ );
479
+ }
480
+
481
+ /**
482
+ * @brief Verifies persisted prompt execution artifacts before successful closure merge.
483
+ * @details Re-reads the persisted execution-session header, verifies the worktree path still exists, confirms the sibling worktree remains registered, and confirms the linked branch is still present before prompt-end closure attempts to restore `base-path` and merge from the original repository. Unlike prompt-start activation checks, this helper intentionally does not require the live process cwd or current session-bound context to remain on `worktree-path`, because pi CLI may already have started end-of-session session replacement or other post-run housekeeping before the extension finishes closure handling. Runtime is dominated by one session-file read, two git subprocess checks, and bounded filesystem probes. No external state is mutated.
484
+ * @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan.
485
+ * @return {void} No return value.
486
+ * @throws {ReqError} Throws when persisted execution-session metadata or worktree artifacts no longer match the expected worktree target.
487
+ * @satisfies REQ-208, REQ-219, REQ-258, REQ-282
488
+ */
489
+ function verifyPromptCommandClosureArtifacts(
490
+ plan: PromptCommandExecutionPlan,
491
+ ): void {
492
+ if (!plan.worktreePath || !plan.worktreeDir || !plan.worktreeRootPath) {
493
+ return;
494
+ }
495
+ const normalizedExecutionSessionFile = path.resolve(plan.executionSessionFile);
496
+ const normalizedWorktreePath = path.resolve(plan.worktreePath);
497
+ const mismatchLines: string[] = [];
498
+ if (!fs.existsSync(normalizedExecutionSessionFile)) {
499
+ mismatchLines.push(`execution session file=missing(${normalizedExecutionSessionFile})`);
500
+ } else {
501
+ const persistedHeaderCwd = readPromptSessionFileCwd(normalizedExecutionSessionFile);
502
+ if (typeof persistedHeaderCwd !== "string") {
503
+ mismatchLines.push(`execution session file=header-unreadable(${normalizedExecutionSessionFile})`);
504
+ } else if (path.resolve(persistedHeaderCwd) !== normalizedWorktreePath) {
505
+ mismatchLines.push(`execution-session cwd=${path.resolve(persistedHeaderCwd)}`);
506
+ }
507
+ }
508
+ if (!fs.existsSync(normalizedWorktreePath)) {
509
+ mismatchLines.push(`worktree-path=missing(${normalizedWorktreePath})`);
510
+ }
511
+ if (!promptWorktreeRegistered(plan.gitPath, normalizedWorktreePath)) {
512
+ mismatchLines.push(`worktree=unregistered(${normalizedWorktreePath})`);
513
+ }
514
+ if (!promptWorktreeBranchExists(plan.gitPath, plan.branchName)) {
515
+ mismatchLines.push(`branch=missing(${plan.branchName})`);
516
+ }
517
+ if (mismatchLines.length === 0) {
518
+ return;
519
+ }
520
+ throw new ReqError(
521
+ `ERROR: Prompt execution finalization expected session ${normalizedExecutionSessionFile} at ${normalizedWorktreePath} but observed ${mismatchLines.join(", ")}.`,
522
+ 1,
523
+ );
524
+ }
525
+
526
+ /**
527
+ * @brief Switches the active prompt session to one persisted session file when required.
528
+ * @details Calls `ctx.switchSession(sessionPath, { withSession })` so current pi runtimes can expose a fresh replacement-session context for every post-switch session-bound operation. When a runtime ignores the callback, the helper falls back to the caller-supplied context and downstream verification continues to rely on the persisted session-file header plus `process.cwd()`. If pi surfaces only the documented stale-extension-context error while invalidating the old execution-session closure, the helper treats that side effect as non-fatal and lets downstream verification confirm whether the target session actually became active. Runtime is dominated by the session switch. Side effects include active-session replacement and cwd mutation by the host runtime.
529
+ * @param[in] sessionFile {string} Target persisted session file.
530
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Candidate prompt-command context.
531
+ * @return {Promise<PromptCommandSessionContext | undefined>} Replacement-session context when the runtime provides one; otherwise the caller-supplied context.
532
+ * @throws {ReqError} Throws when the context cannot switch sessions, when the host cancels the switch, or when later verification proves the target session never became active.
533
+ * @satisfies REQ-068, REQ-271, REQ-272
534
+ */
535
+ async function switchPromptCommandSession(
536
+ sessionFile: string,
537
+ ctx?: PromptCommandSessionContext,
538
+ ): Promise<PromptCommandSessionContext | undefined> {
539
+ const normalizedSessionFile = path.resolve(sessionFile);
540
+ const currentSessionFile = getPromptSessionFile(ctx);
541
+ if (typeof currentSessionFile === "string" && path.resolve(currentSessionFile) === normalizedSessionFile) {
542
+ return ctx;
543
+ }
544
+ if (typeof ctx?.switchSession !== "function") {
545
+ throw new ReqError(`ERROR: Prompt orchestration requires ctx.switchSession() for ${sessionFile}.`, 1);
546
+ }
547
+ let replacementContext: PromptCommandActiveContext | undefined;
548
+ let switchResult: { cancelled?: boolean } | void;
549
+ try {
550
+ switchResult = await ctx.switchSession(sessionFile, {
551
+ withSession: async (activeContext) => {
552
+ replacementContext = activeContext;
553
+ },
554
+ });
555
+ } catch (error) {
556
+ if (!isPromptCommandStaleContextError(error)) {
557
+ throw error;
558
+ }
559
+ }
560
+ if ((switchResult as { cancelled?: boolean } | undefined)?.cancelled === true) {
561
+ throw new ReqError(`ERROR: Session switch cancelled for ${sessionFile}.`, 1);
562
+ }
563
+ return replacementContext ?? ctx;
564
+ }
565
+
566
+ /**
567
+ * @brief Detects the documented stale-extension-context runtime error during prompt-command session switching.
568
+ * @details Matches the guarded pi runtime error emitted when the old execution-session closure is invalidated during a session replacement or reload. Prompt-command session-switch helpers use this detector to distinguish a late stale-context side effect from genuine switch failures, then rely on post-switch verification to confirm whether the target session actually became active. Runtime is O(n) in message length only when an error is supplied. No external state is mutated.
569
+ * @param[in] error {unknown} Candidate thrown value.
570
+ * @return {boolean} `true` when the value matches the stale-extension-context runtime error.
571
+ * @satisfies REQ-280
572
+ */
573
+ function isPromptCommandStaleContextError(error: unknown): boolean {
574
+ return error instanceof Error
575
+ && /stale after session replacement or reload/i.test(error.message);
576
+ }
577
+
578
+ /**
579
+ * @brief Attaches the last valid prompt-command context to one thrown error.
580
+ * @details Preserves the replacement-session context discovered after `ctx.switchSession(...)` so outer callers can continue UI notifications and cleanup without reusing stale pre-switch command objects. Runtime is O(1). Side effect: mutates the error object when it is an `Error` instance.
581
+ * @param[in] error {unknown} Thrown value.
582
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Last valid prompt-command context.
583
+ * @return {unknown} Original thrown value with optional attached prompt context.
584
+ */
585
+ function attachPromptCommandErrorContext(
586
+ error: unknown,
587
+ ctx: PromptCommandSessionContext | undefined,
588
+ ): unknown {
589
+ if (error instanceof Error && ctx !== undefined) {
590
+ (error as PromptCommandContextError).promptContext = ctx;
591
+ }
592
+ return error;
593
+ }
594
+
595
+ /**
596
+ * @brief Reads an attached prompt-command context from one thrown error.
597
+ * @details Returns the replacement-session context captured by prompt orchestration helpers when a switch-triggered failure occurs after the original command context became stale. Runtime is O(1). No external state is mutated.
598
+ * @param[in] error {unknown} Thrown value.
599
+ * @return {PromptCommandSessionContext | undefined} Attached prompt-command context when available.
600
+ */
601
+ export function getPromptCommandErrorContext(
602
+ error: unknown,
603
+ ): PromptCommandSessionContext | undefined {
604
+ return error instanceof Error
605
+ ? (error as PromptCommandContextError).promptContext
606
+ : undefined;
607
+ }
608
+
609
+ /**
610
+ * @brief Defines the canonical doc-validation matrix for bundled prompt commands.
611
+ * @details Maps each prompt command to the exact canonical doc files that must exist before prompt delivery. Access complexity is O(1).
612
+ * @satisfies REQ-201, REQ-202
613
+ */
614
+ const PROMPT_REQUIRED_DOCS: Record<PromptCommandName, readonly PromptRequiredDocSpec[]> = {
615
+ analyze: [
616
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
617
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
618
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
619
+ ],
620
+ change: [
621
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
622
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
623
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
624
+ ],
625
+ check: [
626
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
627
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
628
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
629
+ ],
630
+ cover: [
631
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
632
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
633
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
634
+ ],
635
+ create: [],
636
+ fix: [
637
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
638
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
639
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
640
+ ],
641
+ flowchart: [
642
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
643
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
644
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
645
+ ],
646
+ implement: [
647
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
648
+ ],
649
+ new: [
650
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
651
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
652
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
653
+ ],
654
+ readme: [
655
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
656
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
657
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
658
+ ],
659
+ recreate: [
660
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
661
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
662
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
663
+ ],
664
+ refactor: [
665
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
666
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
667
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
668
+ ],
669
+ references: [
670
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
671
+ ],
672
+ renumber: [
673
+ { fileName: "REQUIREMENTS.md", promptCommand: "/req-write" },
674
+ { fileName: "WORKFLOW.md", promptCommand: "/req-workflow" },
675
+ { fileName: "REFERENCES.md", promptCommand: "/req-references" },
676
+ ],
677
+ workflow: [],
678
+ write: [],
679
+ };
680
+
681
+ /**
682
+ * @brief Executes one git subprocess synchronously and captures UTF-8 output.
683
+ * @details Delegates to `spawnSync`, preserves the supplied working directory, and returns the raw subprocess result used by prompt-command orchestration. Runtime is dominated by external process execution. Side effects include process spawning.
684
+ * @param[in] command {string[]} Executable plus argument vector.
685
+ * @param[in] cwd {string} Working directory for the subprocess.
686
+ * @return {ReturnType<typeof spawnSync>} Captured subprocess result.
687
+ */
688
+ function runCapture(command: string[], cwd: string): ReturnType<typeof spawnSync> {
689
+ return spawnSync(command[0]!, command.slice(1), {
690
+ cwd,
691
+ encoding: "utf8",
692
+ });
693
+ }
694
+
695
+ /**
696
+ * @brief Stores or clears the prompt-command post-create test hook.
697
+ * @details Enables deterministic simulation of post-create worktree verification failures without altering production control flow. Runtime is O(1). Side effect: mutates module-local test state.
698
+ * @param[in] hook {PromptCommandPostCreateHook | undefined} Optional replacement hook.
699
+ * @return {void} No return value.
700
+ */
701
+ export function setPromptCommandPostCreateHookForTests(
702
+ hook: PromptCommandPostCreateHook | undefined,
703
+ ): void {
704
+ promptCommandPostCreateHook = hook;
705
+ }
706
+
707
+ /**
708
+ * @brief Resolves the configured docs root for one project base.
709
+ * @details Joins the project base with the normalized `docs-dir` value while stripping trailing separators from the persisted config field. Runtime is O(p) in path length. No external state is mutated.
710
+ * @param[in] projectBase {string} Absolute project root.
711
+ * @param[in] config {UseReqConfig} Effective project configuration.
712
+ * @return {string} Absolute canonical docs root path.
713
+ */
714
+ function resolvePromptDocsRoot(projectBase: string, config: UseReqConfig): string {
715
+ const docsDir = config["docs-dir"].replace(/[/\\]+$/, "");
716
+ return path.join(projectBase, docsDir);
717
+ }
718
+
719
+ /**
720
+ * @brief Resolves the effective worktree project base relative to the git root.
721
+ * @details Reuses the original project-base location relative to the git root so nested repository subdirectories remain aligned inside the sibling worktree. Runtime is O(p) in path length. No external state is mutated.
722
+ * @param[in] projectBase {string} Original absolute project base path.
723
+ * @param[in] gitPath {string} Absolute runtime git root path.
724
+ * @param[in] worktreeName {string} Created worktree name.
725
+ * @return {{ worktreePath: string; worktreeBasePath: string }} Derived worktree paths.
726
+ */
727
+ function resolveWorktreePaths(
728
+ projectBase: string,
729
+ gitPath: string,
730
+ worktreeDir: string,
731
+ ): {
732
+ parentPath: string;
733
+ baseDir: string;
734
+ worktreeDir: string;
735
+ worktreeRootPath: string;
736
+ worktreePath: string;
737
+ } {
738
+ const normalizedGitPath = path.resolve(gitPath);
739
+ const normalizedProjectBase = path.resolve(projectBase);
740
+ const parentPath = path.resolve(normalizedGitPath, "..");
741
+ const baseDir = normalizeRelativeDirContract(
742
+ path.relative(normalizedGitPath, normalizedProjectBase),
743
+ );
744
+ const worktreeRootPath = path.join(parentPath, worktreeDir);
745
+ return {
746
+ parentPath,
747
+ baseDir,
748
+ worktreeDir,
749
+ worktreeRootPath,
750
+ worktreePath: baseDir === ""
751
+ ? worktreeRootPath
752
+ : path.join(worktreeRootPath, baseDir),
753
+ };
754
+ }
755
+
756
+ /**
757
+ * @brief Rewrites a branch name into a filesystem-safe token for prompt worktrees.
758
+ * @details Replaces characters invalid for worktree directory and branch-name generation with `-`. Runtime is O(n). No external state is mutated.
759
+ * @param[in] branch {string} Raw branch name.
760
+ * @return {string} Sanitized token.
761
+ */
762
+ function sanitizePromptWorktreeBranchName(branch: string): string {
763
+ return branch.replace(/[<>:"/\\|?*\x00-\x1f\s~^{}\[\]]/g, "-");
764
+ }
765
+
766
+ /**
767
+ * @brief Validates a prompt-command-generated worktree or branch name.
768
+ * @details Rejects empty names, dot-path markers, whitespace, and filesystem-invalid characters. Runtime is O(n). No external state is mutated.
769
+ * @param[in] wtName {string} Candidate worktree name.
770
+ * @return {boolean} `true` when the name is acceptable for worktree creation.
771
+ */
772
+ function validatePromptWorktreeName(wtName: string): boolean {
773
+ if (!wtName || wtName === "." || wtName === "..") {
774
+ return false;
775
+ }
776
+ return !/[<>:"/\\|?*\x00-\x1f\s]/.test(wtName);
777
+ }
778
+
779
+ /**
780
+ * @brief Throws the canonical prompt-command git-preflight failure.
781
+ * @details Normalizes all repository-validation failures to the contractually stable prompt-command error string consumed by tests and downstream prompt workflows. Runtime is O(1). No external state is mutated.
782
+ * @return {never} Always throws.
783
+ * @throws {ReqError} Always throws with exit code `1`.
784
+ */
785
+ function throwPromptGitStatusError(): never {
786
+ throw new ReqError("ERROR: Git status unclear!", 1);
787
+ }
788
+
789
+ /**
790
+ * @brief Runs prompt-command-owned git validation and returns the runtime git root.
791
+ * @details Validates work-tree membership, porcelain cleanliness, and symbolic or detached `HEAD` presence without invoking extension custom-tool executors. Runtime is dominated by git subprocess execution. Side effects include process spawning.
792
+ * @param[in] projectBase {string} Absolute current project base.
793
+ * @param[in] config {UseReqConfig | undefined} Optional effective project configuration used to ignore extension-owned debug-log artifacts.
794
+ * @return {string} Absolute runtime git root.
795
+ * @throws {ReqError} Throws the canonical prompt-command git-preflight error on any validation failure.
796
+ * @satisfies REQ-200, REQ-220
797
+ */
798
+ function validatePromptGitState(projectBase: string, config?: UseReqConfig): string {
799
+ const gitPath = resolveRuntimeGitPath(projectBase);
800
+ if (!gitPath) {
801
+ throwPromptGitStatusError();
802
+ }
803
+ const gitRoot = path.resolve(gitPath);
804
+ const insideWorkTree = runCapture(["git", "rev-parse", "--is-inside-work-tree"], gitRoot);
805
+ if (
806
+ insideWorkTree.error
807
+ || insideWorkTree.status !== 0
808
+ || insideWorkTree.stdout.trim() !== "true"
809
+ ) {
810
+ throwPromptGitStatusError();
811
+ }
812
+ const porcelainStatus = runCapture(["git", "status", "--porcelain"], gitRoot);
813
+ const ignoredStatusPaths = new Set<string>();
814
+ if (config) {
815
+ const configuredLogPath = path.isAbsolute(config.DEBUG_LOG_FILE)
816
+ ? path.normalize(config.DEBUG_LOG_FILE)
817
+ : path.resolve(projectBase, config.DEBUG_LOG_FILE);
818
+ const relativeLogPath = path.relative(gitRoot, configuredLogPath);
819
+ if (relativeLogPath !== "" && !relativeLogPath.startsWith("..") && !path.isAbsolute(relativeLogPath)) {
820
+ ignoredStatusPaths.add(relativeLogPath.split(path.sep).join("/"));
821
+ }
822
+ }
823
+ const residualStatusLines = porcelainStatus.stdout
824
+ .split(/\r?\n/)
825
+ .map((line) => line.trimEnd())
826
+ .filter((line) => line !== "")
827
+ .filter((line) => {
828
+ const statusPath = line.slice(3).split(" -> ").at(-1)?.split(path.sep).join("/") ?? "";
829
+ return !ignoredStatusPaths.has(statusPath);
830
+ });
831
+ if (porcelainStatus.error || porcelainStatus.status !== 0 || residualStatusLines.length > 0) {
832
+ throwPromptGitStatusError();
833
+ }
834
+ const symbolicHead = runCapture(["git", "symbolic-ref", "-q", "HEAD"], gitRoot);
835
+ if (symbolicHead.error || symbolicHead.status !== 0 || symbolicHead.stdout.trim() === "") {
836
+ const detachedHead = runCapture(["git", "rev-parse", "--verify", "HEAD"], gitRoot);
837
+ if (detachedHead.error || detachedHead.status !== 0 || detachedHead.stdout.trim() === "") {
838
+ throwPromptGitStatusError();
839
+ }
840
+ }
841
+ return gitRoot;
842
+ }
843
+
844
+ /**
845
+ * @brief Resolves the current local branch name used by prompt-command orchestration.
846
+ * @details Reads `git branch --show-current`, falls back to `unknown` when git cannot provide a branch name, and preserves the raw branch token for later worktree-name generation and state tracking. Runtime is dominated by one git subprocess. Side effects include process spawning.
847
+ * @param[in] gitRoot {string} Absolute runtime git root.
848
+ * @return {string} Current branch name or `unknown` when unavailable.
849
+ */
850
+ function resolveCurrentPromptBranchName(gitRoot: string): string {
851
+ const branchResult = runCapture(["git", "branch", "--show-current"], gitRoot);
852
+ return branchResult.error || branchResult.status !== 0
853
+ ? "unknown"
854
+ : (branchResult.stdout.trim() || "unknown");
855
+ }
856
+
857
+ /**
858
+ * @brief Defines the process-scoped prompt-worktree execution-id key.
859
+ * @details The property stores the last emitted `YYYYMMDDHHMMSS` execution token so successive prompt commands in the same host process never reuse a sibling worktree name when they start within the same wall-clock second. Access complexity is O(1).
860
+ */
861
+ const PI_USEREQ_PROMPT_WORKTREE_EXECUTION_ID_KEY = "__piUsereqPromptWorktreeExecutionId";
862
+
863
+ /**
864
+ * @brief Formats one prompt-worktree execution identifier.
865
+ * @details Serializes the supplied timestamp as `YYYYMMDDHHMMSS` with zero-padded calendar and clock fields so generated worktree names remain stable, lexicographically sortable, and requirement-compatible. Runtime is O(1). No external state is mutated.
866
+ * @param[in] timestamp {Date} Timestamp to encode.
867
+ * @return {string} Formatted execution identifier.
868
+ */
869
+ function formatPromptWorktreeExecutionId(timestamp: Date): string {
870
+ return `${timestamp.getFullYear()}${String(timestamp.getMonth() + 1).padStart(2, "0")}${String(timestamp.getDate()).padStart(2, "0")}${String(timestamp.getHours()).padStart(2, "0")}${String(timestamp.getMinutes()).padStart(2, "0")}${String(timestamp.getSeconds()).padStart(2, "0")}`;
871
+ }
872
+
873
+ /**
874
+ * @brief Resolves the next unique prompt-worktree execution identifier.
875
+ * @details Formats the current wall-clock second as `YYYYMMDDHHMMSS`, then monotonically advances by one-second steps until the identifier is strictly greater than the last value emitted in the current host process. This preserves the documented timestamp-only name shape while preventing immediate same-process worktree-name reuse after fast back-to-back prompt starts. Runtime is O(1) in the common case and O(k) in repeated same-second collisions. Side effect: mutates process-scoped execution-id persistence.
876
+ * @return {string} Unique execution identifier for worktree naming.
877
+ */
878
+ function getNextPromptWorktreeExecutionId(): string {
879
+ const globalState = globalThis as typeof globalThis & {
880
+ __piUsereqPromptWorktreeExecutionId?: string;
881
+ };
882
+ let candidateTimestamp = new Date();
883
+ let candidateId = formatPromptWorktreeExecutionId(candidateTimestamp);
884
+ const lastExecutionId = globalState[PI_USEREQ_PROMPT_WORKTREE_EXECUTION_ID_KEY];
885
+ while (typeof lastExecutionId === "string" && candidateId <= lastExecutionId) {
886
+ candidateTimestamp = new Date(candidateTimestamp.getTime() + 1000);
887
+ candidateId = formatPromptWorktreeExecutionId(candidateTimestamp);
888
+ }
889
+ globalState[PI_USEREQ_PROMPT_WORKTREE_EXECUTION_ID_KEY] = candidateId;
890
+ return candidateId;
891
+ }
892
+
893
+ /**
894
+ * @brief Builds the prompt-command worktree name without invoking agent-tool executors.
895
+ * @details Combines the normalized persisted worktree prefix, repository basename, sanitized current branch, and timestamp execution identifier into the dedicated prompt-command worktree name. Runtime is O(1) plus git execution cost. Side effects include process spawning.
896
+ * @param[in] gitRoot {string} Absolute runtime git root.
897
+ * @param[in] config {UseReqConfig} Effective project configuration.
898
+ * @return {string} Generated worktree and branch name.
899
+ * @throws {ReqError} Throws when the generated name is invalid.
900
+ * @satisfies REQ-206, REQ-220
901
+ */
902
+ function buildPromptWorktreeName(gitRoot: string, config: UseReqConfig): string {
903
+ const projectName = path.basename(gitRoot);
904
+ const branchName = resolveCurrentPromptBranchName(gitRoot);
905
+ const sanitizedBranchName = sanitizePromptWorktreeBranchName(branchName);
906
+ const worktreePrefix = normalizeGitWorktreePrefix(config.GIT_WORKTREE_PREFIX);
907
+ const executionId = getNextPromptWorktreeExecutionId();
908
+ const worktreeName = `${worktreePrefix}${projectName}-${sanitizedBranchName}-${executionId}`;
909
+ if (!validatePromptWorktreeName(worktreeName)) {
910
+ throw new ReqError(`ERROR: Invalid worktree/branch name: ${worktreeName}.`, 1);
911
+ }
912
+ return worktreeName;
913
+ }
914
+
915
+ /**
916
+ * @brief Tests whether the exact prompt-command branch is present in the local branch list.
917
+ * @details Queries `git branch --list --format=%(refname:short)` and returns a boolean without mutating repository state. Runtime is dominated by one git subprocess plus O(n) parsing in listed branch count. Side effects include process spawning.
918
+ * @param[in] gitRoot {string} Absolute runtime git root.
919
+ * @param[in] branchName {string} Candidate local branch name.
920
+ * @return {boolean} `true` when the exact local branch is listed.
921
+ */
922
+ function promptWorktreeBranchExists(gitRoot: string, branchName: string): boolean {
923
+ const branchResult = runCapture(["git", "branch", "--list", "--format=%(refname:short)", branchName], gitRoot);
924
+ if (branchResult.error || branchResult.status !== 0) {
925
+ return false;
926
+ }
927
+ return branchResult.stdout
928
+ .split(/\r?\n/)
929
+ .map((line) => line.trim())
930
+ .includes(branchName);
931
+ }
932
+
933
+ /**
934
+ * @brief Tests whether the exact prompt-command worktree is registered.
935
+ * @details Scans `git worktree list --porcelain` for the resolved target path so cleanup and verification can distinguish registered worktrees from unrelated sibling directories. Runtime is dominated by one git subprocess plus O(n) parsing in listed worktree count. Side effects include process spawning.
936
+ * @param[in] gitRoot {string} Absolute runtime git root.
937
+ * @param[in] worktreePath {string} Absolute sibling worktree path.
938
+ * @return {boolean} `true` when the exact path is registered as a git worktree.
939
+ */
940
+ function promptWorktreeRegistered(gitRoot: string, worktreePath: string): boolean {
941
+ const listResult = runCapture(["git", "worktree", "list", "--porcelain"], gitRoot);
942
+ if (listResult.error || listResult.status !== 0) {
943
+ return false;
944
+ }
945
+ const targetPath = path.resolve(worktreePath);
946
+ return listResult.stdout
947
+ .split(/\r?\n/)
948
+ .filter((line) => line.startsWith("worktree "))
949
+ .map((line) => path.resolve(line.slice("worktree ".length)))
950
+ .includes(targetPath);
951
+ }
952
+
953
+ /**
954
+ * @brief Removes partially created prompt-command worktree resources.
955
+ * @details Force-removes the registered sibling worktree when present, deletes the matching local branch, and falls back to filesystem removal for leftover directories so failed prompt preflight leaves no reusable worktree residue. Runtime is dominated by git subprocess execution. Side effects include branch deletion and directory removal.
956
+ * @param[in] gitRoot {string} Absolute runtime git root.
957
+ * @param[in] worktreePath {string} Absolute sibling worktree path.
958
+ * @param[in] worktreeName {string} Exact worktree and branch name.
959
+ * @return {void} No return value.
960
+ */
961
+ function cleanupPromptWorktreeCreation(
962
+ gitRoot: string,
963
+ worktreePath: string,
964
+ worktreeName: string,
965
+ ): void {
966
+ if (promptWorktreeRegistered(gitRoot, worktreePath) || fs.existsSync(worktreePath)) {
967
+ runCapture(["git", "worktree", "remove", "--force", worktreePath], gitRoot);
968
+ }
969
+ if (fs.existsSync(worktreePath)) {
970
+ fs.rmSync(worktreePath, { recursive: true, force: true });
971
+ }
972
+ if (promptWorktreeBranchExists(gitRoot, worktreeName)) {
973
+ runCapture(["git", "branch", "-D", worktreeName], gitRoot);
974
+ }
975
+ }
976
+
977
+ /**
978
+ * @brief Creates and verifies the prompt-command worktree and branch.
979
+ * @details Creates the sibling worktree, mirrors project config when present, runs the optional post-create test hook, verifies git worktree registration, verifies git branch listing, verifies filesystem paths before prompt dispatch, and appends selected debug entries for worktree creation. Failed verification triggers immediate rollback. Runtime is dominated by git subprocess execution and filesystem metadata checks. Side effects include worktree creation, branch creation, directory creation, file copying, optional debug-log writes, and rollback on failure.
980
+ * @param[in] projectBase {string} Absolute original project base.
981
+ * @param[in] gitRoot {string} Absolute runtime git root.
982
+ * @param[in] worktreeName {string} Exact worktree and branch name.
983
+ * @param[in] promptName {PromptCommandName} Bundled prompt identifier.
984
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
985
+ * @return {{ worktreePath: string; worktreeBasePath: string }} Verified worktree paths.
986
+ * @throws {ReqError} Throws when worktree creation, verification, or rollback finalization fails.
987
+ * @satisfies REQ-206, REQ-219, REQ-220, REQ-245
988
+ */
989
+ function createPromptWorktree(
990
+ projectBase: string,
991
+ gitRoot: string,
992
+ worktreeDir: string,
993
+ promptName: PromptCommandName,
994
+ debugOptions?: PromptCommandDebugOptions,
995
+ ): {
996
+ parentPath: string;
997
+ baseDir: string;
998
+ worktreeDir: string;
999
+ worktreeRootPath: string;
1000
+ worktreePath: string;
1001
+ } {
1002
+ const logWorktreeCreate = (
1003
+ input: unknown,
1004
+ result: unknown,
1005
+ isError = false,
1006
+ ) => {
1007
+ if (!debugOptions) {
1008
+ return;
1009
+ }
1010
+ logDebugPromptEvent(
1011
+ projectBase,
1012
+ debugOptions.config,
1013
+ debugOptions.workflowState,
1014
+ promptName,
1015
+ "worktree_create",
1016
+ input,
1017
+ result,
1018
+ isError,
1019
+ );
1020
+ };
1021
+ logWorktreeCreate(
1022
+ { worktree_dir: worktreeDir },
1023
+ { success: false, stage: "start" },
1024
+ );
1025
+ if (!validatePromptWorktreeName(worktreeDir)) {
1026
+ logWorktreeCreate(
1027
+ { worktree_dir: worktreeDir },
1028
+ { success: false, error: `ERROR: Invalid worktree/branch name: ${worktreeDir}.` },
1029
+ true,
1030
+ );
1031
+ throw new ReqError(`ERROR: Invalid worktree/branch name: ${worktreeDir}.`, 1);
1032
+ }
1033
+ const resolvedWorktreePaths = resolveWorktreePaths(projectBase, gitRoot, worktreeDir);
1034
+ const addResult = runCapture(
1035
+ ["git", "worktree", "add", resolvedWorktreePaths.worktreeRootPath, "-b", worktreeDir],
1036
+ gitRoot,
1037
+ );
1038
+ if (addResult.error || addResult.status !== 0) {
1039
+ logWorktreeCreate(
1040
+ {
1041
+ worktree_dir: worktreeDir,
1042
+ worktree_root_path: resolvedWorktreePaths.worktreeRootPath,
1043
+ },
1044
+ { success: false, error: addResult.stderr.trim() },
1045
+ true,
1046
+ );
1047
+ throw new ReqError(`Error: git worktree add failed: ${addResult.stderr.trim()}`, 1);
1048
+ }
1049
+ try {
1050
+ promptCommandPostCreateHook?.({
1051
+ gitPath: gitRoot,
1052
+ worktreeDir,
1053
+ worktreeRootPath: resolvedWorktreePaths.worktreeRootPath,
1054
+ worktreePath: resolvedWorktreePaths.worktreePath,
1055
+ });
1056
+ const worktreeRegistered = promptWorktreeRegistered(
1057
+ gitRoot,
1058
+ resolvedWorktreePaths.worktreeRootPath,
1059
+ );
1060
+ const worktreeRootReady = fs.existsSync(resolvedWorktreePaths.worktreeRootPath)
1061
+ && fs.statSync(resolvedWorktreePaths.worktreeRootPath).isDirectory();
1062
+ const worktreePathReady = fs.existsSync(resolvedWorktreePaths.worktreePath)
1063
+ && fs.statSync(resolvedWorktreePaths.worktreePath).isDirectory();
1064
+ const branchReady = promptWorktreeBranchExists(gitRoot, worktreeDir);
1065
+ if (!worktreeRegistered || !worktreeRootReady || !worktreePathReady || !branchReady) {
1066
+ throw new ReqError(`ERROR: Worktree verification failed for ${worktreeDir}.`, 1);
1067
+ }
1068
+ logWorktreeCreate(
1069
+ {
1070
+ worktree_dir: worktreeDir,
1071
+ worktree_root_path: resolvedWorktreePaths.worktreeRootPath,
1072
+ },
1073
+ {
1074
+ success: true,
1075
+ worktree_dir: worktreeDir,
1076
+ branch_name: worktreeDir,
1077
+ parent_path: resolvedWorktreePaths.parentPath,
1078
+ base_dir: resolvedWorktreePaths.baseDir,
1079
+ worktree_root_path: resolvedWorktreePaths.worktreeRootPath,
1080
+ worktree_path: resolvedWorktreePaths.worktreePath,
1081
+ },
1082
+ );
1083
+ return resolvedWorktreePaths;
1084
+ } catch (error) {
1085
+ cleanupPromptWorktreeCreation(
1086
+ gitRoot,
1087
+ resolvedWorktreePaths.worktreeRootPath,
1088
+ worktreeDir,
1089
+ );
1090
+ const errorMessage = error instanceof ReqError
1091
+ ? error.message
1092
+ : `ERROR: Unable to finalize worktree creation for ${worktreeDir}.`;
1093
+ logWorktreeCreate(
1094
+ {
1095
+ worktree_dir: worktreeDir,
1096
+ worktree_root_path: resolvedWorktreePaths.worktreeRootPath,
1097
+ },
1098
+ { success: false, error: errorMessage },
1099
+ true,
1100
+ );
1101
+ if (error instanceof ReqError) {
1102
+ throw error;
1103
+ }
1104
+ throw new ReqError(
1105
+ `ERROR: Unable to finalize worktree creation for ${worktreeDir}.`,
1106
+ 1,
1107
+ );
1108
+ }
1109
+ }
1110
+
1111
+ /**
1112
+ * @brief Deletes prompt-command worktree resources without invoking custom-tool executors.
1113
+ * @details Force-removes the sibling worktree and matching branch, verifies both are absent so prompt finalization remains independent from agent-tool implementations, and appends selected debug entries for worktree deletion. Runtime is dominated by git subprocess execution plus filesystem probes. Side effects include worktree deletion, branch deletion, and optional debug-log writes.
1114
+ * @param[in] projectBase {string} Absolute original project base.
1115
+ * @param[in] worktreeName {string} Exact worktree and branch name.
1116
+ * @param[in] promptName {PromptCommandName | undefined} Optional bundled prompt identifier for debug logging.
1117
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1118
+ * @return {void} No return value.
1119
+ * @throws {ReqError} Throws when cleanup cannot remove the worktree and branch fully.
1120
+ * @satisfies REQ-208, REQ-220, REQ-245
1121
+ */
1122
+ function deletePromptWorktree(
1123
+ basePath: string,
1124
+ worktreeDir: string,
1125
+ worktreeRootPath: string,
1126
+ promptName?: PromptCommandName,
1127
+ debugOptions?: PromptCommandDebugOptions,
1128
+ ): void {
1129
+ const logWorktreeDelete = (
1130
+ input: unknown,
1131
+ result: unknown,
1132
+ isError = false,
1133
+ ) => {
1134
+ if (!debugOptions || promptName === undefined) {
1135
+ return;
1136
+ }
1137
+ logDebugPromptEvent(
1138
+ basePath,
1139
+ debugOptions.config,
1140
+ debugOptions.workflowState,
1141
+ promptName,
1142
+ "worktree_delete",
1143
+ input,
1144
+ result,
1145
+ isError,
1146
+ );
1147
+ };
1148
+ logWorktreeDelete(
1149
+ { worktree_dir: worktreeDir, worktree_root_path: worktreeRootPath },
1150
+ { success: false, stage: "start" },
1151
+ );
1152
+ const gitPath = resolveRuntimeGitPath(basePath);
1153
+ if (!gitPath) {
1154
+ logWorktreeDelete(
1155
+ { worktree_dir: worktreeDir, worktree_root_path: worktreeRootPath },
1156
+ { success: false, error: `ERROR: Unable to remove worktree or branch ${worktreeDir}.` },
1157
+ true,
1158
+ );
1159
+ throw new ReqError(`ERROR: Unable to remove worktree or branch ${worktreeDir}.`, 1);
1160
+ }
1161
+ const gitRoot = path.resolve(gitPath);
1162
+ cleanupPromptWorktreeCreation(gitRoot, worktreeRootPath, worktreeDir);
1163
+ const worktreeStillPresent = promptWorktreeRegistered(gitRoot, worktreeRootPath)
1164
+ || fs.existsSync(worktreeRootPath);
1165
+ const branchStillPresent = promptWorktreeBranchExists(gitRoot, worktreeDir);
1166
+ if (worktreeStillPresent || branchStillPresent) {
1167
+ logWorktreeDelete(
1168
+ { worktree_dir: worktreeDir, worktree_root_path: worktreeRootPath },
1169
+ { success: false, error: `ERROR: Unable to remove worktree or branch ${worktreeDir}.` },
1170
+ true,
1171
+ );
1172
+ throw new ReqError(`ERROR: Unable to remove worktree or branch ${worktreeDir}.`, 1);
1173
+ }
1174
+ logWorktreeDelete(
1175
+ { worktree_dir: worktreeDir, worktree_root_path: worktreeRootPath },
1176
+ {
1177
+ success: true,
1178
+ worktree_dir: worktreeDir,
1179
+ worktree_root_path: worktreeRootPath,
1180
+ },
1181
+ );
1182
+ }
1183
+
1184
+ /**
1185
+ * @brief Returns the canonical required-document probes for one prompt command.
1186
+ * @details Performs a constant-time lookup in the prompt-doc matrix used by command preflight validation. No filesystem access occurs.
1187
+ * @param[in] promptName {PromptCommandName} Bundled prompt identifier.
1188
+ * @return {readonly PromptRequiredDocSpec[]} Required-doc definitions in probe order.
1189
+ * @satisfies REQ-201, REQ-202
1190
+ */
1191
+ export function getPromptRequiredDocs(promptName: PromptCommandName): readonly PromptRequiredDocSpec[] {
1192
+ return PROMPT_REQUIRED_DOCS[promptName];
1193
+ }
1194
+
1195
+ /**
1196
+ * @brief Runs prompt-specific required-document validation.
1197
+ * @details Resolves the configured docs root, verifies the prompt-mapped canonical docs exist as files, throws a deterministic remediation error for the first missing document, and appends selected debug entries for required-doc checks. Runtime is O(d) in required-doc count plus filesystem metadata cost. Side effects are limited to filesystem reads and optional debug-log writes.
1198
+ * @param[in] promptName {PromptCommandName} Bundled prompt identifier.
1199
+ * @param[in] projectBase {string} Absolute project root.
1200
+ * @param[in] config {UseReqConfig} Effective project configuration.
1201
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1202
+ * @return {void} No return value.
1203
+ * @throws {ReqError} Throws when a required canonical doc is missing.
1204
+ * @satisfies REQ-201, REQ-202, REQ-203, REQ-245
1205
+ */
1206
+ export function validatePromptRequiredDocs(
1207
+ promptName: PromptCommandName,
1208
+ projectBase: string,
1209
+ config: UseReqConfig,
1210
+ debugOptions?: PromptCommandDebugOptions,
1211
+ ): void {
1212
+ const docsRoot = resolvePromptDocsRoot(projectBase, config);
1213
+ const requirements = getPromptRequiredDocs(promptName);
1214
+ for (const requirement of requirements) {
1215
+ const canonicalPath = path.join(docsRoot, requirement.fileName);
1216
+ if (!fs.existsSync(canonicalPath) || !fs.statSync(canonicalPath).isFile()) {
1217
+ logDebugPromptEvent(
1218
+ projectBase,
1219
+ debugOptions?.config ?? config,
1220
+ debugOptions?.workflowState ?? "unknown",
1221
+ promptName,
1222
+ "required_docs_check",
1223
+ {
1224
+ required_docs: requirements.map((entry) => ({
1225
+ file_name: entry.fileName,
1226
+ prompt_command: entry.promptCommand,
1227
+ })),
1228
+ },
1229
+ {
1230
+ success: false,
1231
+ missing_file: requirement.fileName,
1232
+ missing_path: canonicalPath,
1233
+ prompt_command: requirement.promptCommand,
1234
+ },
1235
+ true,
1236
+ );
1237
+ throw new ReqError(
1238
+ `ERROR: File ${canonicalPath} does not exist, generate it with the ${requirement.promptCommand} prompt!`,
1239
+ 1,
1240
+ );
1241
+ }
1242
+ }
1243
+ logDebugPromptEvent(
1244
+ projectBase,
1245
+ debugOptions?.config ?? config,
1246
+ debugOptions?.workflowState ?? "unknown",
1247
+ promptName,
1248
+ "required_docs_check",
1249
+ {
1250
+ required_docs: requirements.map((entry) => ({
1251
+ file_name: entry.fileName,
1252
+ prompt_command: entry.promptCommand,
1253
+ })),
1254
+ },
1255
+ { success: true },
1256
+ );
1257
+ }
1258
+
1259
+ /**
1260
+ * @brief Prepares prompt-command execution for one bundled prompt.
1261
+ * @details Runs slash-command-owned git validation, enforces the prompt-specific required-doc matrix, resolves persisted origin and execution session files, applies the effective worktree policy, generates and verifies a dedicated worktree when enabled, and returns the execution plan consumed by prompt rendering plus lifecycle hooks. Worktree-backed execution reuses the active session directory for the forked session file. Runtime is dominated by git subprocesses, worktree creation, and optional session-file cloning. Side effects include worktree creation, session-file creation, filesystem reads, and optional prompt debug-log writes.
1262
+ * @param[in] promptName {PromptCommandName} Bundled prompt identifier.
1263
+ * @param[in] promptArgs {string} Raw prompt argument string.
1264
+ * @param[in] projectBase {string} Absolute current project base.
1265
+ * @param[in] config {UseReqConfig} Effective project configuration.
1266
+ * @param[in] currentSessionFile {string | undefined} Active session file used as the switch origin when available.
1267
+ * @param[in] currentSessionDir {string | undefined} Active session directory reused when the execution session is forked.
1268
+ * @param[in] currentSessionBranch {PromptCommandSessionEntry[] | undefined} Active in-memory session branch copied when the origin session file is not flushed yet.
1269
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1270
+ * @return {PromptCommandExecutionPlan} Prepared execution plan.
1271
+ * @throws {ReqError} Throws when repository validation, required-doc validation, worktree creation, or session preparation fails.
1272
+ * @satisfies REQ-200, REQ-203, REQ-206, REQ-207, REQ-215, REQ-219, REQ-220, REQ-245, REQ-256, REQ-271
1273
+ */
1274
+ export function preparePromptCommandExecution(
1275
+ promptName: PromptCommandName,
1276
+ promptArgs: string,
1277
+ projectBase: string,
1278
+ config: UseReqConfig,
1279
+ currentSessionFile: string | undefined,
1280
+ currentSessionDir: string | undefined,
1281
+ currentSessionBranch: PromptCommandSessionEntry[] | undefined,
1282
+ debugOptions?: PromptCommandDebugOptions,
1283
+ ): PromptCommandExecutionPlan {
1284
+ const gitPath = validatePromptGitState(projectBase, config);
1285
+ const reuseCurrentSessionFile = isUsablePromptSessionFile(currentSessionFile, projectBase);
1286
+ const originalSessionFile = resolvePromptSessionFile(
1287
+ reuseCurrentSessionFile ? currentSessionFile : undefined,
1288
+ projectBase,
1289
+ );
1290
+ setRuntimeGitPath(gitPath);
1291
+ validatePromptRequiredDocs(promptName, projectBase, config, debugOptions);
1292
+ const staticWorktreePaths = resolveWorktreePaths(projectBase, gitPath, "");
1293
+ const gitWorktreeEnabled = resolveEffectiveGitWorktreeEnabled(
1294
+ config.AUTO_GIT_COMMIT,
1295
+ config.GIT_WORKTREE_ENABLED,
1296
+ );
1297
+ if (gitWorktreeEnabled === "disable") {
1298
+ setRuntimeWorktreePathState({});
1299
+ return {
1300
+ promptName,
1301
+ promptArgs,
1302
+ gitPath,
1303
+ branchName: resolveCurrentPromptBranchName(gitPath),
1304
+ basePath: projectBase,
1305
+ contextPath: projectBase,
1306
+ parentPath: staticWorktreePaths.parentPath,
1307
+ baseDir: staticWorktreePaths.baseDir,
1308
+ originalSessionFile,
1309
+ executionSessionFile: originalSessionFile,
1310
+ };
1311
+ }
1312
+ const worktreeDir = buildPromptWorktreeName(gitPath, config);
1313
+ const worktreePaths = createPromptWorktree(
1314
+ projectBase,
1315
+ gitPath,
1316
+ worktreeDir,
1317
+ promptName,
1318
+ debugOptions,
1319
+ );
1320
+ const effectiveSessionDir = reuseCurrentSessionFile
1321
+ && typeof currentSessionDir === "string"
1322
+ && currentSessionDir !== ""
1323
+ ? currentSessionDir
1324
+ : path.dirname(originalSessionFile);
1325
+ const executionSessionFile = createPromptExecutionSessionFile(
1326
+ originalSessionFile,
1327
+ worktreePaths.worktreePath,
1328
+ effectiveSessionDir,
1329
+ reuseCurrentSessionFile ? currentSessionBranch : undefined,
1330
+ );
1331
+ setRuntimeWorktreePathState({
1332
+ worktreeDir: worktreePaths.worktreeDir,
1333
+ worktreePath: worktreePaths.worktreePath,
1334
+ });
1335
+ return {
1336
+ promptName,
1337
+ promptArgs,
1338
+ gitPath,
1339
+ branchName: worktreeDir,
1340
+ basePath: projectBase,
1341
+ contextPath: worktreePaths.worktreePath,
1342
+ parentPath: worktreePaths.parentPath,
1343
+ baseDir: worktreePaths.baseDir,
1344
+ originalSessionFile,
1345
+ executionSessionFile,
1346
+ worktreeDir: worktreePaths.worktreeDir,
1347
+ worktreePath: worktreePaths.worktreePath,
1348
+ worktreeRootPath: worktreePaths.worktreeRootPath,
1349
+ };
1350
+ }
1351
+
1352
+ /**
1353
+ * @brief Activates the prepared prompt execution path before prompt dispatch or agent start.
1354
+ * @details Switches the active session to the execution-session file when worktree routing changed the cwd, re-aligns `process.cwd()` to the execution path, verifies active-session cwd plus cwd mirrors after the switch completes, and stores the verified command-capable replacement-session context for later closure handling. Runtime is dominated by the optional session switch and one optional cwd mutation. Side effects include active-session replacement, host-process cwd mutation, runtime-path state mutation, and process-scoped command-context persistence.
1355
+ * @param[in] plan {PromptCommandExecutionPlan} Prepared prompt execution plan.
1356
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Optional prompt-command context.
1357
+ * @return {Promise<PromptCommandSessionContext | undefined>} Active prompt-command context after any required session switch.
1358
+ * @throws {ReqError} Throws when the session switch or cwd verification fails.
1359
+ * @satisfies REQ-206, REQ-207, REQ-257, REQ-272, REQ-276
1360
+ */
1361
+ export async function activatePromptCommandExecution(
1362
+ plan: PromptCommandExecutionPlan,
1363
+ ctx?: PromptCommandSessionContext,
1364
+ ): Promise<PromptCommandSessionContext | undefined> {
1365
+ const nextContextPath = plan.worktreePath ?? plan.basePath;
1366
+ let activeContext = ctx;
1367
+ if (plan.worktreePath) {
1368
+ const switchContext = resolvePromptCommandSwitchContext(plan, ctx);
1369
+ activeContext = switchContext.context ?? activeContext;
1370
+ activeContext = await switchPromptCommandSession(plan.executionSessionFile, switchContext.context);
1371
+ try {
1372
+ syncPromptCommandProcessCwd(nextContextPath, "Prompt execution activation");
1373
+ verifyPromptCommandSessionTarget(
1374
+ plan.executionSessionFile,
1375
+ nextContextPath,
1376
+ activeContext,
1377
+ "Prompt execution activation",
1378
+ );
1379
+ } catch (error) {
1380
+ throw attachPromptCommandErrorContext(error, activeContext);
1381
+ }
1382
+ if (activeContext !== undefined) {
1383
+ writePersistedPromptCommandSessionContext(plan.executionSessionFile, activeContext);
1384
+ }
1385
+ } else {
1386
+ clearPersistedPromptCommandSessionContext();
1387
+ }
1388
+ setRuntimeContextPath(nextContextPath);
1389
+ setRuntimeWorktreePathState({
1390
+ worktreeDir: plan.worktreeDir,
1391
+ worktreePath: plan.worktreePath,
1392
+ });
1393
+ return activeContext;
1394
+ }
1395
+
1396
+ /**
1397
+ * @brief Restores the original project base path before merge or session-closure return.
1398
+ * @details Switches the active session back to the original session file when worktree routing changed the cwd, re-aligns `process.cwd()` to `base-path`, verifies the restored session target, reuses the persisted replacement-session context when lifecycle handlers receive non-command contexts, tolerates the documented stale-extension-context error when the old replacement-session closure becomes invalid immediately after a successful restore, emits optional workflow restoration debug entries, and clears active worktree path facts before session closure continues. Runtime is dominated by the optional session switch and one optional cwd mutation. Side effects include active-session replacement, host-process cwd mutation, runtime-path state mutation, and optional workflow-debug writes.
1399
+ * @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan whose original base should be restored.
1400
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Optional prompt-command context.
1401
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1402
+ * @return {Promise<PromptCommandSessionContext | undefined>} Active prompt-command context after any required restoration switch.
1403
+ * @throws {ReqError} Throws when the session switch or cwd verification fails.
1404
+ * @satisfies REQ-208, REQ-209, REQ-245, REQ-257, REQ-272, REQ-276
1405
+ */
1406
+ export async function restorePromptCommandExecution(
1407
+ plan: PromptCommandExecutionPlan,
1408
+ ctx?: PromptCommandSessionContext,
1409
+ debugOptions?: PromptCommandDebugOptions,
1410
+ ): Promise<PromptCommandSessionContext | undefined> {
1411
+ let activeContext = ctx;
1412
+ if (plan.worktreePath) {
1413
+ const switchContext = resolvePromptCommandSwitchContext(plan, ctx);
1414
+ activeContext = switchContext.context ?? activeContext;
1415
+ const workflowInput = {
1416
+ execution_session_file: plan.executionSessionFile,
1417
+ original_session_file: plan.originalSessionFile,
1418
+ base_path: plan.basePath,
1419
+ context_source: switchContext.source,
1420
+ };
1421
+ try {
1422
+ activeContext = await switchPromptCommandSession(plan.originalSessionFile, switchContext.context);
1423
+ syncPromptCommandProcessCwd(plan.basePath, "Prompt execution restoration");
1424
+ verifyPromptCommandSessionTarget(
1425
+ plan.originalSessionFile,
1426
+ plan.basePath,
1427
+ activeContext,
1428
+ "Prompt execution restoration",
1429
+ );
1430
+ if (debugOptions) {
1431
+ logDebugPromptWorkflowEvent(
1432
+ plan.basePath,
1433
+ debugOptions.config,
1434
+ debugOptions.workflowState,
1435
+ plan.promptName,
1436
+ "workflow_restore",
1437
+ workflowInput,
1438
+ { success: true, base_path: plan.basePath, context_source: switchContext.source },
1439
+ );
1440
+ }
1441
+ } catch (error) {
1442
+ if (debugOptions) {
1443
+ logDebugPromptWorkflowEvent(
1444
+ plan.basePath,
1445
+ debugOptions.config,
1446
+ debugOptions.workflowState,
1447
+ plan.promptName,
1448
+ "workflow_restore",
1449
+ workflowInput,
1450
+ { success: false, error: error instanceof Error ? error.message : String(error), context_source: switchContext.source },
1451
+ true,
1452
+ );
1453
+ }
1454
+ throw attachPromptCommandErrorContext(error, activeContext);
1455
+ }
1456
+ }
1457
+ if (activeContext !== undefined) {
1458
+ try {
1459
+ Reflect.set(activeContext, "cwd", plan.basePath);
1460
+ } catch (error) {
1461
+ if (!isPromptCommandStaleContextError(error)) {
1462
+ throw attachPromptCommandErrorContext(error, activeContext);
1463
+ }
1464
+ }
1465
+ }
1466
+ setRuntimeContextPath(plan.basePath);
1467
+ setRuntimeWorktreePathState({});
1468
+ clearPersistedPromptCommandSessionContext();
1469
+ return activeContext;
1470
+ }
1471
+
1472
+ /**
1473
+ * @brief Aborts one prepared prompt-command execution before pi CLI takes ownership.
1474
+ * @details Restores the original session-backed cwd and deletes any created worktree plus branch when command-side preflight, prompt rendering, or prompt handoff fails before agent completion. Restoration failures are returned as structured cleanup errors so the original preflight failure is not masked. Runtime is dominated by the optional session switch plus git subprocess execution. Side effects include active-session replacement, optional worktree deletion, and optional debug-log writes.
1475
+ * @param[in] plan {PromptCommandExecutionPlan} Prepared prompt execution plan.
1476
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Optional prompt-command context.
1477
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1478
+ * @return {Promise<{ cleanupSucceeded: boolean; errorMessage?: string; activeContext?: PromptCommandSessionContext }>} Abort-cleanup facts plus the last valid active prompt-command context.
1479
+ * @satisfies REQ-226, REQ-220, REQ-245
1480
+ */
1481
+ export async function abortPromptCommandExecution(
1482
+ plan: PromptCommandExecutionPlan,
1483
+ ctx?: PromptCommandSessionContext,
1484
+ debugOptions?: PromptCommandDebugOptions,
1485
+ ): Promise<{
1486
+ cleanupSucceeded: boolean;
1487
+ errorMessage?: string;
1488
+ activeContext?: PromptCommandSessionContext;
1489
+ }> {
1490
+ let activeContext = ctx;
1491
+ try {
1492
+ activeContext = await restorePromptCommandExecution(plan, ctx, debugOptions);
1493
+ } catch (error) {
1494
+ return {
1495
+ cleanupSucceeded: false,
1496
+ errorMessage: error instanceof Error ? error.message : String(error),
1497
+ activeContext: (getPromptCommandErrorContext(error) ?? activeContext) as PromptCommandSessionContext | undefined,
1498
+ };
1499
+ }
1500
+ if (!plan.worktreeDir || !plan.worktreeRootPath) {
1501
+ setRuntimeWorktreePathState({});
1502
+ return {
1503
+ cleanupSucceeded: true,
1504
+ activeContext,
1505
+ };
1506
+ }
1507
+ try {
1508
+ deletePromptWorktree(
1509
+ plan.basePath,
1510
+ plan.worktreeDir,
1511
+ plan.worktreeRootPath,
1512
+ plan.promptName,
1513
+ debugOptions,
1514
+ );
1515
+ setRuntimeWorktreePathState({});
1516
+ return {
1517
+ cleanupSucceeded: true,
1518
+ activeContext,
1519
+ };
1520
+ } catch {
1521
+ return {
1522
+ cleanupSucceeded: false,
1523
+ errorMessage: `ERROR: Unable to remove worktree or branch ${plan.worktreeDir}.`,
1524
+ activeContext,
1525
+ };
1526
+ }
1527
+ }
1528
+
1529
+ /**
1530
+ * @brief Finalizes one matched successful worktree-backed prompt execution.
1531
+ * @details Re-verifies persisted execution-session metadata plus worktree artifacts, restores the original session-backed `base-path`, fast-forward merges the successful worktree branch from `base-path`, deletes the worktree after merge success, and preserves the restored base session across closure failures. Closure intentionally treats `base-path` restoration as authoritative even when pi CLI has already started end-of-session session replacement or other housekeeping that moved the live runtime away from `worktree-path`. Runtime is dominated by session switching plus git subprocess execution. Side effects include active-session replacement, branch merges, worktree deletion, and optional debug-log writes.
1532
+ * @param[in] plan {PromptCommandExecutionPlan} Prompt execution plan.
1533
+ * @param[in] ctx {PromptCommandSessionContext | undefined} Optional prompt-command context.
1534
+ * @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
1535
+ * @return {Promise<{ mergeAttempted: boolean; mergeSucceeded: boolean; cleanupSucceeded: boolean; errorMessage?: string; activeContext?: PromptCommandSessionContext }>} Finalization facts plus the last valid active prompt-command context.
1536
+ * @satisfies REQ-208, REQ-209, REQ-220, REQ-245, REQ-282
1537
+ */
1538
+ export async function finalizePromptCommandExecution(
1539
+ plan: PromptCommandExecutionPlan,
1540
+ ctx?: PromptCommandSessionContext,
1541
+ debugOptions?: PromptCommandDebugOptions,
1542
+ ): Promise<{
1543
+ mergeAttempted: boolean;
1544
+ mergeSucceeded: boolean;
1545
+ cleanupSucceeded: boolean;
1546
+ errorMessage?: string;
1547
+ activeContext?: PromptCommandSessionContext;
1548
+ }> {
1549
+ let activeContext = ctx;
1550
+ let verificationErrorMessage: string | undefined;
1551
+ try {
1552
+ verifyPromptCommandClosureArtifacts(plan);
1553
+ } catch (error) {
1554
+ verificationErrorMessage = error instanceof Error ? error.message : String(error);
1555
+ }
1556
+ if (!plan.worktreeDir || !plan.worktreeRootPath) {
1557
+ setRuntimeWorktreePathState({});
1558
+ return {
1559
+ mergeAttempted: false,
1560
+ mergeSucceeded: false,
1561
+ cleanupSucceeded: true,
1562
+ activeContext,
1563
+ };
1564
+ }
1565
+ try {
1566
+ activeContext = await restorePromptCommandExecution(plan, activeContext, debugOptions);
1567
+ } catch (error) {
1568
+ activeContext = (getPromptCommandErrorContext(error) ?? activeContext) as PromptCommandSessionContext | undefined;
1569
+ return {
1570
+ mergeAttempted: false,
1571
+ mergeSucceeded: false,
1572
+ cleanupSucceeded: false,
1573
+ errorMessage: error instanceof Error ? error.message : String(error),
1574
+ activeContext,
1575
+ };
1576
+ }
1577
+ if (verificationErrorMessage !== undefined) {
1578
+ return {
1579
+ mergeAttempted: false,
1580
+ mergeSucceeded: false,
1581
+ cleanupSucceeded: true,
1582
+ errorMessage: verificationErrorMessage,
1583
+ activeContext,
1584
+ };
1585
+ }
1586
+ const mergeResult = runCapture(
1587
+ ["git", "merge", "--ff-only", plan.branchName],
1588
+ plan.basePath,
1589
+ );
1590
+ const mergeSucceeded = !mergeResult.error && mergeResult.status === 0;
1591
+ const errorMessage = mergeSucceeded
1592
+ ? undefined
1593
+ : `ERROR: Fast-forward merge failed for worktree ${plan.worktreeDir}.`;
1594
+ if (debugOptions) {
1595
+ logDebugPromptEvent(
1596
+ plan.basePath,
1597
+ debugOptions.config,
1598
+ debugOptions.workflowState,
1599
+ plan.promptName,
1600
+ "merge",
1601
+ { worktree_dir: plan.worktreeDir, branch_name: plan.branchName },
1602
+ { success: mergeSucceeded, error: errorMessage },
1603
+ !mergeSucceeded,
1604
+ );
1605
+ }
1606
+ if (!mergeSucceeded) {
1607
+ return {
1608
+ mergeAttempted: true,
1609
+ mergeSucceeded: false,
1610
+ cleanupSucceeded: true,
1611
+ errorMessage,
1612
+ activeContext,
1613
+ };
1614
+ }
1615
+ try {
1616
+ deletePromptWorktree(
1617
+ plan.basePath,
1618
+ plan.worktreeDir,
1619
+ plan.worktreeRootPath,
1620
+ plan.promptName,
1621
+ debugOptions,
1622
+ );
1623
+ } catch {
1624
+ return {
1625
+ mergeAttempted: true,
1626
+ mergeSucceeded: true,
1627
+ cleanupSucceeded: false,
1628
+ errorMessage: `ERROR: Unable to remove worktree or branch ${plan.worktreeDir}.`,
1629
+ activeContext,
1630
+ };
1631
+ }
1632
+ return {
1633
+ mergeAttempted: true,
1634
+ mergeSucceeded: true,
1635
+ cleanupSucceeded: true,
1636
+ activeContext,
1637
+ };
1638
+ }
1639
+
1640
+ /**
1641
+ * @brief Maps one `agent_end` payload into the canonical prompt-worktree finalization outcome.
1642
+ * @details Delegates to the shared notification outcome classifier so worktree merge and fork-session retention decisions stay aligned with prompt-end notification routing. Runtime is O(m) in assistant message count. No external state is mutated.
1643
+ * @param[in] event {Pick<import("@mariozechner/pi-coding-agent").AgentEndEvent, "messages">} Agent-end payload subset.
1644
+ * @return {PiNotifyOutcome} Canonical prompt-end outcome.
1645
+ */
1646
+ export function classifyPromptCommandOutcome(
1647
+ event: Pick<import("@mariozechner/pi-coding-agent").AgentEndEvent, "messages">,
1648
+ ): PiNotifyOutcome {
1649
+ return classifyPiNotifyOutcome(event);
1650
+ }