@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 1.1.1-dev.b2556262

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 (325) hide show
  1. package/dist/headless-recover.js +56 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
  4. package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
  5. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  6. package/dist/resources/extensions/browser-tools/index.js +68 -24
  7. package/dist/resources/extensions/browser-tools/state.js +12 -0
  8. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  9. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  10. package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  12. package/dist/resources/extensions/gsd/auto/phases.js +87 -12
  13. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  14. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
  17. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  18. package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
  19. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  20. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  21. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  22. package/dist/resources/extensions/gsd/auto-timers.js +24 -10
  23. package/dist/resources/extensions/gsd/auto.js +40 -15
  24. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  27. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  28. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
  29. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  30. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  31. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  33. package/dist/resources/extensions/gsd/config-overlay.js +1 -0
  34. package/dist/resources/extensions/gsd/context-masker.js +129 -5
  35. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  37. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  38. package/dist/resources/extensions/gsd/guided-flow.js +4 -1
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  40. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  41. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  42. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  43. package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
  44. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  45. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  46. package/dist/resources/extensions/gsd/preferences-models.js +111 -43
  47. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  48. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  49. package/dist/resources/extensions/gsd/preferences.js +4 -1
  50. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  51. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  52. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
  55. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  56. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  57. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  58. package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
  59. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  60. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  61. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  62. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  63. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  64. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  65. package/dist/resources/extensions/gsd/state.js +7 -3
  66. package/dist/resources/extensions/gsd/tool-contract.js +15 -1
  67. package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
  68. package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
  69. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  70. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  71. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +62 -406
  72. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  73. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  74. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  75. package/dist/resources/extensions/gsd/unit-tool-contracts.js +38 -14
  76. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  77. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -3
  79. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  80. package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
  81. package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
  82. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  83. package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
  84. package/dist/resources/extensions/subagent/agents.js +1 -0
  85. package/dist/resources/extensions/subagent/index.js +27 -12
  86. package/dist/resources/extensions/subagent/launch.js +7 -2
  87. package/dist/web/standalone/.next/BUILD_ID +1 -1
  88. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  89. package/dist/web/standalone/.next/build-manifest.json +2 -2
  90. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  115. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  116. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  118. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  119. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  120. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  121. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  122. package/package.json +4 -4
  123. package/packages/cloud-mcp-gateway/package.json +2 -2
  124. package/packages/contracts/package.json +1 -1
  125. package/packages/daemon/package.json +4 -4
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  140. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  143. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  144. package/packages/gsd-agent-modes/package.json +7 -7
  145. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  146. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  147. package/packages/mcp-server/package.json +3 -3
  148. package/packages/native/dist/native.js +22 -0
  149. package/packages/native/package.json +1 -1
  150. package/packages/pi-agent-core/package.json +1 -1
  151. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  152. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  153. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  154. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  155. package/packages/pi-ai/dist/models.generated.d.ts +174 -29
  156. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models.generated.js +178 -54
  158. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  159. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  160. package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
  161. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  162. package/packages/pi-ai/package.json +1 -1
  163. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  164. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  165. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  166. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  167. package/packages/pi-coding-agent/package.json +7 -7
  168. package/packages/pi-tui/dist/utils.d.ts +11 -0
  169. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  170. package/packages/pi-tui/dist/utils.js +119 -6
  171. package/packages/pi-tui/dist/utils.js.map +1 -1
  172. package/packages/pi-tui/package.json +2 -1
  173. package/packages/rpc-client/package.json +2 -2
  174. package/pkg/dist/theme/themes.js +1 -1
  175. package/pkg/dist/theme/themes.js.map +1 -1
  176. package/pkg/package.json +1 -1
  177. package/scripts/install/handoff.js +16 -3
  178. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
  179. package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
  180. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  181. package/src/resources/extensions/browser-tools/index.ts +75 -27
  182. package/src/resources/extensions/browser-tools/state.ts +13 -0
  183. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
  184. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  185. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
  186. package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
  187. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  188. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  189. package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
  190. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  191. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  192. package/src/resources/extensions/gsd/auto/phases.ts +89 -15
  193. package/src/resources/extensions/gsd/auto/session.ts +24 -1
  194. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  195. package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
  196. package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
  197. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  198. package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
  199. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  200. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  201. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  202. package/src/resources/extensions/gsd/auto-timers.ts +25 -9
  203. package/src/resources/extensions/gsd/auto.ts +41 -14
  204. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  205. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
  206. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  207. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  208. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
  209. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  210. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  211. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
  212. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  213. package/src/resources/extensions/gsd/config-overlay.ts +1 -0
  214. package/src/resources/extensions/gsd/context-masker.ts +152 -5
  215. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  216. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  217. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  218. package/src/resources/extensions/gsd/guided-flow.ts +4 -1
  219. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  220. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  221. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  222. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  223. package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
  224. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  225. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  226. package/src/resources/extensions/gsd/preferences-models.ts +113 -43
  227. package/src/resources/extensions/gsd/preferences-types.ts +47 -0
  228. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  229. package/src/resources/extensions/gsd/preferences.ts +5 -0
  230. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  231. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  232. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  233. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  234. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
  235. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  236. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  237. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  238. package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
  239. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  240. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  241. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  242. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  243. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  244. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  245. package/src/resources/extensions/gsd/state.ts +7 -4
  246. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +114 -0
  247. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
  248. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  249. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  250. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  251. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  252. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
  253. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  254. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
  255. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  256. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  257. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
  258. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  259. package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
  260. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
  261. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
  262. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  263. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  264. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  265. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  266. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +4 -1
  267. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
  268. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
  269. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  270. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
  271. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  273. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  274. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  275. package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
  276. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  277. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  278. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +133 -0
  279. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
  280. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  281. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  282. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +28 -0
  283. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  284. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  285. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
  286. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  287. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  288. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  289. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  290. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  291. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  292. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  293. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  294. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  295. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
  296. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
  297. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  298. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
  299. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  300. package/src/resources/extensions/gsd/tool-contract.ts +29 -1
  301. package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
  302. package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
  303. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  304. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  305. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +71 -489
  306. package/src/resources/extensions/gsd/types.ts +1 -0
  307. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  308. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  309. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  310. package/src/resources/extensions/gsd/unit-tool-contracts.ts +38 -14
  311. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  312. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  313. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -3
  314. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  315. package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
  316. package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
  317. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  318. package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
  319. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  320. package/src/resources/extensions/subagent/agents.ts +4 -0
  321. package/src/resources/extensions/subagent/index.ts +28 -3
  322. package/src/resources/extensions/subagent/launch.ts +8 -0
  323. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  324. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  325. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -5,6 +5,7 @@ import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
5
5
 
6
6
  import type { NextAction } from "../shared/next-action-ui.js";
7
7
  import type { GSDState } from "./types.js";
8
+ import { setAutoOutcomeWidget } from "./auto-dashboard.js";
8
9
  import { invalidateAllCaches } from "./cache.js";
9
10
  import { mergeCompletedMilestone } from "./parallel-merge.js";
10
11
  import { cleanupQuickBranch, detectStrandedQuickBranch, type StrandedQuickBranch } from "./quick.js";
@@ -21,6 +22,13 @@ export interface CloseoutContext {
21
22
  unmergedMilestones: UnmergedMilestoneBlocker[];
22
23
  }
23
24
 
25
+ const MILESTONE_MERGE_CLOSEOUT_COMMANDS = [
26
+ "/gsd status for overview",
27
+ "/gsd visualize to inspect",
28
+ "/gsd notifications for history",
29
+ "/gsd start for new work",
30
+ ];
31
+
24
32
  export async function loadCloseoutContext(basePath: string): Promise<CloseoutContext> {
25
33
  const unmergedMilestones = await findUnmergedCompletedMilestones(basePath);
26
34
  return {
@@ -91,6 +99,23 @@ export function buildIdleMenuSummary(state: GSDState, closeout: CloseoutContext)
91
99
  return [state.nextAction || "No active milestone."];
92
100
  }
93
101
 
102
+ export function showMilestoneMergeCloseout(
103
+ ctx: ExtensionCommandContext,
104
+ blocker: UnmergedMilestoneBlocker,
105
+ ): void {
106
+ ctx.ui.setStatus?.("gsd-auto", undefined);
107
+ ctx.ui.setStatus?.("gsd-step", undefined);
108
+ ctx.ui.setWidget?.("gsd-progress", undefined);
109
+
110
+ setAutoOutcomeWidget(ctx, {
111
+ status: "complete",
112
+ title: `Milestone ${blocker.milestoneId} merged`,
113
+ detail: `Merged ${blocker.branch} into ${blocker.integrationBranch}. Product changes are now on ${blocker.integrationBranch}.`,
114
+ nextAction: "Review the closeout, then start the next milestone when ready.",
115
+ commands: MILESTONE_MERGE_CLOSEOUT_COMMANDS,
116
+ });
117
+ }
118
+
94
119
  export async function runMergeQuickTask(
95
120
  ctx: ExtensionCommandContext,
96
121
  basePath: string,
@@ -113,31 +138,23 @@ export async function runMergeQuickTask(
113
138
  return false;
114
139
  }
115
140
 
116
- export async function runMergeMilestone(
141
+ export async function runMergeMilestoneBlocker(
117
142
  ctx: ExtensionCommandContext,
118
143
  basePath: string,
119
- milestoneId?: string,
144
+ blocker: UnmergedMilestoneBlocker,
120
145
  ): Promise<boolean> {
121
- const blockers = await findUnmergedCompletedMilestones(basePath);
122
- const blocker = milestoneId
123
- ? blockers.find((candidate) => candidate.milestoneId === milestoneId)
124
- : blockers[0];
125
- if (!blocker) {
126
- ctx.ui.notify("No unmerged completed milestone found.", "warning");
127
- return false;
128
- }
129
-
130
146
  ctx.ui.notify(
131
147
  `Completing preserved milestone merge for ${blocker.milestoneId} from ${blocker.branch} into ${blocker.integrationBranch}.`,
132
148
  "info",
133
149
  );
134
150
  const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
135
151
  if (result.success) {
152
+ invalidateAllCaches();
153
+ showMilestoneMergeCloseout(ctx, blocker);
136
154
  ctx.ui.notify(
137
- `Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Run /gsd again when ready.`,
155
+ `Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Closeout is complete.`,
138
156
  "info",
139
157
  );
140
- invalidateAllCaches();
141
158
  return true;
142
159
  }
143
160
 
@@ -148,6 +165,23 @@ export async function runMergeMilestone(
148
165
  return false;
149
166
  }
150
167
 
168
+ export async function runMergeMilestone(
169
+ ctx: ExtensionCommandContext,
170
+ basePath: string,
171
+ milestoneId?: string,
172
+ ): Promise<boolean> {
173
+ const blockers = await findUnmergedCompletedMilestones(basePath);
174
+ const blocker = milestoneId
175
+ ? blockers.find((candidate) => candidate.milestoneId === milestoneId)
176
+ : blockers[0];
177
+ if (!blocker) {
178
+ ctx.ui.notify("No unmerged completed milestone found.", "warning");
179
+ return false;
180
+ }
181
+
182
+ return runMergeMilestoneBlocker(ctx, basePath, blocker);
183
+ }
184
+
151
185
  export async function handleCloseoutChoice(
152
186
  ctx: ExtensionCommandContext,
153
187
  basePath: string,
@@ -209,6 +209,15 @@ export async function handleAutoCommand(trimmed: string, ctx: ExtensionCommandCo
209
209
  if (trimmed === "") {
210
210
  if (!(await guardRemoteSession(ctx, pi))) return true;
211
211
  const basePath = projectRoot();
212
+ // Cold start after /quit lands at the project root, not the worktree. If the
213
+ // active milestone has a live worktree, chdir back into it now so the agent
214
+ // doesn't have to search for it. Best-effort; resolves to a no-op otherwise.
215
+ try {
216
+ const { reenterActiveWorktreeIfNeeded } = await import("../../worktree-reentry.js");
217
+ await reenterActiveWorktreeIfNeeded(basePath, {
218
+ notify: (message) => ctx.ui.notify(message, "info"),
219
+ });
220
+ } catch { /* non-fatal */ }
212
221
  const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
213
222
  const { gsdRoot } = await import("../../paths.js");
214
223
  if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
@@ -19,7 +19,7 @@ import { handleSessionReport } from "../../commands-session-report.js";
19
19
  import { handlePrBranch } from "../../commands-pr-branch.js";
20
20
  import { currentDirectoryRoot, projectRoot } from "../context.js";
21
21
  import { findUnmergedCompletedMilestones } from "../../unmerged-milestone-guard.js";
22
- import { mergeCompletedMilestone } from "../../parallel-merge.js";
22
+ import { runMergeMilestoneBlocker } from "../../closeout-wizard.js";
23
23
 
24
24
  async function handleCompletedMilestoneRecovery(
25
25
  phase: string,
@@ -37,22 +37,7 @@ async function handleCompletedMilestoneRecovery(
37
37
  : blockers[0];
38
38
  if (!blocker) return false;
39
39
 
40
- ctx.ui.notify(
41
- `Completing preserved milestone merge for ${blocker.milestoneId} from ${blocker.branch} into ${blocker.integrationBranch}.`,
42
- "info",
43
- );
44
- const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
45
- if (result.success) {
46
- ctx.ui.notify(
47
- `Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Run /gsd again when ready.`,
48
- "info",
49
- );
50
- } else {
51
- ctx.ui.notify(
52
- `Milestone ${blocker.milestoneId} merge recovery failed: ${result.error}`,
53
- "error",
54
- );
55
- }
40
+ await runMergeMilestoneBlocker(ctx, basePath, blocker);
56
41
  return true;
57
42
  }
58
43
 
@@ -481,6 +481,8 @@ export async function handleCleanupProjects(args: string, ctx: ExtensionCommandC
481
481
  ctx.ui.notify(lines.join("\n"), "info");
482
482
  }
483
483
 
484
+ type HierarchyCounts = { milestones: number; slices: number; tasks: number };
485
+
484
486
  function recoverConfirmed(args: string): boolean {
485
487
  return args
486
488
  .split(/\s+/)
@@ -488,27 +490,82 @@ function recoverConfirmed(args: string): boolean {
488
490
  .some((part) => part === "--confirm" || part === "--yes" || part === "confirm");
489
491
  }
490
492
 
491
- async function confirmRecover(ctx: ExtensionCommandContext, args: string): Promise<boolean> {
492
- if (recoverConfirmed(args)) return true;
493
+ function recoverAllowsDataLoss(args: string): boolean {
494
+ return args
495
+ .split(/\s+/)
496
+ .map((part) => part.trim().toLowerCase())
497
+ .some((part) => part === "--allow-data-loss" || part === "--force");
498
+ }
493
499
 
500
+ async function confirmRecover(
501
+ ctx: ExtensionCommandContext,
502
+ args: string,
503
+ markdown: HierarchyCounts,
504
+ beforeDb: HierarchyCounts,
505
+ dataLoss: boolean,
506
+ ): Promise<boolean> {
494
507
  const warning = [
495
508
  "gsd recover imports markdown into the database.",
496
509
  "It clears and reconstructs milestone, slice, and task hierarchy rows from rendered markdown.",
497
510
  "Use /gsd rebuild markdown for normal DB-to-markdown realignment.",
498
- ].join("\n");
511
+ "",
512
+ ` Markdown on disk: ${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T`,
513
+ ` Current DB: ${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T`,
514
+ ];
515
+ if (dataLoss) {
516
+ warning.push(
517
+ "",
518
+ "⚠ The DB holds rows the markdown lacks. Recover will permanently DELETE",
519
+ " those rows. A snapshot is written to .gsd/backups/ first, but if the DB",
520
+ " is the source of truth you almost certainly want /gsd rebuild markdown.",
521
+ );
522
+ }
523
+ const warningText = warning.join("\n");
524
+
525
+ if (recoverConfirmed(args)) {
526
+ // Non-interactive --confirm still refuses a data-loss recover unless the
527
+ // caller explicitly opts in with --allow-data-loss / --force.
528
+ if (dataLoss && !recoverAllowsDataLoss(args)) {
529
+ ctx.ui.notify(
530
+ `${warningText}\n\nRefusing: this would delete authoritative DB rows. Re-run with ` +
531
+ `/gsd recover --confirm --allow-data-loss to proceed, or use /gsd rebuild markdown ` +
532
+ `to re-project markdown from the DB instead.`,
533
+ "error",
534
+ );
535
+ return false;
536
+ }
537
+ return true;
538
+ }
499
539
 
500
540
  if (typeof ctx.ui.confirm === "function") {
501
541
  const confirmed = await ctx.ui.confirm(
502
542
  "Import markdown into the DB?",
503
- `${warning}\n\nContinue only if the DB is lost or corrupt and markdown is the source you intend to import.`,
543
+ `${warningText}\n\nContinue only if the DB is lost or corrupt and markdown is the source you intend to import.`,
504
544
  );
505
- if (confirmed) return true;
506
- ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
507
- return false;
545
+ if (!confirmed) {
546
+ ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
547
+ return false;
548
+ }
549
+ // Data loss requires a second, explicit acknowledgement — the interactive
550
+ // equivalent of the --allow-data-loss opt-in the non-interactive paths
551
+ // demand. A single generic "yes" must not silently delete DB rows.
552
+ if (dataLoss) {
553
+ const acknowledged = await ctx.ui.confirm(
554
+ "Permanently delete DB rows the markdown lacks?",
555
+ "This recover will DELETE authoritative DB rows the markdown does not contain. " +
556
+ "A snapshot is saved to .gsd/backups/ first, but /gsd rebuild markdown is usually " +
557
+ "what you want. Proceed with the deletion?",
558
+ );
559
+ if (!acknowledged) {
560
+ ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
561
+ return false;
562
+ }
563
+ }
564
+ return true;
508
565
  }
509
566
 
510
567
  ctx.ui.notify(
511
- `${warning}\n\nNo database changes made. Re-run /gsd recover --confirm to proceed.`,
568
+ `${warningText}\n\nNo database changes made. Re-run /gsd recover --confirm to proceed.`,
512
569
  "warning",
513
570
  );
514
571
  return false;
@@ -524,18 +581,31 @@ async function confirmRecover(ctx: ExtensionCommandContext, args: string): Promi
524
581
  * Prints counts of recovered items and the resulting project phase.
525
582
  */
526
583
  export async function handleRecover(ctx: ExtensionCommandContext, basePath: string, args = ""): Promise<void> {
527
- const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction } = await import("./gsd-db.js");
584
+ const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction, backupDatabaseSnapshot } = await import("./gsd-db.js");
528
585
  const { migrateHierarchyToDb } = await import("./md-importer.js");
529
586
  const { invalidateStateCache } = await import("./state.js");
587
+ const { countDbHierarchy, countMarkdownHierarchy, recoverWouldDeleteDbRows } = await import("./migration-auto-check.js");
588
+ const { renderAllFromDb } = await import("./markdown-renderer.js");
530
589
 
531
590
  if (!dbAvailable()) {
532
591
  ctx.ui.notify("gsd recover: No database open. Run a GSD command first to initialize the DB.", "error");
533
592
  return;
534
593
  }
535
594
 
536
- if (!(await confirmRecover(ctx, args))) return;
595
+ // Compare markdown-on-disk against the live DB so the confirmation prompt can
596
+ // surface exactly what recover will overwrite (and refuse silent data loss).
597
+ // The data-loss check is identity-based, not count-based: it flags any DB row
598
+ // markdown lacks, including equal-count divergence (DB S99 vs markdown S01).
599
+ const markdown = countMarkdownHierarchy(basePath);
600
+ const beforeDb = countDbHierarchy();
601
+ const dataLoss = recoverWouldDeleteDbRows(basePath);
602
+
603
+ if (!(await confirmRecover(ctx, args, markdown, beforeDb, dataLoss))) return;
537
604
 
538
605
  try {
606
+ // 0. Snapshot the DB before the destructive clear so recover is reversible.
607
+ const backupPath = backupDatabaseSnapshot("pre-recover");
608
+
539
609
  // 1. Delete + re-populate inside a single transaction for atomicity.
540
610
  // clearEngineHierarchy() uses transaction() internally but transaction()
541
611
  // is re-entrant, so wrapping in dbTransaction() keeps the whole
@@ -545,9 +615,16 @@ export async function handleRecover(ctx: ExtensionCommandContext, basePath: stri
545
615
  return migrateHierarchyToDb(basePath);
546
616
  });
547
617
 
548
- // 3. Invalidate state cache so deriveState() picks up fresh DB data
618
+ // 2. Invalidate state cache so deriveState() picks up fresh DB data
549
619
  invalidateStateCache();
550
620
 
621
+ // 3. Re-project markdown from the freshly imported DB so disk and DB agree
622
+ // immediately (otherwise the markdown still reflects the pre-import state
623
+ // and the next startup check would flag fresh drift). renderAllFromDb
624
+ // swallows per-artifact failures into its result, so inspect them — a
625
+ // silent projection failure must not be reported as a clean success.
626
+ const renderResult = await renderAllFromDb(basePath);
627
+
551
628
  // 4. Derive state to verify sanity
552
629
  const state = await deriveState(basePath);
553
630
 
@@ -560,6 +637,23 @@ export async function handleRecover(ctx: ExtensionCommandContext, basePath: stri
560
637
  ``,
561
638
  ` Phase: ${state.phase}`,
562
639
  ];
640
+ // Post-import verification: markdown that failed to parse imports as fewer
641
+ // rows than countMarkdownHierarchy saw on disk. Surface the shortfall.
642
+ if (
643
+ counts.milestones < markdown.milestones ||
644
+ counts.slices < markdown.slices ||
645
+ counts.tasks < markdown.tasks
646
+ ) {
647
+ lines.push(
648
+ ``,
649
+ ` ⚠ Imported fewer rows than markdown contained ` +
650
+ `(${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T on disk). ` +
651
+ `Some markdown may have failed to parse — review before continuing.`,
652
+ );
653
+ }
654
+ if (backupPath) {
655
+ lines.push(``, ` Backup: ${backupPath}`);
656
+ }
563
657
  if (state.activeMilestone) {
564
658
  lines.push(` Active: ${state.activeMilestone.id}: ${state.activeMilestone.title}`);
565
659
  }
@@ -570,10 +664,27 @@ export async function handleRecover(ctx: ExtensionCommandContext, basePath: stri
570
664
  lines.push(` Task: ${state.activeTask.id}: ${state.activeTask.title}`);
571
665
  }
572
666
 
667
+ // Surface markdown projection failures: renderAllFromDb resolves even when
668
+ // individual artifacts fail to render, so a clean exit here would otherwise
669
+ // hide a stale/partial projection.
670
+ const renderFailed = renderResult.errors.length > 0;
671
+ if (renderFailed) {
672
+ lines.push(
673
+ ``,
674
+ ` ⚠ ${renderResult.errors.length} markdown projection(s) failed to render — ` +
675
+ `markdown may be stale. Re-run /gsd rebuild markdown.`,
676
+ );
677
+ for (const e of renderResult.errors.slice(0, 5)) lines.push(` - ${e}`);
678
+ if (renderResult.errors.length > 5) {
679
+ lines.push(` …and ${renderResult.errors.length - 5} more`);
680
+ }
681
+ }
682
+
573
683
  process.stderr.write(
574
- `gsd-recover: recovered ${counts.milestones}M/${counts.slices}S/${counts.tasks}T hierarchy\n`,
684
+ `gsd-recover: recovered ${counts.milestones}M/${counts.slices}S/${counts.tasks}T hierarchy` +
685
+ `${renderFailed ? ` (${renderResult.errors.length} projection errors)` : ""}\n`,
575
686
  );
576
- ctx.ui.notify(lines.join("\n"), "success");
687
+ ctx.ui.notify(lines.join("\n"), renderFailed ? "warning" : "success");
577
688
  } catch (err) {
578
689
  const msg = err instanceof Error ? err.message : String(err);
579
690
  logWarning("command", `recover failed: ${msg}`);
@@ -73,7 +73,7 @@ export function formatMcpInitResult(
73
73
  `Config: ${configPath}`,
74
74
  "",
75
75
  "MCP-capable clients can now load the GSD workflow and gsd-browser MCP servers from this folder.",
76
- "Pi Providers use the managed gsd-browser engine directly; this project config is for External MCP Clients.",
76
+ "Pi Providers use built-in browser tools directly; this project config is for External MCP Clients.",
77
77
  "Restart or reconnect any client that already has this project open.",
78
78
  ].join("\n");
79
79
  }
@@ -1285,7 +1285,7 @@ async function configureHooks(ctx: ExtensionCommandContext, prefs: Record<string
1285
1285
  if (geEnabled !== undefined) ge.enabled = geEnabled;
1286
1286
  const currentSliceGates = Array.isArray(ge.slice_gates) ? ge.slice_gates as string[] : [];
1287
1287
  const sgInput = await ctx.ui.input(
1288
- `Slice gates to evaluate (comma-separated, blank keeps)${currentSliceGates.length ? ` (current: ${currentSliceGates.join(", ")})` : " (default: Q3,Q4)"}:`,
1288
+ `Gate-evaluate slice gates (Q3,Q4; comma-separated, blank keeps)${currentSliceGates.length ? ` (current: ${currentSliceGates.join(", ")})` : " (default: Q3,Q4)"}:`,
1289
1289
  currentSliceGates.join(", "),
1290
1290
  );
1291
1291
  if (sgInput !== null && sgInput !== undefined) {
@@ -1751,7 +1751,7 @@ export function serializePreferencesToFrontmatter(prefs: Record<string, unknown>
1751
1751
  // Ordered keys for consistent output
1752
1752
  const orderedKeys = [
1753
1753
  "version", "mode", "always_use_skills", "prefer_skills", "avoid_skills",
1754
- "skill_rules", "custom_instructions", "models", "skill_discovery",
1754
+ "skill_rules", "custom_instructions", "models", "thinking", "skill_discovery",
1755
1755
  "skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
1756
1756
  "budget_ceiling", "budget_enforcement", "context_pause_threshold",
1757
1757
  "notifications", "cmux", "remote_questions", "git",
@@ -139,6 +139,7 @@ function collectConfigSections(): ConfigSection[] {
139
139
  if (sup.model) supRows.push({ label: "Model", value: sup.model });
140
140
  supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
141
141
  supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
142
+ supRows.push({ label: "Stalled tool timeout", value: `${sup.stalled_tool_timeout_minutes}m` });
142
143
  supRows.push({ label: "Hard timeout", value: `${sup.hard_timeout_minutes}m` });
143
144
  sections.push({ title: "Auto Supervisor", rows: supRows });
144
145
  }
@@ -5,10 +5,17 @@
5
5
  * Reduces context bloat between compactions with zero LLM overhead.
6
6
  * Preserves message ordering, roles, and all assistant/user messages.
7
7
  *
8
- * Operates on the pi-ai Message[] format (post-convertToLlm, pre-provider):
8
+ * Operates on provider payloads after convertToLlm:
9
+ *
10
+ * pi-ai Message[] payloads:
9
11
  * - toolResult messages: { role: "toolResult", content: TextContent[] }
10
12
  * - bash results are already converted to: { role: "user", content: [{type:"text",text:"..."}] }
11
13
  * and start with "Ran `" from bashExecutionToText.
14
+ *
15
+ * OpenAI/Codex Responses payloads:
16
+ * - conversation items live in `input`, not `messages`
17
+ * - tool results are { type: "function_call_output", output: string | content[] }
18
+ * - bash results are user items with input_text content starting with "Ran `"
12
19
  */
13
20
 
14
21
  interface MaskableMessage {
@@ -20,6 +27,37 @@ interface MaskableMessage {
20
27
 
21
28
  const MASK_PLACEHOLDER = "[result masked — within summarized history]";
22
29
  const MASK_CONTENT_BLOCK = [{ type: "text" as const, text: MASK_PLACEHOLDER }];
30
+ const RESPONSES_MASK_CONTENT_BLOCK = [{ type: "input_text" as const, text: MASK_PLACEHOLDER }];
31
+ const TRUNCATION_MARKER = "\n…[truncated]";
32
+
33
+ type TextLikeBlock = {
34
+ type?: string;
35
+ text?: unknown;
36
+ [key: string]: unknown;
37
+ };
38
+
39
+ interface ResponsesInputItem {
40
+ role?: string;
41
+ type?: string;
42
+ content?: unknown;
43
+ output?: unknown;
44
+ [key: string]: unknown;
45
+ }
46
+
47
+ function isTextLikeBlock(block: unknown): block is TextLikeBlock {
48
+ return Boolean(block && typeof block === "object" && "text" in block);
49
+ }
50
+
51
+ function firstTextFromContent(content: unknown): string | undefined {
52
+ if (typeof content === "string") return content;
53
+ if (!Array.isArray(content)) return undefined;
54
+ const first = content.find(isTextLikeBlock);
55
+ return typeof first?.text === "string" ? first.text : undefined;
56
+ }
57
+
58
+ function isBashResultText(text: string | undefined): boolean {
59
+ return typeof text === "string" && text.startsWith("Ran `");
60
+ }
23
61
 
24
62
  function findTurnBoundary(messages: MaskableMessage[], keepRecentTurns: number): number {
25
63
  let turnsSeen = 0;
@@ -43,10 +81,8 @@ function findTurnBoundary(messages: MaskableMessage[], keepRecentTurns: number):
43
81
  * The bashExecutionToText format always starts with "Ran `".
44
82
  */
45
83
  function isBashResultUserMessage(m: MaskableMessage): boolean {
46
- if (m.role !== "user" || !Array.isArray(m.content)) return false;
47
- const first = m.content[0];
48
- return first && typeof first === "object" && "text" in first &&
49
- typeof first.text === "string" && first.text.startsWith("Ran `");
84
+ if (m.role !== "user") return false;
85
+ return isBashResultText(firstTextFromContent(m.content));
50
86
  }
51
87
 
52
88
  function isMaskableMessage(m: MaskableMessage): boolean {
@@ -72,3 +108,114 @@ export function createObservationMask(keepRecentTurns: number = 8) {
72
108
  });
73
109
  };
74
110
  }
111
+
112
+ function isResponsesBashResultUserItem(item: ResponsesInputItem): boolean {
113
+ if (item.role !== "user") return false;
114
+ return isBashResultText(firstTextFromContent(item.content));
115
+ }
116
+
117
+ function findResponsesTurnBoundary(items: ResponsesInputItem[], keepRecentTurns: number): number {
118
+ let turnsSeen = 0;
119
+ for (let i = items.length - 1; i >= 0; i--) {
120
+ const item = items[i];
121
+ if (item.role === "user" && !isResponsesBashResultUserItem(item)) {
122
+ turnsSeen++;
123
+ if (turnsSeen >= keepRecentTurns) return i;
124
+ }
125
+ }
126
+ return 0;
127
+ }
128
+
129
+ /**
130
+ * Observation masking for OpenAI/Codex Responses API payloads.
131
+ *
132
+ * Responses payloads store the conversation under `input` instead of
133
+ * `messages`, with tool results as `function_call_output` items. Keep this
134
+ * separate from createObservationMask so each payload shape stays explicit.
135
+ */
136
+ export function createResponsesInputObservationMask(keepRecentTurns: number = 8) {
137
+ return (items: ResponsesInputItem[]): ResponsesInputItem[] => {
138
+ const boundary = findResponsesTurnBoundary(items, keepRecentTurns);
139
+ if (boundary === 0) return items;
140
+
141
+ return items.map((item, i) => {
142
+ if (i >= boundary) return item;
143
+ if (item.type === "function_call_output") {
144
+ return { ...item, output: MASK_PLACEHOLDER };
145
+ }
146
+ if (isResponsesBashResultUserItem(item)) {
147
+ return { ...item, content: RESPONSES_MASK_CONTENT_BLOCK };
148
+ }
149
+ return item;
150
+ });
151
+ };
152
+ }
153
+
154
+ function truncateText(text: string, maxChars: number): string {
155
+ if (text.length <= maxChars) return text;
156
+ return text.slice(0, maxChars) + TRUNCATION_MARKER;
157
+ }
158
+
159
+ function truncateTextBlocks(content: unknown, maxChars: number): unknown {
160
+ if (typeof content === "string") {
161
+ return truncateText(content, maxChars);
162
+ }
163
+ if (!Array.isArray(content)) return content;
164
+
165
+ let remaining = maxChars;
166
+ let didTruncate = false;
167
+ const nextBlocks: unknown[] = [];
168
+
169
+ for (const block of content) {
170
+ if (!isTextLikeBlock(block) || typeof block.text !== "string") {
171
+ nextBlocks.push(block);
172
+ continue;
173
+ }
174
+
175
+ if (remaining <= 0) {
176
+ didTruncate = true;
177
+ continue;
178
+ }
179
+
180
+ const text = block.text;
181
+ if (text.length <= remaining) {
182
+ nextBlocks.push(block);
183
+ remaining -= text.length;
184
+ continue;
185
+ }
186
+
187
+ nextBlocks.push({ ...block, text: truncateText(text, remaining) });
188
+ remaining = 0;
189
+ didTruncate = true;
190
+ }
191
+
192
+ return didTruncate ? nextBlocks : content;
193
+ }
194
+
195
+ function normalizedMaxChars(maxChars: number): number {
196
+ return Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : 800;
197
+ }
198
+
199
+ export function truncateContextResultMessages(messages: MaskableMessage[], maxChars: number = 800): MaskableMessage[] {
200
+ const limit = normalizedMaxChars(maxChars);
201
+ return messages.map((message) => {
202
+ if (!isMaskableMessage(message)) return message;
203
+ const content = truncateTextBlocks(message.content, limit);
204
+ return content === message.content ? message : { ...message, content };
205
+ });
206
+ }
207
+
208
+ export function truncateResponsesInputResultItems(items: ResponsesInputItem[], maxChars: number = 800): ResponsesInputItem[] {
209
+ const limit = normalizedMaxChars(maxChars);
210
+ return items.map((item) => {
211
+ if (item.type === "function_call_output") {
212
+ const output = truncateTextBlocks(item.output, limit);
213
+ return output === item.output ? item : { ...item, output };
214
+ }
215
+ if (isResponsesBashResultUserItem(item)) {
216
+ const content = truncateTextBlocks(item.content, limit);
217
+ return content === item.content ? item : { ...item, content };
218
+ }
219
+ return item;
220
+ });
221
+ }
@@ -452,6 +452,44 @@ export function _resetDecisionSaveLock(): void {
452
452
  _decisionSaveLock = Promise.resolve();
453
453
  }
454
454
 
455
+ /**
456
+ * Re-project root DECISIONS.md from the authoritative decision records, with no
457
+ * new decision being added. Mirrors the projection saveDecisionToDb performs
458
+ * after a save, but over the full set — so a DB → markdown re-projection
459
+ * (recover, rebuild, reconcile) re-derives DECISIONS.md and never leaves it
460
+ * showing a stale subset (e.g. after a worktree merge that accepted one
461
+ * branch's DECISIONS.md while the DB holds the union of both branches'
462
+ * decisions). DB stays the single source of truth; this only writes markdown.
463
+ */
464
+ export async function regenerateDecisionsMarkdown(basePath: string): Promise<void> {
465
+ const { getAllDecisionsFromMemories } = await import('./context-store.js');
466
+ const allDecisions: Decision[] = getAllDecisionsFromMemories();
467
+
468
+ const filePath = resolveGsdRootFile(basePath, 'DECISIONS');
469
+ let existingContent: string | null = null;
470
+ if (existsSync(filePath)) {
471
+ existingContent = readFileSync(filePath, 'utf-8');
472
+ }
473
+
474
+ // Nothing to project: no decisions in the DB and no file to normalize.
475
+ if (allDecisions.length === 0 && existingContent === null) return;
476
+
477
+ let md: string;
478
+ if (existingContent && !isDecisionsTableFormat(existingContent)) {
479
+ // Preserve freeform content; refresh only the appended decisions table.
480
+ const marker = '---\n\n## Decisions Table';
481
+ const markerIdx = existingContent.indexOf(marker);
482
+ const freeformPart = markerIdx >= 0
483
+ ? existingContent.substring(0, markerIdx).trimEnd()
484
+ : existingContent.trimEnd();
485
+ md = freeformPart + '\n' + generateDecisionsAppendBlock(allDecisions);
486
+ } else {
487
+ md = generateDecisionsMd(allDecisions);
488
+ }
489
+
490
+ await saveFile(filePath, md);
491
+ }
492
+
455
493
  // ─── Save Decision to DB + Regenerate Markdown ────────────────────────────
456
494
 
457
495
  export interface SaveDecisionFields {