@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 1.1.1-dev.74e8dd1

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 (232) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
  3. package/dist/resources/extensions/gsd/auto/phases.js +4 -3
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
  7. package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
  8. package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
  9. package/dist/resources/extensions/gsd/auto-start.js +94 -15
  10. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
  11. package/dist/resources/extensions/gsd/auto.js +22 -4
  12. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
  13. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
  16. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  17. package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
  18. package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
  19. package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
  20. package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
  21. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
  22. package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
  23. package/dist/resources/extensions/gsd/config-overlay.js +2 -1
  24. package/dist/resources/extensions/gsd/error-classifier.js +2 -1
  25. package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
  26. package/dist/resources/extensions/gsd/gsd-db.js +37 -4
  27. package/dist/resources/extensions/gsd/guided-flow.js +1 -1
  28. package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
  29. package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
  31. package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
  32. package/dist/resources/extensions/gsd/prompts/system.md +3 -1
  33. package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
  34. package/dist/resources/extensions/gsd/skill-activation.js +20 -3
  35. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
  36. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
  37. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
  38. package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
  39. package/dist/resources/extensions/gsd/state.js +15 -12
  40. package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
  41. package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
  42. package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
  43. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  44. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
  45. package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
  46. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
  47. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
  48. package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
  49. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
  50. package/dist/resources/extensions/mcp-client/manager.js +31 -1
  51. package/dist/web/standalone/.next/BUILD_ID +1 -1
  52. package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
  53. package/dist/web/standalone/.next/build-manifest.json +2 -2
  54. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  55. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.html +1 -1
  72. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app-paths-manifest.json +4 -4
  79. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  80. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  82. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  83. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  84. package/package.json +2 -2
  85. package/packages/cloud-mcp-gateway/package.json +2 -2
  86. package/packages/contracts/dist/workflow.d.ts +14 -0
  87. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  88. package/packages/contracts/dist/workflow.js +16 -0
  89. package/packages/contracts/dist/workflow.js.map +1 -1
  90. package/packages/contracts/package.json +1 -1
  91. package/packages/daemon/package.json +4 -4
  92. package/packages/gsd-agent-core/package.json +5 -5
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  98. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +72 -31
  100. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
  102. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
  103. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  104. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  106. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  108. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  112. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
  113. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  114. package/packages/gsd-agent-modes/package.json +7 -7
  115. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  116. package/packages/mcp-server/dist/workflow-tools.js +82 -0
  117. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  118. package/packages/mcp-server/package.json +3 -3
  119. package/packages/native/package.json +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
  122. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/image-models.generated.js +15 -0
  124. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  125. package/packages/pi-ai/dist/models.generated.d.ts +338 -17
  126. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  127. package/packages/pi-ai/dist/models.generated.js +412 -112
  128. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  129. package/packages/pi-ai/package.json +1 -1
  130. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  131. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  133. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  134. package/packages/pi-coding-agent/package.json +7 -7
  135. package/packages/pi-tui/dist/terminal.d.ts +1 -0
  136. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  137. package/packages/pi-tui/dist/terminal.js +8 -4
  138. package/packages/pi-tui/dist/terminal.js.map +1 -1
  139. package/packages/pi-tui/package.json +1 -1
  140. package/packages/rpc-client/package.json +2 -2
  141. package/pkg/package.json +1 -1
  142. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
  143. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
  144. package/src/resources/extensions/gsd/auto/phases.ts +5 -3
  145. package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
  146. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
  147. package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
  148. package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
  149. package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
  150. package/src/resources/extensions/gsd/auto-start.ts +112 -17
  151. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
  152. package/src/resources/extensions/gsd/auto.ts +35 -3
  153. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
  154. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
  155. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
  156. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
  157. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  158. package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
  159. package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
  160. package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
  161. package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
  162. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
  163. package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
  164. package/src/resources/extensions/gsd/config-overlay.ts +3 -1
  165. package/src/resources/extensions/gsd/error-classifier.ts +2 -1
  166. package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
  167. package/src/resources/extensions/gsd/gsd-db.ts +41 -6
  168. package/src/resources/extensions/gsd/guided-flow.ts +1 -1
  169. package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
  170. package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
  171. package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
  172. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  173. package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
  174. package/src/resources/extensions/gsd/prompts/system.md +3 -1
  175. package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
  176. package/src/resources/extensions/gsd/skill-activation.ts +20 -2
  177. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
  178. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
  179. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
  180. package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
  181. package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
  182. package/src/resources/extensions/gsd/state.ts +16 -12
  183. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
  184. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
  185. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
  186. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
  187. package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
  188. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
  189. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
  190. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
  191. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
  192. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
  193. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
  194. package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
  195. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
  196. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
  197. package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
  198. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
  199. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
  200. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
  201. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
  202. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
  203. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
  204. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
  205. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
  206. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
  207. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
  209. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
  210. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
  211. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
  212. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
  213. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
  214. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
  215. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
  216. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
  217. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
  218. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
  219. package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
  220. package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
  221. package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
  222. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  223. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
  224. package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
  225. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
  226. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
  227. package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
  228. package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
  229. package/src/resources/extensions/mcp-client/manager.ts +33 -1
  230. package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
  231. /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
  232. /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
@@ -25,6 +25,57 @@ async function loadContextModePreferences(baseDir: string) {
25
25
  }
26
26
 
27
27
  export function registerExecTools(pi: ExtensionAPI): void {
28
+ pi.registerTool({
29
+ name: "gsd_uat_exec",
30
+ label: "UAT Exec",
31
+ description:
32
+ "Run a UAT-scoped bash/node/python check with milestone/slice/check metadata. " +
33
+ "Uses the same capped .gsd/exec evidence store as gsd_exec, but rejects commands that mutate dependencies, git state, credentials, or destructive files.",
34
+ promptSnippet: "Run one UAT check and save typed evidence under .gsd/exec",
35
+ promptGuidelines: [
36
+ "Use gsd_uat_exec for each automated UAT check.",
37
+ "Every PASS/FAIL check saved by gsd_uat_result_save must reference objective evidence from this tool or another approved GSD evidence path.",
38
+ "Do not install packages, mutate git state, edit source files, or dump credentials during UAT.",
39
+ ],
40
+ parameters: Type.Object({
41
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
42
+ sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
43
+ checkId: Type.String({ description: "Stable check ID from the UAT spec (e.g. UAT-01)" }),
44
+ intent: Type.String({
45
+ description:
46
+ "UAT command intent. Use one canonical value: uat-artifact-check, uat-runtime-check, " +
47
+ "uat-browser-check, uat-service-start, or uat-log-inspection. Short aliases such as artifact, " +
48
+ "runtime, browser, service-start, and log-inspection are accepted.",
49
+ }),
50
+ runtime: Type.Optional(
51
+ Type.String({
52
+ description:
53
+ "Optional interpreter. Defaults to bash. Supported: bash, node, python; sh/shell, js/nodejs, and py/python3 aliases are accepted.",
54
+ }),
55
+ ),
56
+ script: Type.Optional(Type.String({ description: "Script body. Keep output small (log the finding, not the data)." })),
57
+ command: Type.Optional(Type.String({ description: "Alias for script; defaults to bash when runtime is omitted." })),
58
+ cmd: Type.Optional(Type.String({ description: "Short alias for script." })),
59
+ code: Type.Optional(Type.String({ description: "Alias for script, useful for node/python snippets." })),
60
+ expected: Type.Optional(Type.String({ description: "Expected outcome for this UAT check." })),
61
+ timeout_ms: Type.Optional(
62
+ Type.Number({
63
+ description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
64
+ minimum: 1_000,
65
+ maximum: 600_000,
66
+ }),
67
+ ),
68
+ }),
69
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
70
+ const { executeUatExec } = await import("../tools/exec-tool.js");
71
+ const baseDir = resolveCtxCwd(_ctx);
72
+ return executeUatExec(params as Parameters<typeof executeUatExec>[0], {
73
+ baseDir,
74
+ preferences: await loadContextModePreferences(baseDir),
75
+ });
76
+ },
77
+ });
78
+
28
79
  pi.registerTool({
29
80
  name: "gsd_exec",
30
81
  label: "Exec (Sandboxed)",
@@ -36,7 +36,9 @@ import { resolveSkillManifest } from "../skill-manifest.js";
36
36
  import { applyUnitSkillVisibility, unitHasSkillManifest } from "../skill-scope.js";
37
37
  import { getGuidedUnitContext } from "../guided-unit-context.js";
38
38
  import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-recovery.js";
39
- import { AUTO_UNIT_SCOPED_TOOLS, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
39
+ import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
40
+ import { filterToolsForProvider } from "../model-router.js";
41
+ import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
40
42
 
41
43
  let approvalQuestionAbortInFlight = false;
42
44
 
@@ -123,6 +125,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
123
125
  "gsd_resume",
124
126
  "gsd_milestone_status",
125
127
  "gsd_checkpoint_db",
128
+ "gsd_plan_milestone",
126
129
  "memory_query",
127
130
  "capture_thought",
128
131
  ] as const;
@@ -226,6 +229,9 @@ export function buildMinimalAutoGsdToolSet(
226
229
  unitType: string | undefined,
227
230
  registeredToolNames: readonly string[] = activeToolNames,
228
231
  ): string[] {
232
+ if (unitType === "run-uat") {
233
+ return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
234
+ }
229
235
  const unitTools = unitType ? AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [] : [];
230
236
  const autoBaseTools = new Set<string>(MINIMAL_AUTO_BASE_TOOL_NAMES);
231
237
  const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
@@ -240,6 +246,17 @@ export function buildMinimalAutoGsdToolSet(
240
246
  return withPreservedShimTools([...new Set([...preserved, ...scoped])]);
241
247
  }
242
248
 
249
+ export function buildRunUatGsdToolSet(
250
+ activeToolNames: readonly string[],
251
+ registeredToolNames: readonly string[] = activeToolNames,
252
+ ): string[] {
253
+ const scoped = resolveScopedToolNames(
254
+ [...activeToolNames, ...registeredToolNames],
255
+ [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
256
+ );
257
+ return [...new Set(scoped)];
258
+ }
259
+
243
260
  export function buildMinimalGsdWorkflowToolSet(
244
261
  activeToolNames: readonly string[],
245
262
  registeredToolNames: readonly string[] = activeToolNames,
@@ -1022,9 +1039,8 @@ export function registerHooks(
1022
1039
  if (result.block) return result;
1023
1040
  });
1024
1041
 
1025
- // ── Safety harness: evidence collection + destructive command warnings ──
1042
+ // ── Safety harness: evidence collection + destructive command blocking ──
1026
1043
  pi.on("tool_call", async (event, ctx) => {
1027
- if (!isAutoActive()) return;
1028
1044
  markToolStart(event.toolCallId, event.toolName);
1029
1045
  safetyRecordToolCall(event.toolCallId, event.toolName, event.input as Record<string, unknown>);
1030
1046
 
@@ -1041,17 +1057,28 @@ export function registerHooks(
1041
1057
  }
1042
1058
  }
1043
1059
 
1044
- // Destructive command classification (warn only, never block)
1060
+ // Destructive command classification + hard gate in all modes.
1045
1061
  if (isToolCallEventType("bash", event)) {
1046
1062
  const classification = classifyCommand(event.input.command);
1047
1063
  if (classification.destructive) {
1064
+ const reason = [
1065
+ "HARD BLOCK: destructive Bash command requires explicit human confirmation.",
1066
+ `Detected: ${classification.labels.join(", ")}`,
1067
+ "Run this via ask_user_questions, wait for the user's response,",
1068
+ "then issue the command only when confirmed in the current turn.",
1069
+ ].join(" ");
1048
1070
  safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
1049
1071
  command: String(event.input.command).slice(0, 200),
1050
1072
  });
1051
- ctx.ui.notify(
1052
- `Destructive command detected: ${classification.labels.join(", ")}`,
1053
- "warning",
1054
- );
1073
+ if (ctx) {
1074
+ await maybePauseAutoForApprovalGate(
1075
+ ctx,
1076
+ pi,
1077
+ isAutoActive(),
1078
+ "Depth confirmation is waiting for your answer — pausing auto-mode.",
1079
+ );
1080
+ }
1081
+ return { block: true, reason };
1055
1082
  }
1056
1083
  }
1057
1084
  });
@@ -1320,19 +1347,27 @@ export function registerHooks(
1320
1347
  const fullToolsRequested = isFullGsdToolSurfaceRequested();
1321
1348
  const dropAliases = !fullToolsRequested;
1322
1349
  const dropBrowser = !fullToolsRequested && !isBrowserToolSurfaceRequested();
1323
- const providerCompatible = compatible.filter(
1324
- (name) => !(dropAliases && isWorkflowAliasTool(name)) && !(dropBrowser && isBrowserTool(name)),
1350
+ const aliasFilteredCompatible = compatible.filter(
1351
+ (name) => !(dropAliases && isWorkflowAliasTool(name)),
1352
+ );
1353
+ const providerCompatible = aliasFilteredCompatible.filter(
1354
+ (name) => !(dropBrowser && isBrowserTool(name)),
1325
1355
  );
1326
1356
  const surfaceReduced = providerCompatible.length !== compatible.length;
1327
1357
  if (fullToolsRequested) {
1328
1358
  return surfaceReduced ? { toolNames: providerCompatible } : undefined;
1329
1359
  }
1330
1360
  const registeredToolNames = resolveRegisteredToolNames(pi, event.activeToolNames);
1361
+ const compatibleRegisteredToolNames = filterToolsForProvider(
1362
+ registeredToolNames,
1363
+ event.selectedModelApi,
1364
+ event.selectedModelProvider,
1365
+ ).compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)));
1331
1366
  const guidedUnit = getGuidedUnitContext();
1332
1367
  const requestScoped = buildRequestScopedGsdToolSet(
1333
- providerCompatible,
1368
+ guidedUnit?.unitType === "run-uat" ? aliasFilteredCompatible : providerCompatible,
1334
1369
  event.requestCustomMessages,
1335
- registeredToolNames,
1370
+ guidedUnit?.unitType === "run-uat" ? compatibleRegisteredToolNames : registeredToolNames,
1336
1371
  guidedUnit?.unitType,
1337
1372
  );
1338
1373
  if (requestScoped) {
@@ -1342,9 +1377,11 @@ export function registerHooks(
1342
1377
  if (dash.active && dash.currentUnit) {
1343
1378
  return {
1344
1379
  toolNames: buildMinimalAutoGsdToolSet(
1345
- providerCompatible,
1380
+ dash.currentUnit.type === "run-uat" ? aliasFilteredCompatible : providerCompatible,
1346
1381
  dash.currentUnit.type,
1347
- resolveRegisteredToolNames(pi, event.activeToolNames),
1382
+ dash.currentUnit.type === "run-uat"
1383
+ ? compatibleRegisteredToolNames
1384
+ : resolveRegisteredToolNames(pi, event.activeToolNames),
1348
1385
  ),
1349
1386
  };
1350
1387
  }
@@ -679,6 +679,7 @@ const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
679
679
  * manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
680
680
  */
681
681
  const PLANNING_DISPATCH_AGENT_REGISTRY = {
682
+ mnemo: { readOnlySpecialist: true },
682
683
  scout: { readOnlySpecialist: true },
683
684
  planner: { readOnlySpecialist: true },
684
685
  reviewer: { readOnlySpecialist: true },
@@ -692,7 +693,7 @@ export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set<string>(
692
693
  .map(([agentId]) => agentId),
693
694
  );
694
695
 
695
- let warnedMissingPlanningDispatchAgentClasses = false;
696
+ let warnedMissingControlledDispatchAgentClasses = false;
696
697
 
697
698
  function isReadOnlySpecialist(agentId: string): boolean {
698
699
  const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId as keyof typeof PLANNING_DISPATCH_AGENT_REGISTRY];
@@ -703,11 +704,20 @@ function allowedPlanningDispatchAgentsList(): string {
703
704
  return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
704
705
  }
705
706
 
706
- function warnMissingPlanningDispatchAgentClasses(unitType: string, mode: string, toolName: string): void {
707
- if (warnedMissingPlanningDispatchAgentClasses) return;
708
- warnedMissingPlanningDispatchAgentClasses = true;
707
+ function allowsControlledSubagentDispatch(
708
+ policy: ToolsPolicy,
709
+ ): policy is ToolsPolicy & { readonly allowedSubagents: readonly string[] } {
710
+ return (
711
+ (policy.mode === "planning-dispatch" || policy.mode === "verification") &&
712
+ Array.isArray((policy as { readonly allowedSubagents?: unknown }).allowedSubagents)
713
+ );
714
+ }
715
+
716
+ function warnMissingControlledDispatchAgentClasses(unitType: string, mode: string, toolName: string): void {
717
+ if (warnedMissingControlledDispatchAgentClasses) return;
718
+ warnedMissingControlledDispatchAgentClasses = true;
709
719
  // TODO(#5060): Remove this migration shim once all subagent/task callers are verified to forward agent identities.
710
- const message = `[write-gate] planning-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
720
+ const message = `[write-gate] controlled-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
711
721
  `on unit "${unitType}" without agentClasses - stale caller; blocking dispatch.`;
712
722
  console.warn(message);
713
723
  logWarning("intercept", message, {
@@ -777,8 +787,9 @@ function blockReason(unitType: string, mode: string, what: string): string {
777
787
  * - "docs" → like "planning" but also allows writes to paths
778
788
  * matching `allowedPathGlobs` relative to basePath.
779
789
  * - "verification"
780
- * → allows Bash for project verification commands, but keeps
781
- * writes restricted to .gsd/ and blocks subagent dispatch.
790
+ * → allows Bash for project verification commands, keeps
791
+ * writes restricted to .gsd/, and permits subagent dispatch
792
+ * only when the manifest declares allowedSubagents.
782
793
  *
783
794
  * `pathOrCommand` is the file path for write/edit-shaped tools and the
784
795
  * shell command for bash. Other tools ignore this argument.
@@ -825,7 +836,7 @@ export function shouldBlockPlanningUnit(
825
836
  if (tool.startsWith("gsd_")) return { block: false };
826
837
 
827
838
  if (PLANNING_SUBAGENT_TOOLS.has(tool)) {
828
- if (policy.mode === "planning-dispatch") {
839
+ if (allowsControlledSubagentDispatch(policy)) {
829
840
  const requested = (agentClasses ?? []).map(a => a.trim()).filter(Boolean);
830
841
  const dispatchContract = compileSubagentPermissionContract(policy);
831
842
  const allowedSubagents = dispatchContract.allowedSubagents;
@@ -834,7 +845,7 @@ export function shouldBlockPlanningUnit(
834
845
  // agent identities yet. Block and warn so stale callers surface in telemetry
835
846
  // instead of silently bypassing the gate.
836
847
  if (agentClasses === undefined) {
837
- warnMissingPlanningDispatchAgentClasses(unitType, policy.mode, tool);
848
+ warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
838
849
  return {
839
850
  block: true,
840
851
  reason: blockReason(
@@ -857,7 +868,7 @@ export function shouldBlockPlanningUnit(
857
868
  reason: blockReason(
858
869
  unitType,
859
870
  policy.mode,
860
- `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from planning-dispatch units`,
871
+ `subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
861
872
  ),
862
873
  };
863
874
  }
@@ -14,7 +14,7 @@ export interface GsdCommandDefinition {
14
14
  type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
15
15
 
16
16
  export const GSD_COMMAND_DESCRIPTION =
17
- "GSD — Git Ship Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|report|queue|quick|discuss|capture|triage|dispatch|verdict|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|closeout|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|memory|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|upgrade|fast|mcp|rethink|workflow|codebase|notifications|ship|do|usage|context|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
17
+ "GSD — Git Ship Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|report|queue|quick|discuss|capture|triage|dispatch|verdict|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|closeout|rebuild|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|memory|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|upgrade|fast|mcp|rethink|workflow|codebase|notifications|ship|do|usage|context|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
18
18
 
19
19
  export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
20
20
  { cmd: "help", desc: "Categorized command reference with descriptions" },
@@ -44,6 +44,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
44
44
  { cmd: "export", desc: "Alias for /gsd report" },
45
45
  { cmd: "cleanup", desc: "Remove merged branches or snapshots" },
46
46
  { cmd: "closeout", desc: "Recover failed git closeout actions (status, retry, resolve)" },
47
+ { cmd: "rebuild", desc: "Rebuild markdown projections from the canonical DB" },
47
48
  { cmd: "model", desc: "Switch the active session model or open a picker" },
48
49
  { cmd: "mode", desc: "Switch workflow mode (solo/team)" },
49
50
  { cmd: "prefs", desc: "Manage preferences (model selection, timeouts, etc.)" },
@@ -214,6 +215,10 @@ const NESTED_COMPLETIONS: CompletionMap = {
214
215
  { cmd: "retry", desc: "Retry the latest failed closeout git action" },
215
216
  { cmd: "resolve", desc: "Mark closeout resolved after the worktree is clean" },
216
217
  ],
218
+ rebuild: [
219
+ { cmd: "markdown", desc: "Rebuild markdown projections from the canonical DB" },
220
+ { cmd: "database", desc: "Reserved for DB-native rebuilds; does not import markdown" },
221
+ ],
217
222
  knowledge: [
218
223
  { cmd: "rule", desc: "Add a project rule (always/never do X)" },
219
224
  { cmd: "pattern", desc: "Add a code pattern to follow" },
@@ -56,6 +56,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
56
56
  " /gsd keys API key manager (LLM + tool keys)",
57
57
  " /gsd doctor Diagnose and repair .gsd/ state",
58
58
  " /gsd closeout Recover failed git closeout actions",
59
+ " /gsd rebuild Rebuild markdown projections from the DB [markdown]",
59
60
  "",
60
61
  "Use /gsd help full for the complete command reference.",
61
62
  ];
@@ -74,7 +75,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
74
75
  " /gsd new-milestone Create milestone from headless context (used by gsd headless)",
75
76
  " /gsd new-project Bootstrap a new project (use --deep for staged project-level discovery)",
76
77
  " /gsd quick Execute a quick task without full planning overhead",
77
- " /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|uat|replan]",
78
+ " /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|validate|reassess|uat|replan]",
78
79
  " /gsd verdict <v> Override milestone validation verdict [pass|needs-attention|needs-remediation] [--milestone Mxxx] [--rationale \"...\"]",
79
80
  " /gsd parallel Parallel milestone orchestration [start|status|stop|pause|resume|merge|watch]",
80
81
  " /gsd workflow Custom workflow lifecycle [new|run|list|validate|pause|resume]",
@@ -138,7 +139,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
138
139
  " /gsd skill-health Skill lifecycle dashboard",
139
140
  " /gsd extensions Manage extensions [list|enable|disable|info]",
140
141
  " /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
141
- " /gsd mcp MCP server management [status|check|test|enable|disable|import|delete|init]",
142
+ " /gsd mcp MCP server management [status|check|discover|test|enable|disable|import|delete|init]",
142
143
  "",
143
144
  "MAINTENANCE",
144
145
  " /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
@@ -147,6 +148,9 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
147
148
  " /gsd export Alias for /gsd report",
148
149
  " /gsd cleanup Remove merged branches or snapshots [branches|snapshots]",
149
150
  " /gsd closeout Recover failed git closeout actions [status|retry|resolve] [unit-id]",
151
+ " /gsd rebuild markdown Rebuild markdown projections from the canonical DB",
152
+ " /gsd rebuild database Reserved for DB-native rebuilds; does not import markdown",
153
+ " /gsd recover --confirm Import markdown into the DB after DB loss/corruption",
150
154
  " /gsd worktree Manage worktrees from the TUI [list|merge|clean|remove]",
151
155
  " /gsd migrate Migrate .planning/ (v1) to DB-backed .gsd/ with backup + audit",
152
156
  " /gsd remote Control remote auto-mode [slack|discord|status|disconnect]",
@@ -9,7 +9,7 @@ import { handleDoctor, handleCapture, handleKnowledge, handleRunHook, handleSkil
9
9
  import { handleInspect } from "../../commands-inspect.js";
10
10
  import { handleLogs } from "../../commands-logs.js";
11
11
  import { handleDebug } from "../../commands-debug.js";
12
- import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleCleanupProjects, handleCleanupWorktrees, handleRecover } from "../../commands-maintenance.js";
12
+ import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleCleanupProjects, handleCleanupWorktrees, handleRecover, handleRebuild } from "../../commands-maintenance.js";
13
13
  import { handleExport } from "../../export.js";
14
14
  import { handleHistory } from "../../history.js";
15
15
  import { handleUndo } from "../../undo.js";
@@ -146,8 +146,12 @@ export async function handleOpsCommand(trimmed: string, ctx: ExtensionCommandCon
146
146
  await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
147
147
  return true;
148
148
  }
149
- if (trimmed === "recover") {
150
- await handleRecover(ctx, projectRoot());
149
+ if (trimmed === "recover" || trimmed.startsWith("recover ")) {
150
+ await handleRecover(ctx, projectRoot(), trimmed.replace(/^recover\s*/, "").trim());
151
+ return true;
152
+ }
153
+ if (trimmed === "rebuild" || trimmed.startsWith("rebuild ")) {
154
+ await handleRebuild(ctx, projectRoot(), trimmed.replace(/^rebuild\s*/, "").trim());
151
155
  return true;
152
156
  }
153
157
  if (trimmed === "closeout" || trimmed.startsWith("closeout ")) {
@@ -1,11 +1,14 @@
1
1
  /**
2
2
  * GSD Maintenance — cleanup, skip, dry-run, and recover handlers.
3
3
  *
4
- * Contains: handleCleanupBranches, handleCleanupSnapshots, handleCleanupWorktrees, handleSkip, handleDryRun, handleRecover
4
+ * Contains: handleCleanupBranches, handleCleanupSnapshots, handleCleanupWorktrees, handleSkip, handleDryRun, handleRecover, handleRebuild
5
5
  */
6
6
 
7
7
  import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
8
+ import { existsSync, mkdirSync, renameSync } from "node:fs";
9
+ import { dirname, isAbsolute, join, relative } from "node:path";
8
10
  import { deriveState } from "./state.js";
11
+ import { gsdProjectionRoot, gsdRoot } from "./paths.js";
9
12
  import { nativeBranchList, nativeDetectMainBranch, nativeBranchListMerged, nativeBranchDelete, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
10
13
  import { logWarning } from "./workflow-logger.js";
11
14
 
@@ -478,6 +481,39 @@ export async function handleCleanupProjects(args: string, ctx: ExtensionCommandC
478
481
  ctx.ui.notify(lines.join("\n"), "info");
479
482
  }
480
483
 
484
+ function recoverConfirmed(args: string): boolean {
485
+ return args
486
+ .split(/\s+/)
487
+ .map((part) => part.trim().toLowerCase())
488
+ .some((part) => part === "--confirm" || part === "--yes" || part === "confirm");
489
+ }
490
+
491
+ async function confirmRecover(ctx: ExtensionCommandContext, args: string): Promise<boolean> {
492
+ if (recoverConfirmed(args)) return true;
493
+
494
+ const warning = [
495
+ "gsd recover imports markdown into the database.",
496
+ "It clears and reconstructs milestone, slice, and task hierarchy rows from rendered markdown.",
497
+ "Use /gsd rebuild markdown for normal DB-to-markdown realignment.",
498
+ ].join("\n");
499
+
500
+ if (typeof ctx.ui.confirm === "function") {
501
+ const confirmed = await ctx.ui.confirm(
502
+ "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.`,
504
+ );
505
+ if (confirmed) return true;
506
+ ctx.ui.notify("gsd recover cancelled. No database changes made.", "info");
507
+ return false;
508
+ }
509
+
510
+ ctx.ui.notify(
511
+ `${warning}\n\nNo database changes made. Re-run /gsd recover --confirm to proceed.`,
512
+ "warning",
513
+ );
514
+ return false;
515
+ }
516
+
481
517
  /**
482
518
  * `gsd recover` — Reconstruct DB hierarchy state from rendered markdown on disk.
483
519
  *
@@ -487,7 +523,7 @@ export async function handleCleanupProjects(args: string, ctx: ExtensionCommandC
487
523
  *
488
524
  * Prints counts of recovered items and the resulting project phase.
489
525
  */
490
- export async function handleRecover(ctx: ExtensionCommandContext, basePath: string): Promise<void> {
526
+ export async function handleRecover(ctx: ExtensionCommandContext, basePath: string, args = ""): Promise<void> {
491
527
  const { isDbAvailable: dbAvailable, clearEngineHierarchy, transaction: dbTransaction } = await import("./gsd-db.js");
492
528
  const { migrateHierarchyToDb } = await import("./md-importer.js");
493
529
  const { invalidateStateCache } = await import("./state.js");
@@ -497,6 +533,8 @@ export async function handleRecover(ctx: ExtensionCommandContext, basePath: stri
497
533
  return;
498
534
  }
499
535
 
536
+ if (!(await confirmRecover(ctx, args))) return;
537
+
500
538
  try {
501
539
  // 1. Delete + re-populate inside a single transaction for atomicity.
502
540
  // clearEngineHierarchy() uses transaction() internally but transaction()
@@ -542,3 +580,160 @@ export async function handleRecover(ctx: ExtensionCommandContext, basePath: stri
542
580
  ctx.ui.notify(`gsd recover failed: ${msg}`, "error");
543
581
  }
544
582
  }
583
+
584
+ function normalizeArtifactPath(value: string): string {
585
+ return value.replace(/\\/g, "/");
586
+ }
587
+
588
+ function pathWithin(root: string, candidate: string): boolean {
589
+ const rel = relative(root, candidate);
590
+ return rel.length === 0 || (!rel.startsWith("..") && !isAbsolute(rel));
591
+ }
592
+
593
+ function artifactPathForDb(basePath: string, absPath: string): string {
594
+ const projectionRoot = gsdProjectionRoot(basePath);
595
+ const root = pathWithin(projectionRoot, absPath) ? projectionRoot : gsdRoot(basePath);
596
+ return normalizeArtifactPath(relative(root, absPath));
597
+ }
598
+
599
+ function quarantineRelativePath(basePath: string, absPath: string): string {
600
+ for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
601
+ if (pathWithin(root, absPath)) {
602
+ return normalizeArtifactPath(relative(root, absPath));
603
+ }
604
+ }
605
+ return normalizeArtifactPath(absPath.replace(/^[/\\]+/, ""));
606
+ }
607
+
608
+ function uniquePath(path: string): string {
609
+ if (!existsSync(path)) return path;
610
+ let idx = 2;
611
+ while (existsSync(`${path}.${idx}`)) idx++;
612
+ return `${path}.${idx}`;
613
+ }
614
+
615
+ function resolveDiskArtifactPath(basePath: string, artifactPath: string): string {
616
+ if (isAbsolute(artifactPath)) return artifactPath;
617
+ const candidates = [
618
+ join(gsdProjectionRoot(basePath), artifactPath),
619
+ join(gsdRoot(basePath), artifactPath),
620
+ ];
621
+ return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0]!;
622
+ }
623
+
624
+ function quarantineProjectionFile(basePath: string, absPath: string, stamp: string): string {
625
+ const rel = quarantineRelativePath(basePath, absPath);
626
+ const target = uniquePath(join(gsdProjectionRoot(basePath), "quarantine", "projections", stamp, rel));
627
+ mkdirSync(dirname(target), { recursive: true });
628
+ renameSync(absPath, target);
629
+ return target;
630
+ }
631
+
632
+ type RebuildTarget = "markdown" | "database" | "usage";
633
+
634
+ function parseRebuildTarget(args: string): RebuildTarget {
635
+ const trimmed = args.trim().toLowerCase();
636
+ if (!trimmed || trimmed === "markdown") return "markdown";
637
+ if (trimmed === "database" || trimmed === "db") return "database";
638
+ return "usage";
639
+ }
640
+
641
+ /**
642
+ * `gsd rebuild markdown` — Re-render markdown projections from the authoritative DB.
643
+ *
644
+ * This is the DB-first realignment command. It does not import markdown into
645
+ * the DB. Completion SUMMARY files that contradict open DB rows are preserved
646
+ * under `.gsd/quarantine/projections/` before DB projections are rendered.
647
+ */
648
+ export async function handleRebuild(ctx: ExtensionCommandContext, basePath: string, args = ""): Promise<void> {
649
+ const { isDbAvailable: dbAvailable, deleteArtifactByPath } = await import("./gsd-db.js");
650
+ const { detectArtifactDbDrift } = await import("./state-reconciliation/drift/artifact-db.js");
651
+ const { renderAllFromDb } = await import("./markdown-renderer.js");
652
+ const { invalidateStateCache } = await import("./state.js");
653
+
654
+ const target = parseRebuildTarget(args);
655
+ if (target === "usage") {
656
+ ctx.ui.notify(
657
+ [
658
+ "Usage:",
659
+ " /gsd rebuild markdown Rebuild markdown projections from the canonical DB",
660
+ " /gsd rebuild database Reserved for DB-native rebuilds; does not import markdown",
661
+ ].join("\n"),
662
+ "warning",
663
+ );
664
+ return;
665
+ }
666
+
667
+ if (target === "database") {
668
+ ctx.ui.notify(
669
+ [
670
+ "gsd rebuild database is reserved for DB-native rebuilds.",
671
+ "It will not import markdown projections into the DB.",
672
+ "For normal realignment, run /gsd rebuild markdown.",
673
+ "If the DB is lost or corrupt and markdown is the source to import, run /gsd recover --confirm.",
674
+ ].join("\n"),
675
+ "warning",
676
+ );
677
+ return;
678
+ }
679
+
680
+ if (!dbAvailable()) {
681
+ ctx.ui.notify("gsd rebuild markdown: No database open. Run a GSD command first to initialize the DB.", "error");
682
+ return;
683
+ }
684
+
685
+ try {
686
+ invalidateStateCache();
687
+ const state = await deriveState(basePath);
688
+ const drifts = detectArtifactDbDrift(state, { basePath, state });
689
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
690
+ const quarantined: string[] = [];
691
+ const seen = new Set<string>();
692
+
693
+ for (const drift of drifts) {
694
+ if (drift.kind !== "artifact-db-status-divergence") continue;
695
+ if (drift.artifactType !== "SUMMARY" || !drift.artifactPath) continue;
696
+ const absPath = resolveDiskArtifactPath(basePath, drift.artifactPath);
697
+ if (seen.has(absPath) || !existsSync(absPath)) continue;
698
+ seen.add(absPath);
699
+ const artifactDbPath = artifactPathForDb(basePath, absPath);
700
+ const target = quarantineProjectionFile(basePath, absPath, stamp);
701
+ deleteArtifactByPath(artifactDbPath);
702
+ quarantined.push(target);
703
+ }
704
+
705
+ const rendered = await renderAllFromDb(basePath);
706
+ invalidateStateCache();
707
+
708
+ const lines = [
709
+ "gsd rebuild markdown: rebuilt markdown projections from the canonical DB",
710
+ ` Rendered: ${rendered.rendered}`,
711
+ ` Skipped: ${rendered.skipped}`,
712
+ ` Quarantined: ${quarantined.length}`,
713
+ ];
714
+ if (rendered.errors.length > 0) {
715
+ lines.push(` Errors: ${rendered.errors.length}`);
716
+ for (const err of rendered.errors.slice(0, 5)) {
717
+ lines.push(` - ${err}`);
718
+ }
719
+ if (rendered.errors.length > 5) {
720
+ lines.push(` - ${rendered.errors.length - 5} more`);
721
+ }
722
+ }
723
+ if (quarantined.length > 0) {
724
+ lines.push("", " Quarantine:");
725
+ for (const target of quarantined.slice(0, 5)) {
726
+ lines.push(` - ${target}`);
727
+ }
728
+ if (quarantined.length > 5) {
729
+ lines.push(` - ${quarantined.length - 5} more`);
730
+ }
731
+ }
732
+
733
+ ctx.ui.notify(lines.join("\n"), rendered.errors.length > 0 ? "warning" : "success");
734
+ } catch (err) {
735
+ const msg = err instanceof Error ? err.message : String(err);
736
+ logWarning("command", `rebuild failed: ${msg}`);
737
+ ctx.ui.notify(`gsd rebuild failed: ${msg}`, "error");
738
+ }
739
+ }