@opengsd/gsd-pi 1.1.1-dev.9f86580 → 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 (261) 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/index.js +39 -22
  4. package/dist/resources/extensions/browser-tools/state.js +12 -0
  5. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  6. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  7. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  8. package/dist/resources/extensions/gsd/auto/phases.js +43 -10
  9. package/dist/resources/extensions/gsd/auto/session.js +20 -1
  10. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +72 -12
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +128 -9
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  14. package/dist/resources/extensions/gsd/auto-prompts.js +24 -19
  15. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  16. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  18. package/dist/resources/extensions/gsd/auto.js +14 -11
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -65
  21. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  22. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  23. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  24. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  25. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  26. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  27. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  28. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  29. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  31. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  32. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  33. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  34. package/dist/resources/extensions/gsd/preferences-models.js +110 -43
  35. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  36. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  37. package/dist/resources/extensions/gsd/preferences.js +4 -1
  38. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  41. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  42. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  43. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  44. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  45. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  46. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  47. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  48. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  49. package/dist/resources/extensions/gsd/state.js +7 -3
  50. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  51. package/dist/resources/extensions/gsd/tool-presentation-plan.js +1 -9
  52. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -6
  53. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  54. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  55. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +57 -429
  56. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  57. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  58. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  59. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  60. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  61. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  62. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  63. package/dist/resources/extensions/subagent/agents.js +1 -0
  64. package/dist/resources/extensions/subagent/index.js +27 -12
  65. package/dist/resources/extensions/subagent/launch.js +7 -2
  66. package/dist/web/standalone/.next/BUILD_ID +1 -1
  67. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  68. package/dist/web/standalone/.next/build-manifest.json +2 -2
  69. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  70. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.html +1 -1
  87. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  94. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  97. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  98. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  99. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  100. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  101. package/package.json +4 -4
  102. package/packages/cloud-mcp-gateway/package.json +2 -2
  103. package/packages/contracts/package.json +1 -1
  104. package/packages/daemon/package.json +4 -4
  105. package/packages/gsd-agent-core/package.json +5 -5
  106. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  108. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  110. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  112. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  113. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  114. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  115. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  116. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  117. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  118. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  119. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  120. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  121. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  122. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  123. package/packages/gsd-agent-modes/package.json +7 -7
  124. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  125. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  126. package/packages/mcp-server/package.json +3 -3
  127. package/packages/native/dist/native.js +22 -0
  128. package/packages/native/package.json +1 -1
  129. package/packages/pi-agent-core/package.json +1 -1
  130. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  131. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  133. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.d.ts +23 -17
  135. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  136. package/packages/pi-ai/dist/models.generated.js +25 -24
  137. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  138. package/packages/pi-ai/package.json +1 -1
  139. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  140. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  142. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  143. package/packages/pi-coding-agent/package.json +7 -7
  144. package/packages/pi-tui/dist/utils.d.ts +11 -0
  145. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  146. package/packages/pi-tui/dist/utils.js +119 -6
  147. package/packages/pi-tui/dist/utils.js.map +1 -1
  148. package/packages/pi-tui/package.json +2 -1
  149. package/packages/rpc-client/package.json +2 -2
  150. package/pkg/dist/theme/themes.js +1 -1
  151. package/pkg/dist/theme/themes.js.map +1 -1
  152. package/pkg/package.json +1 -1
  153. package/src/resources/extensions/browser-tools/index.ts +39 -22
  154. package/src/resources/extensions/browser-tools/state.ts +13 -0
  155. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  156. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  157. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  158. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  159. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  160. package/src/resources/extensions/gsd/auto/phases.ts +42 -10
  161. package/src/resources/extensions/gsd/auto/session.ts +22 -1
  162. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  163. package/src/resources/extensions/gsd/auto-dispatch.ts +85 -12
  164. package/src/resources/extensions/gsd/auto-model-selection.ts +164 -12
  165. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  166. package/src/resources/extensions/gsd/auto-prompts.ts +23 -20
  167. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  168. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  169. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  170. package/src/resources/extensions/gsd/auto.ts +13 -10
  171. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  172. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +225 -72
  173. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  174. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  175. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  176. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  177. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  178. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  179. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  180. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  181. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  182. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  183. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  184. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  185. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  186. package/src/resources/extensions/gsd/preferences-models.ts +112 -43
  187. package/src/resources/extensions/gsd/preferences-types.ts +39 -0
  188. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  189. package/src/resources/extensions/gsd/preferences.ts +5 -0
  190. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  192. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  193. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  194. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  195. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  196. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  197. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  198. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  199. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  200. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  201. package/src/resources/extensions/gsd/state.ts +7 -4
  202. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
  203. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  204. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  205. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  206. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  207. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  208. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  209. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  210. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  211. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +16 -2
  212. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  213. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  214. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  215. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  216. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +15 -0
  217. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  218. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  219. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  220. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  221. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  222. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  223. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +9 -0
  224. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  225. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  226. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -0
  227. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  228. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  229. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  230. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  231. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  232. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  233. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  234. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  235. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  236. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  237. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  238. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +73 -6
  239. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  240. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  241. package/src/resources/extensions/gsd/tool-contract.ts +28 -0
  242. package/src/resources/extensions/gsd/tool-presentation-plan.ts +1 -11
  243. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -6
  244. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  245. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  246. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +66 -526
  247. package/src/resources/extensions/gsd/types.ts +1 -0
  248. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  249. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  250. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  251. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  252. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  253. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  254. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  255. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  256. package/src/resources/extensions/subagent/agents.ts +4 -0
  257. package/src/resources/extensions/subagent/index.ts +28 -3
  258. package/src/resources/extensions/subagent/launch.ts +8 -0
  259. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  260. /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  261. /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -1,17 +1,17 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Registers GSD extension runtime hooks and token-saving tool policies.
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, mkdirSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { isToolCallEventType } from "@gsd/pi-coding-agent";
7
7
  import { ALWAYS_PRESERVED_SHIM_TOOL_NAMES } from "@gsd/pi-ai";
8
8
  import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
9
- import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
9
+ import { buildMilestoneFileName, clearPathCache, milestonesDir, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
10
10
  import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isMilestoneDepthVerified, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
11
11
  import { resolveManifest } from "../unit-context-manifest.js";
12
12
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
13
13
  import { loadFile, saveFile, formatContinue } from "../files.js";
14
- import { clearToolInvocationError, getAutoRuntimeSnapshot, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto-runtime-state.js";
14
+ import { clearToolInvocationError, getAutoRuntimeSnapshot, getSourceObservationStore, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError, } from "../auto-runtime-state.js";
15
15
  import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
16
16
  import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
17
17
  import { saveActivityLog } from "../activity-log.js";
@@ -31,6 +31,7 @@ import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-rec
31
31
  import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
32
32
  import { filterToolsForProvider } from "../model-router.js";
33
33
  import { RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
34
+ import { injectSourceContextBlockIntoPayload, supportsSourceObservationsForUnit } from "../source-observations.js";
34
35
  let approvalQuestionAbortInFlight = false;
35
36
  async function loadWelcomeScreenModule() {
36
37
  const candidates = [];
@@ -370,6 +371,50 @@ function deferApprovalGate(gateId, basePath) {
370
371
  function contextBasePath(ctx) {
371
372
  return typeof ctx?.cwd === "string" ? ctx.cwd : process.cwd();
372
373
  }
374
+ function beginSourceObservationStoreForCurrentUnit(ctx) {
375
+ if (!isAutoActive())
376
+ return null;
377
+ const dash = getAutoRuntimeSnapshot();
378
+ if (!dash.currentUnit)
379
+ return null;
380
+ if (!supportsSourceObservationsForUnit(dash.currentUnit.type))
381
+ return null;
382
+ const store = getSourceObservationStore();
383
+ store.beginUnit({
384
+ unitType: dash.currentUnit.type,
385
+ unitId: dash.currentUnit.id,
386
+ startedAt: dash.currentUnit.startedAt,
387
+ basePath: dash.currentUnit.workspaceRoot ?? (dash.basePath || contextBasePath(ctx)),
388
+ });
389
+ return store;
390
+ }
391
+ function refreshSourceObservationAfterMutation(canonicalName, input, ctx) {
392
+ if (canonicalName !== "edit" && canonicalName !== "write")
393
+ return;
394
+ if (!input || typeof input !== "object")
395
+ return;
396
+ const store = beginSourceObservationStoreForCurrentUnit(ctx);
397
+ if (!store)
398
+ return;
399
+ store.observeMutation(input);
400
+ }
401
+ function clearSourceObservationsAfterShell(canonicalName) {
402
+ if (!isAutoActive())
403
+ return;
404
+ if (!isShellExecutionTool(canonicalName))
405
+ return;
406
+ const dash = getAutoRuntimeSnapshot();
407
+ if (!dash.currentUnit || !supportsSourceObservationsForUnit(dash.currentUnit.type))
408
+ return;
409
+ getSourceObservationStore().clear();
410
+ }
411
+ function isShellExecutionTool(canonicalName) {
412
+ return canonicalName === "bash" ||
413
+ canonicalName === "bg_shell" ||
414
+ canonicalName === "async_bash" ||
415
+ canonicalName === "shell" ||
416
+ canonicalName === "powershell";
417
+ }
373
418
  function activateDeferredApprovalGate(basePath) {
374
419
  if (deferredApprovalGate?.basePath !== basePath)
375
420
  return;
@@ -392,6 +437,79 @@ function isContextDraftSummarySave(toolName, input) {
392
437
  return false;
393
438
  return input.artifact_type === "CONTEXT-DRAFT";
394
439
  }
440
+ function selectedAnswerLabel(selected) {
441
+ if (Array.isArray(selected))
442
+ return selected.map(String).join(", ");
443
+ if (selected == null)
444
+ return "";
445
+ return String(selected);
446
+ }
447
+ function formatQuestionExchange(questions, answers) {
448
+ const lines = [];
449
+ for (const question of questions) {
450
+ lines.push(`### ${question.header ?? "Question"}`, "", question.question ?? "");
451
+ if (Array.isArray(question.options)) {
452
+ lines.push("");
453
+ for (const opt of question.options) {
454
+ lines.push(`- **${opt.label ?? ""}** — ${opt.description ?? ""}`);
455
+ }
456
+ }
457
+ const answer = question.id ? answers?.[question.id] : undefined;
458
+ if (answer) {
459
+ lines.push("");
460
+ const selected = selectedAnswerLabel(answer.selected);
461
+ if (selected)
462
+ lines.push(`**Selected:** ${selected}`);
463
+ if (answer.notes)
464
+ lines.push(`**Notes:** ${String(answer.notes)}`);
465
+ }
466
+ lines.push("");
467
+ }
468
+ return lines.join("\n");
469
+ }
470
+ async function ensureMilestoneShell(basePath, milestoneId) {
471
+ const milestoneDir = resolveMilestonePath(basePath, milestoneId)
472
+ ?? join(milestonesDir(basePath), milestoneId);
473
+ mkdirSync(milestoneDir, { recursive: true });
474
+ clearPathCache();
475
+ try {
476
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
477
+ if (await ensureDbOpen(basePath)) {
478
+ const { getMilestone, insertMilestone } = await import("../gsd-db.js");
479
+ if (!getMilestone(milestoneId)) {
480
+ insertMilestone({
481
+ id: milestoneId,
482
+ title: `New milestone ${milestoneId}`,
483
+ status: "queued",
484
+ });
485
+ }
486
+ }
487
+ }
488
+ catch (err) {
489
+ safetyLogWarning("guided", `failed to persist milestone shell for ${milestoneId}: ${err.message}`);
490
+ }
491
+ return milestoneDir;
492
+ }
493
+ async function saveDiscussionQuestionRound(basePath, milestoneId, questions, details) {
494
+ const milestoneDir = await ensureMilestoneShell(basePath, milestoneId);
495
+ const answers = details?.response?.answers;
496
+ const timestamp = new Date().toISOString();
497
+ const exchange = formatQuestionExchange(questions, answers);
498
+ const discussionPath = join(milestoneDir, buildMilestoneFileName(milestoneId, "DISCUSSION"));
499
+ const existingDiscussion = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
500
+ await saveFile(discussionPath, `${existingDiscussion}## Exchange — ${timestamp}\n\n${exchange}---\n\n`);
501
+ const draftPath = join(milestoneDir, buildMilestoneFileName(milestoneId, "CONTEXT-DRAFT"));
502
+ const existingDraft = await loadFile(draftPath);
503
+ const draftHeader = existingDraft
504
+ ?? [
505
+ `# ${milestoneId}: New milestone ${milestoneId}`,
506
+ "",
507
+ "This draft was captured automatically from structured question responses.",
508
+ "Use it so `/gsd` can resume the in-flight milestone discussion.",
509
+ "",
510
+ ].join("\n");
511
+ await saveFile(draftPath, `${draftHeader.trimEnd()}\n\n## Captured Question Round — ${timestamp}\n\n${exchange}`);
512
+ }
395
513
  function withDepthGateDisplayReason(result, displayReason = "Depth confirmation is waiting for your answer.") {
396
514
  if (!result.block)
397
515
  return result;
@@ -918,6 +1036,17 @@ export function registerHooks(pi, ecosystemHandlers) {
918
1036
  if (isAutoActive() && typeof event.toolCallId === "string") {
919
1037
  markToolEnd(event.toolCallId);
920
1038
  }
1039
+ const toolName = canonicalToolName(event.toolName);
1040
+ if (isAutoActive() && toolName === "read" && !event.isError) {
1041
+ const store = beginSourceObservationStoreForCurrentUnit(ctx);
1042
+ if (store) {
1043
+ store.observeRead(event.input);
1044
+ }
1045
+ }
1046
+ if (!event.isError) {
1047
+ refreshSourceObservationAfterMutation(toolName, event.input, ctx);
1048
+ clearSourceObservationsAfterShell(toolName);
1049
+ }
921
1050
  if (isAutoActive() && event.isError) {
922
1051
  const resultPayload = ("result" in event ? event.result : undefined);
923
1052
  const errorText = typeof resultPayload === "string"
@@ -934,12 +1063,10 @@ export function registerHooks(pi, ecosystemHandlers) {
934
1063
  else if (isAutoActive()) {
935
1064
  clearToolInvocationError();
936
1065
  }
937
- const toolName = canonicalToolName(event.toolName);
938
1066
  if (toolName !== "ask_user_questions")
939
1067
  return;
940
1068
  const basePath = contextBasePath(ctx);
941
1069
  const milestoneId = await getDiscussionMilestoneIdFor(basePath);
942
- const queueActive = isQueuePhaseActive(basePath);
943
1070
  const details = event.details;
944
1071
  // ── Discussion gate enforcement: handle gate question responses ──
945
1072
  // If the result is cancelled or has no response, the pending gate stays active
@@ -1012,38 +1139,9 @@ export function registerHooks(pi, ecosystemHandlers) {
1012
1139
  break;
1013
1140
  }
1014
1141
  }
1015
- if (!milestoneId && !queueActive)
1016
- return;
1017
1142
  if (!milestoneId)
1018
1143
  return;
1019
- const milestoneDir = resolveMilestonePath(basePath, milestoneId);
1020
- if (!milestoneDir)
1021
- return;
1022
- const discussionPath = join(milestoneDir, buildMilestoneFileName(milestoneId, "DISCUSSION"));
1023
- const timestamp = new Date().toISOString();
1024
- const lines = [`## Exchange — ${timestamp}`, ""];
1025
- for (const question of questions) {
1026
- lines.push(`### ${question.header ?? "Question"}`, "", question.question ?? "");
1027
- if (Array.isArray(question.options)) {
1028
- lines.push("");
1029
- for (const opt of question.options) {
1030
- lines.push(`- **${opt.label}** — ${opt.description ?? ""}`);
1031
- }
1032
- }
1033
- const answer = details.response?.answers?.[question.id];
1034
- if (answer) {
1035
- lines.push("");
1036
- const selected = Array.isArray(answer.selected) ? answer.selected.join(", ") : answer.selected;
1037
- lines.push(`**Selected:** ${selected}`);
1038
- if (answer.notes) {
1039
- lines.push(`**Notes:** ${answer.notes}`);
1040
- }
1041
- }
1042
- lines.push("");
1043
- }
1044
- lines.push("---", "");
1045
- const existing = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
1046
- await saveFile(discussionPath, existing + lines.join("\n"));
1144
+ await saveDiscussionQuestionRound(basePath, milestoneId, questions, details);
1047
1145
  });
1048
1146
  pi.on("tool_execution_start", async (event, ctx) => {
1049
1147
  const basePath = contextBasePath(ctx);
@@ -1095,42 +1193,51 @@ export function registerHooks(pi, ecosystemHandlers) {
1095
1193
  const payload = event.payload;
1096
1194
  if (!payload || typeof payload !== "object")
1097
1195
  return;
1098
- // ── Observation Masking ─────────────────────────────────────────────
1099
- // Replace old tool results with placeholders to reduce context bloat.
1100
- // Only active during auto-mode when context_management.observation_masking is enabled.
1101
- if (isAutoActive()) {
1102
- try {
1103
- const { loadEffectiveGSDPreferences } = await import("../preferences.js");
1104
- const { createObservationMask, createResponsesInputObservationMask, truncateContextResultMessages, truncateResponsesInputResultItems, } = await import("../context-masker.js");
1105
- const prefs = loadEffectiveGSDPreferences();
1106
- const cmConfig = prefs?.preferences.context_management;
1107
- // Observation masking: replace old tool results with placeholders
1108
- if (cmConfig?.observation_masking !== false) {
1109
- const keepTurns = cmConfig?.observation_mask_turns ?? 8;
1110
- const messages = payload.messages;
1111
- if (Array.isArray(messages)) {
1112
- payload.messages = createObservationMask(keepTurns)(messages);
1113
- }
1114
- const input = payload.input;
1115
- if (Array.isArray(input)) {
1116
- payload.input = createResponsesInputObservationMask(keepTurns)(input);
1117
- }
1118
- }
1119
- // Tool result truncation: cap individual tool result content length.
1120
- // In pi-ai format, toolResult messages have role: "toolResult" and content: TextContent[].
1121
- // Creates new objects to avoid mutating shared conversation state.
1122
- const maxChars = cmConfig?.tool_result_max_chars ?? 800;
1123
- const msgs = payload.messages;
1124
- if (Array.isArray(msgs)) {
1125
- payload.messages = truncateContextResultMessages(msgs, maxChars);
1196
+ // ── Context Management ──────────────────────────────────────────────
1197
+ // Load preferences once for both masking and truncation.
1198
+ try {
1199
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
1200
+ const { createObservationMask, createResponsesInputObservationMask, truncateContextResultMessages, truncateResponsesInputResultItems, } = await import("../context-masker.js");
1201
+ const prefs = loadEffectiveGSDPreferences();
1202
+ const cmConfig = prefs?.preferences.context_management;
1203
+ // Observation masking: replace old tool results with placeholders.
1204
+ // Only active during auto-mode when context_management.observation_masking is enabled.
1205
+ if (isAutoActive() && cmConfig?.observation_masking !== false) {
1206
+ const keepTurns = cmConfig?.observation_mask_turns ?? 8;
1207
+ const messages = payload.messages;
1208
+ if (Array.isArray(messages)) {
1209
+ payload.messages = createObservationMask(keepTurns)(messages);
1126
1210
  }
1127
1211
  const input = payload.input;
1128
1212
  if (Array.isArray(input)) {
1129
- payload.input = truncateResponsesInputResultItems(input, maxChars);
1213
+ payload.input = createResponsesInputObservationMask(keepTurns)(input);
1214
+ }
1215
+ }
1216
+ // Tool result truncation: cap individual tool result content length.
1217
+ // Applies in ALL modes (auto + interactive) to prevent context bloat.
1218
+ // In pi-ai format, toolResult messages have role: "toolResult" and content: TextContent[].
1219
+ // Creates new objects to avoid mutating shared conversation state.
1220
+ const maxChars = cmConfig?.tool_result_max_chars ?? 800;
1221
+ const msgs = payload.messages;
1222
+ if (Array.isArray(msgs)) {
1223
+ payload.messages = truncateContextResultMessages(msgs, maxChars);
1224
+ }
1225
+ const input = payload.input;
1226
+ if (Array.isArray(input)) {
1227
+ payload.input = truncateResponsesInputResultItems(input, maxChars);
1228
+ }
1229
+ }
1230
+ catch { /* non-fatal */ }
1231
+ try {
1232
+ if (isAutoActive()) {
1233
+ const sourceContextBlock = getSourceObservationStore().renderActiveBlock();
1234
+ if (sourceContextBlock) {
1235
+ const nextPayload = injectSourceContextBlockIntoPayload(payload, sourceContextBlock);
1236
+ Object.assign(payload, nextPayload);
1130
1237
  }
1131
1238
  }
1132
- catch { /* non-fatal */ }
1133
1239
  }
1240
+ catch { /* non-fatal */ }
1134
1241
  // ── Service Tier ────────────────────────────────────────────────────
1135
1242
  const modelId = event.model?.id;
1136
1243
  if (!modelId)
@@ -1,10 +1,17 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Shared closeout detection and merge actions for /gsd home and smart entry.
3
+ import { setAutoOutcomeWidget } from "./auto-dashboard.js";
3
4
  import { invalidateAllCaches } from "./cache.js";
4
5
  import { mergeCompletedMilestone } from "./parallel-merge.js";
5
6
  import { cleanupQuickBranch, detectStrandedQuickBranch } from "./quick.js";
6
7
  import { findUnmergedCompletedMilestones, } from "./unmerged-milestone-guard.js";
7
8
  import { appendRequirementsBacklogToSummary } from "./requirements-backlog.js";
9
+ const MILESTONE_MERGE_CLOSEOUT_COMMANDS = [
10
+ "/gsd status for overview",
11
+ "/gsd visualize to inspect",
12
+ "/gsd notifications for history",
13
+ "/gsd start for new work",
14
+ ];
8
15
  export async function loadCloseoutContext(basePath) {
9
16
  const unmergedMilestones = await findUnmergedCompletedMilestones(basePath);
10
17
  return {
@@ -65,6 +72,18 @@ export function buildIdleMenuSummary(state, closeout) {
65
72
  }
66
73
  return [state.nextAction || "No active milestone."];
67
74
  }
75
+ export function showMilestoneMergeCloseout(ctx, blocker) {
76
+ ctx.ui.setStatus?.("gsd-auto", undefined);
77
+ ctx.ui.setStatus?.("gsd-step", undefined);
78
+ ctx.ui.setWidget?.("gsd-progress", undefined);
79
+ setAutoOutcomeWidget(ctx, {
80
+ status: "complete",
81
+ title: `Milestone ${blocker.milestoneId} merged`,
82
+ detail: `Merged ${blocker.branch} into ${blocker.integrationBranch}. Product changes are now on ${blocker.integrationBranch}.`,
83
+ nextAction: "Review the closeout, then start the next milestone when ready.",
84
+ commands: MILESTONE_MERGE_CLOSEOUT_COMMANDS,
85
+ });
86
+ }
68
87
  export async function runMergeQuickTask(ctx, basePath, strandedQuick) {
69
88
  const merged = cleanupQuickBranch(basePath);
70
89
  if (merged) {
@@ -75,6 +94,18 @@ export async function runMergeQuickTask(ctx, basePath, strandedQuick) {
75
94
  ctx.ui.notify("Could not merge the quick-task branch automatically. Run `git status`, resolve any conflicts, then retry /gsd.", "error");
76
95
  return false;
77
96
  }
97
+ export async function runMergeMilestoneBlocker(ctx, basePath, blocker) {
98
+ ctx.ui.notify(`Completing preserved milestone merge for ${blocker.milestoneId} from ${blocker.branch} into ${blocker.integrationBranch}.`, "info");
99
+ const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
100
+ if (result.success) {
101
+ invalidateAllCaches();
102
+ showMilestoneMergeCloseout(ctx, blocker);
103
+ ctx.ui.notify(`Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Closeout is complete.`, "info");
104
+ return true;
105
+ }
106
+ ctx.ui.notify(`Milestone ${blocker.milestoneId} merge failed: ${result.error ?? "unknown error"}`, "error");
107
+ return false;
108
+ }
78
109
  export async function runMergeMilestone(ctx, basePath, milestoneId) {
79
110
  const blockers = await findUnmergedCompletedMilestones(basePath);
80
111
  const blocker = milestoneId
@@ -84,15 +115,7 @@ export async function runMergeMilestone(ctx, basePath, milestoneId) {
84
115
  ctx.ui.notify("No unmerged completed milestone found.", "warning");
85
116
  return false;
86
117
  }
87
- ctx.ui.notify(`Completing preserved milestone merge for ${blocker.milestoneId} from ${blocker.branch} into ${blocker.integrationBranch}.`, "info");
88
- const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
89
- if (result.success) {
90
- ctx.ui.notify(`Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Run /gsd again when ready.`, "info");
91
- invalidateAllCaches();
92
- return true;
93
- }
94
- ctx.ui.notify(`Milestone ${blocker.milestoneId} merge failed: ${result.error ?? "unknown error"}`, "error");
95
- return false;
118
+ return runMergeMilestoneBlocker(ctx, basePath, blocker);
96
119
  }
97
120
  export async function handleCloseoutChoice(ctx, basePath, choice, closeout) {
98
121
  if (choice === "finish_quick") {
@@ -14,7 +14,7 @@ import { handleSessionReport } from "../../commands-session-report.js";
14
14
  import { handlePrBranch } from "../../commands-pr-branch.js";
15
15
  import { currentDirectoryRoot, projectRoot } from "../context.js";
16
16
  import { findUnmergedCompletedMilestones } from "../../unmerged-milestone-guard.js";
17
- import { mergeCompletedMilestone } from "../../parallel-merge.js";
17
+ import { runMergeMilestoneBlocker } from "../../closeout-wizard.js";
18
18
  async function handleCompletedMilestoneRecovery(phase, ctx, basePath) {
19
19
  const tokens = phase.split(/\s+/).filter(Boolean);
20
20
  const dispatchPhase = tokens[0] ?? "";
@@ -27,14 +27,7 @@ async function handleCompletedMilestoneRecovery(phase, ctx, basePath) {
27
27
  : blockers[0];
28
28
  if (!blocker)
29
29
  return false;
30
- ctx.ui.notify(`Completing preserved milestone merge for ${blocker.milestoneId} from ${blocker.branch} into ${blocker.integrationBranch}.`, "info");
31
- const result = await mergeCompletedMilestone(basePath, blocker.milestoneId);
32
- if (result.success) {
33
- ctx.ui.notify(`Milestone ${blocker.milestoneId} merged to ${blocker.integrationBranch}. Run /gsd again when ready.`, "info");
34
- }
35
- else {
36
- ctx.ui.notify(`Milestone ${blocker.milestoneId} merge recovery failed: ${result.error}`, "error");
37
- }
30
+ await runMergeMilestoneBlocker(ctx, basePath, blocker);
38
31
  return true;
39
32
  }
40
33
  export function normalizeReportExportArgs(trimmed) {
@@ -448,22 +448,57 @@ function recoverConfirmed(args) {
448
448
  .map((part) => part.trim().toLowerCase())
449
449
  .some((part) => part === "--confirm" || part === "--yes" || part === "confirm");
450
450
  }
451
- async function confirmRecover(ctx, args) {
452
- if (recoverConfirmed(args))
453
- return true;
451
+ function recoverAllowsDataLoss(args) {
452
+ return args
453
+ .split(/\s+/)
454
+ .map((part) => part.trim().toLowerCase())
455
+ .some((part) => part === "--allow-data-loss" || part === "--force");
456
+ }
457
+ async function confirmRecover(ctx, args, markdown, beforeDb, dataLoss) {
454
458
  const warning = [
455
459
  "gsd recover imports markdown into the database.",
456
460
  "It clears and reconstructs milestone, slice, and task hierarchy rows from rendered markdown.",
457
461
  "Use /gsd rebuild markdown for normal DB-to-markdown realignment.",
458
- ].join("\n");
462
+ "",
463
+ ` Markdown on disk: ${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T`,
464
+ ` Current DB: ${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T`,
465
+ ];
466
+ if (dataLoss) {
467
+ warning.push("", "⚠ The DB holds rows the markdown lacks. Recover will permanently DELETE", " those rows. A snapshot is written to .gsd/backups/ first, but if the DB", " is the source of truth you almost certainly want /gsd rebuild markdown.");
468
+ }
469
+ const warningText = warning.join("\n");
470
+ if (recoverConfirmed(args)) {
471
+ // Non-interactive --confirm still refuses a data-loss recover unless the
472
+ // caller explicitly opts in with --allow-data-loss / --force.
473
+ if (dataLoss && !recoverAllowsDataLoss(args)) {
474
+ ctx.ui.notify(`${warningText}\n\nRefusing: this would delete authoritative DB rows. Re-run with ` +
475
+ `/gsd recover --confirm --allow-data-loss to proceed, or use /gsd rebuild markdown ` +
476
+ `to re-project markdown from the DB instead.`, "error");
477
+ return false;
478
+ }
479
+ return true;
480
+ }
459
481
  if (typeof ctx.ui.confirm === "function") {
460
- const confirmed = await ctx.ui.confirm("Import markdown into the DB?", `${warning}\n\nContinue only if the DB is lost or corrupt and markdown is the source you intend to import.`);
461
- if (confirmed)
462
- return true;
463
- ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
464
- return false;
482
+ const confirmed = await ctx.ui.confirm("Import markdown into the DB?", `${warningText}\n\nContinue only if the DB is lost or corrupt and markdown is the source you intend to import.`);
483
+ if (!confirmed) {
484
+ ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
485
+ return false;
486
+ }
487
+ // Data loss requires a second, explicit acknowledgement — the interactive
488
+ // equivalent of the --allow-data-loss opt-in the non-interactive paths
489
+ // demand. A single generic "yes" must not silently delete DB rows.
490
+ if (dataLoss) {
491
+ const acknowledged = await ctx.ui.confirm("Permanently delete DB rows the markdown lacks?", "This recover will DELETE authoritative DB rows the markdown does not contain. " +
492
+ "A snapshot is saved to .gsd/backups/ first, but /gsd rebuild markdown is usually " +
493
+ "what you want. Proceed with the deletion?");
494
+ if (!acknowledged) {
495
+ ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
496
+ return false;
497
+ }
498
+ }
499
+ return true;
465
500
  }
466
- ctx.ui.notify(`${warning}\n\nNo database changes made. Re-run /gsd recover --confirm to proceed.`, "warning");
501
+ ctx.ui.notify(`${warningText}\n\nNo database changes made. Re-run /gsd recover --confirm to proceed.`, "warning");
467
502
  return false;
468
503
  }
469
504
  /**
@@ -476,16 +511,27 @@ async function confirmRecover(ctx, args) {
476
511
  * Prints counts of recovered items and the resulting project phase.
477
512
  */
478
513
  export async function handleRecover(ctx, basePath, args = "") {
479
- const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction } = await import("./gsd-db.js");
514
+ const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction, backupDatabaseSnapshot } = await import("./gsd-db.js");
480
515
  const { migrateHierarchyToDb } = await import("./md-importer.js");
481
516
  const { invalidateStateCache } = await import("./state.js");
517
+ const { countDbHierarchy, countMarkdownHierarchy, recoverWouldDeleteDbRows } = await import("./migration-auto-check.js");
518
+ const { renderAllFromDb } = await import("./markdown-renderer.js");
482
519
  if (!dbAvailable()) {
483
520
  ctx.ui.notify("gsd recover: No database open. Run a GSD command first to initialize the DB.", "error");
484
521
  return;
485
522
  }
486
- if (!(await confirmRecover(ctx, args)))
523
+ // Compare markdown-on-disk against the live DB so the confirmation prompt can
524
+ // surface exactly what recover will overwrite (and refuse silent data loss).
525
+ // The data-loss check is identity-based, not count-based: it flags any DB row
526
+ // markdown lacks, including equal-count divergence (DB S99 vs markdown S01).
527
+ const markdown = countMarkdownHierarchy(basePath);
528
+ const beforeDb = countDbHierarchy();
529
+ const dataLoss = recoverWouldDeleteDbRows(basePath);
530
+ if (!(await confirmRecover(ctx, args, markdown, beforeDb, dataLoss)))
487
531
  return;
488
532
  try {
533
+ // 0. Snapshot the DB before the destructive clear so recover is reversible.
534
+ const backupPath = backupDatabaseSnapshot("pre-recover");
489
535
  // 1. Delete + re-populate inside a single transaction for atomicity.
490
536
  // clearEngineHierarchy() uses transaction() internally but transaction()
491
537
  // is re-entrant, so wrapping in dbTransaction() keeps the whole
@@ -494,8 +540,14 @@ export async function handleRecover(ctx, basePath, args = "") {
494
540
  clearEngineHierarchy();
495
541
  return migrateHierarchyToDb(basePath);
496
542
  });
497
- // 3. Invalidate state cache so deriveState() picks up fresh DB data
543
+ // 2. Invalidate state cache so deriveState() picks up fresh DB data
498
544
  invalidateStateCache();
545
+ // 3. Re-project markdown from the freshly imported DB so disk and DB agree
546
+ // immediately (otherwise the markdown still reflects the pre-import state
547
+ // and the next startup check would flag fresh drift). renderAllFromDb
548
+ // swallows per-artifact failures into its result, so inspect them — a
549
+ // silent projection failure must not be reported as a clean success.
550
+ const renderResult = await renderAllFromDb(basePath);
499
551
  // 4. Derive state to verify sanity
500
552
  const state = await deriveState(basePath);
501
553
  // 5. Report
@@ -507,6 +559,18 @@ export async function handleRecover(ctx, basePath, args = "") {
507
559
  ``,
508
560
  ` Phase: ${state.phase}`,
509
561
  ];
562
+ // Post-import verification: markdown that failed to parse imports as fewer
563
+ // rows than countMarkdownHierarchy saw on disk. Surface the shortfall.
564
+ if (counts.milestones < markdown.milestones ||
565
+ counts.slices < markdown.slices ||
566
+ counts.tasks < markdown.tasks) {
567
+ lines.push(``, ` ⚠ Imported fewer rows than markdown contained ` +
568
+ `(${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T on disk). ` +
569
+ `Some markdown may have failed to parse — review before continuing.`);
570
+ }
571
+ if (backupPath) {
572
+ lines.push(``, ` Backup: ${backupPath}`);
573
+ }
510
574
  if (state.activeMilestone) {
511
575
  lines.push(` Active: ${state.activeMilestone.id}: ${state.activeMilestone.title}`);
512
576
  }
@@ -516,8 +580,22 @@ export async function handleRecover(ctx, basePath, args = "") {
516
580
  if (state.activeTask) {
517
581
  lines.push(` Task: ${state.activeTask.id}: ${state.activeTask.title}`);
518
582
  }
519
- process.stderr.write(`gsd-recover: recovered ${counts.milestones}M/${counts.slices}S/${counts.tasks}T hierarchy\n`);
520
- ctx.ui.notify(lines.join("\n"), "success");
583
+ // Surface markdown projection failures: renderAllFromDb resolves even when
584
+ // individual artifacts fail to render, so a clean exit here would otherwise
585
+ // hide a stale/partial projection.
586
+ const renderFailed = renderResult.errors.length > 0;
587
+ if (renderFailed) {
588
+ lines.push(``, ` ⚠ ${renderResult.errors.length} markdown projection(s) failed to render — ` +
589
+ `markdown may be stale. Re-run /gsd rebuild markdown.`);
590
+ for (const e of renderResult.errors.slice(0, 5))
591
+ lines.push(` - ${e}`);
592
+ if (renderResult.errors.length > 5) {
593
+ lines.push(` …and ${renderResult.errors.length - 5} more`);
594
+ }
595
+ }
596
+ process.stderr.write(`gsd-recover: recovered ${counts.milestones}M/${counts.slices}S/${counts.tasks}T hierarchy` +
597
+ `${renderFailed ? ` (${renderResult.errors.length} projection errors)` : ""}\n`);
598
+ ctx.ui.notify(lines.join("\n"), renderFailed ? "warning" : "success");
521
599
  }
522
600
  catch (err) {
523
601
  const msg = err instanceof Error ? err.message : String(err);
@@ -1236,7 +1236,7 @@ async function configureHooks(ctx, prefs) {
1236
1236
  if (geEnabled !== undefined)
1237
1237
  ge.enabled = geEnabled;
1238
1238
  const currentSliceGates = Array.isArray(ge.slice_gates) ? ge.slice_gates : [];
1239
- const sgInput = await ctx.ui.input(`Slice gates to evaluate (comma-separated, blank keeps)${currentSliceGates.length ? ` (current: ${currentSliceGates.join(", ")})` : " (default: Q3,Q4)"}:`, currentSliceGates.join(", "));
1239
+ const sgInput = await ctx.ui.input(`Gate-evaluate slice gates (Q3,Q4; comma-separated, blank keeps)${currentSliceGates.length ? ` (current: ${currentSliceGates.join(", ")})` : " (default: Q3,Q4)"}:`, currentSliceGates.join(", "));
1240
1240
  if (sgInput !== null && sgInput !== undefined) {
1241
1241
  const parsed = parseStringList(sgInput);
1242
1242
  if (parsed.length > 0)
@@ -1697,7 +1697,7 @@ export function serializePreferencesToFrontmatter(prefs) {
1697
1697
  // Ordered keys for consistent output
1698
1698
  const orderedKeys = [
1699
1699
  "version", "mode", "always_use_skills", "prefer_skills", "avoid_skills",
1700
- "skill_rules", "custom_instructions", "models", "skill_discovery",
1700
+ "skill_rules", "custom_instructions", "models", "thinking", "skill_discovery",
1701
1701
  "skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
1702
1702
  "budget_ceiling", "budget_enforcement", "context_pause_threshold",
1703
1703
  "notifications", "cmux", "remote_questions", "git",
@@ -397,6 +397,41 @@ let _decisionSaveLock = Promise.resolve();
397
397
  export function _resetDecisionSaveLock() {
398
398
  _decisionSaveLock = Promise.resolve();
399
399
  }
400
+ /**
401
+ * Re-project root DECISIONS.md from the authoritative decision records, with no
402
+ * new decision being added. Mirrors the projection saveDecisionToDb performs
403
+ * after a save, but over the full set — so a DB → markdown re-projection
404
+ * (recover, rebuild, reconcile) re-derives DECISIONS.md and never leaves it
405
+ * showing a stale subset (e.g. after a worktree merge that accepted one
406
+ * branch's DECISIONS.md while the DB holds the union of both branches'
407
+ * decisions). DB stays the single source of truth; this only writes markdown.
408
+ */
409
+ export async function regenerateDecisionsMarkdown(basePath) {
410
+ const { getAllDecisionsFromMemories } = await import('./context-store.js');
411
+ const allDecisions = getAllDecisionsFromMemories();
412
+ const filePath = resolveGsdRootFile(basePath, 'DECISIONS');
413
+ let existingContent = null;
414
+ if (existsSync(filePath)) {
415
+ existingContent = readFileSync(filePath, 'utf-8');
416
+ }
417
+ // Nothing to project: no decisions in the DB and no file to normalize.
418
+ if (allDecisions.length === 0 && existingContent === null)
419
+ return;
420
+ let md;
421
+ if (existingContent && !isDecisionsTableFormat(existingContent)) {
422
+ // Preserve freeform content; refresh only the appended decisions table.
423
+ const marker = '---\n\n## Decisions Table';
424
+ const markerIdx = existingContent.indexOf(marker);
425
+ const freeformPart = markerIdx >= 0
426
+ ? existingContent.substring(0, markerIdx).trimEnd()
427
+ : existingContent.trimEnd();
428
+ md = freeformPart + '\n' + generateDecisionsAppendBlock(allDecisions);
429
+ }
430
+ else {
431
+ md = generateDecisionsMd(allDecisions);
432
+ }
433
+ await saveFile(filePath, md);
434
+ }
400
435
  /**
401
436
  * Save a new decision to DB and regenerate DECISIONS.md.
402
437
  * Auto-assigns the next ID via nextDecisionId().