@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
@@ -394,6 +394,50 @@ function stripKnownIdPrefix(value: string | undefined | null, id: string): strin
394
394
  return raw;
395
395
  }
396
396
 
397
+ function parseReactiveBatchTaskIds(unitId: string): string[] {
398
+ const { task: batchPart } = parseUnitId(unitId);
399
+ if (!batchPart?.startsWith("reactive+")) return [];
400
+
401
+ const rawIds = batchPart
402
+ .slice("reactive+".length)
403
+ .split(",")
404
+ .map((taskId) => taskId.trim().toUpperCase())
405
+ .filter(Boolean);
406
+
407
+ const unique = new Set<string>();
408
+ for (const taskId of rawIds) {
409
+ unique.add(taskId);
410
+ }
411
+ return [...unique];
412
+ }
413
+
414
+ function dedupePaths(values: string[]): string[] {
415
+ const seen = new Set<string>();
416
+ const result: string[] = [];
417
+ for (const value of values) {
418
+ if (!seen.has(value)) {
419
+ seen.add(value);
420
+ result.push(value);
421
+ }
422
+ }
423
+ return result;
424
+ }
425
+
426
+ function getPlannedKeyFiles(tasks: Array<
427
+ { expected_output?: string[]; files?: string[]; key_files?: string[] }
428
+ >): string[] {
429
+ return dedupePaths(
430
+ tasks.flatMap((taskRow) => [
431
+ ...(taskRow.expected_output ?? []),
432
+ ...(taskRow.files ?? []),
433
+ ...(taskRow.key_files ?? []),
434
+ ]),
435
+ );
436
+ }
437
+
438
+ export const _parseReactiveBatchTaskIdsForTest = parseReactiveBatchTaskIds;
439
+ export const _getPlannedKeyFilesForTest = getPlannedKeyFiles;
440
+
397
441
  function resolveVerificationFailureMarkerPath(
398
442
  unitType: string,
399
443
  unitId: string,
@@ -481,6 +525,40 @@ async function buildTaskCommitContextForUnit(
481
525
  };
482
526
  }
483
527
 
528
+ async function buildReactiveTaskCommitContext(
529
+ _basePath: string,
530
+ unitId: string,
531
+ ): Promise<TaskCommitContext | undefined> {
532
+ const { milestone: mid, slice: sid } = parseUnitId(unitId);
533
+ if (!mid || !sid || !isDbAvailable()) return undefined;
534
+
535
+ const batchTaskIds = parseReactiveBatchTaskIds(unitId);
536
+ if (batchTaskIds.length === 0) return undefined;
537
+
538
+ const milestone = getMilestone(mid);
539
+ const slice = getSlice(mid, sid);
540
+ const taskRows = batchTaskIds
541
+ .map((tid) => getTask(mid, sid, tid))
542
+ .filter((taskRow): taskRow is NonNullable<ReturnType<typeof getTask>> => taskRow !== null);
543
+
544
+ const keyFiles = getPlannedKeyFiles(taskRows);
545
+ if (taskRows.length === 0 || keyFiles.length === 0) return undefined;
546
+
547
+ const taskLabel = taskRows.map((row) => row.id).join(",");
548
+
549
+ return {
550
+ taskId: `${sid}/${taskLabel}`,
551
+ taskDisplayId: "reactive-batch",
552
+ taskTitle: `Reactive batch: ${taskLabel}`,
553
+ milestoneId: mid,
554
+ milestoneTitle: stripKnownIdPrefix(milestone?.title, mid),
555
+ sliceId: sid,
556
+ sliceTitle: stripKnownIdPrefix(slice?.title, sid),
557
+ oneLiner: `Reactive execute for ${taskLabel}`,
558
+ keyFiles,
559
+ };
560
+ }
561
+
484
562
  async function runPostUnitGitHubSyncIfNeeded(
485
563
  basePath: string,
486
564
  unit: NonNullable<AutoSession["currentUnit"]>,
@@ -943,6 +1021,8 @@ export async function autoCommitUnit(
943
1021
 
944
1022
  if (unitType === "execute-task") {
945
1023
  taskContext = await buildTaskCommitContextForUnit(basePath, unitId);
1024
+ } else if (unitType === "reactive-execute") {
1025
+ taskContext = await buildReactiveTaskCommitContext(basePath, unitId);
946
1026
  }
947
1027
 
948
1028
  _resetHasChangesCache();
@@ -1005,6 +1085,21 @@ async function runCloseoutGitAction(
1005
1085
  if (mid && sid && tid && isDbAvailable()) {
1006
1086
  targetRepositories = getTask(mid, sid, tid)?.target_repositories;
1007
1087
  }
1088
+ } else if (turnAction === "commit" && unit.type === "reactive-execute") {
1089
+ taskContext = await buildReactiveTaskCommitContext(s.basePath, unit.id);
1090
+ const { milestone: mid, slice: sid } = parseUnitId(unit.id);
1091
+ if (mid && sid && isDbAvailable()) {
1092
+ const repositories = new Set<string>();
1093
+ for (const tid of parseReactiveBatchTaskIds(unit.id)) {
1094
+ const taskRow = getTask(mid, sid, tid);
1095
+ for (const repoId of taskRow?.target_repositories ?? []) {
1096
+ repositories.add(repoId);
1097
+ }
1098
+ }
1099
+ if (repositories.size > 0) {
1100
+ targetRepositories = [...repositories];
1101
+ }
1102
+ }
1008
1103
  }
1009
1104
 
1010
1105
  // Invalidate the nativeHasChanges cache before auto-commit (#1853).
@@ -1436,12 +1531,24 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
1436
1531
  const { milestone: sMid, slice: sSid, task: sTid } = parseUnitId(s.currentUnit.id);
1437
1532
 
1438
1533
  // File change validation (execute-task only, after unit execution)
1439
- if (safetyConfig.file_change_validation && s.currentUnit.type === "execute-task" && sMid && sSid && sTid && isDbAvailable()) {
1534
+ if (safetyConfig.file_change_validation && s.currentUnit.type === "execute-task" && sMid && sSid && sTid) {
1440
1535
  try {
1441
- const taskRow = getTask(sMid, sSid, sTid);
1442
- if (taskRow) {
1443
- const expectedOutput = taskRow.expected_output ?? [];
1444
- const plannedFiles = taskRow.files ?? [];
1536
+ const sliceTaskRows = isDbAvailable()
1537
+ ? getSliceTasks(sMid, sSid).filter((t) => isClosedStatus(t.status) || t.id === sTid)
1538
+ : [];
1539
+
1540
+ if (sliceTaskRows.length > 0) {
1541
+ const expectedOutput = getPlannedKeyFiles(
1542
+ sliceTaskRows.map((taskRow) => ({
1543
+ expected_output: taskRow.expected_output,
1544
+ files: taskRow.files,
1545
+ })),
1546
+ );
1547
+ const plannedFiles = getPlannedKeyFiles(
1548
+ sliceTaskRows.map((taskRow) => ({
1549
+ files: taskRow.files,
1550
+ })),
1551
+ );
1445
1552
  const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
1446
1553
  if (audit && audit.violations.length > 0) {
1447
1554
  const warnings = audit.violations.filter(v => v.severity === "warning");
@@ -1455,6 +1562,30 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
1455
1562
  );
1456
1563
  }
1457
1564
  }
1565
+ } else {
1566
+ const taskRow = getTask(sMid, sSid, sTid);
1567
+ if (taskRow) {
1568
+ const expectedOutput = taskRow.expected_output ?? [];
1569
+ const plannedFiles = taskRow.files ?? [];
1570
+ const audit = validateFileChanges(
1571
+ s.basePath,
1572
+ expectedOutput,
1573
+ plannedFiles,
1574
+ safetyConfig.file_change_allowlist,
1575
+ );
1576
+ if (audit && audit.violations.length > 0) {
1577
+ const warnings = audit.violations.filter(v => v.severity === "warning");
1578
+ for (const v of warnings) {
1579
+ logWarning("safety", `file-change: ${v.file} — ${v.reason}`);
1580
+ }
1581
+ if (warnings.length > 0) {
1582
+ ctx.ui.notify(
1583
+ `Safety: ${warnings.length} unexpected file change(s) outside task plan`,
1584
+ "warning",
1585
+ );
1586
+ }
1587
+ }
1588
+ }
1458
1589
  }
1459
1590
  } catch (e) {
1460
1591
  debugLog("postUnit", { phase: "safety-file-change", error: String(e) });
@@ -2134,8 +2265,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
2134
2265
  await renderPlanCheckboxes(s.canonicalProjectRoot, mid, sid);
2135
2266
  } catch (dbErr) {
2136
2267
  // DB unavailable — fail explicitly rather than silently reverting to markdown mutation.
2137
- // Use 'gsd recover' to rebuild DB state from disk if needed.
2138
- logError("engine", `retry state-reset failed (DB unavailable): ${(dbErr as Error).message}. Run 'gsd recover' to reconcile.`);
2268
+ // Use 'gsd recover --confirm' to import markdown into the DB if needed.
2269
+ logError("engine", `retry state-reset failed (DB unavailable): ${(dbErr as Error).message}. Run 'gsd recover --confirm' to import markdown into the DB.`);
2139
2270
  }
2140
2271
  }
2141
2272
 
@@ -2711,6 +2711,15 @@ export async function buildCompleteSlicePrompt(
2711
2711
  sliceSummaryPath,
2712
2712
  sliceUatPath,
2713
2713
  gatesToClose,
2714
+ skillActivation: buildSkillActivationBlock({
2715
+ base,
2716
+ milestoneId: mid,
2717
+ milestoneTitle: midTitle,
2718
+ sliceId: sid,
2719
+ sliceTitle: sTitle,
2720
+ extraContext: [inlinedContext],
2721
+ unitType: "complete-slice",
2722
+ }),
2714
2723
  });
2715
2724
  }
2716
2725
 
@@ -867,7 +867,7 @@ export function buildLoopRemediationSteps(
867
867
  return [
868
868
  ` 1. Run \`gsd undo-task ${mid}/${sid}/${tid}\` to reset the task state`,
869
869
  ` 2. Resume auto-mode — it will re-execute the task`,
870
- ` 3. If the task keeps failing, run \`gsd recover\` to rebuild DB state from disk`,
870
+ ` 3. If the task keeps failing and markdown should repopulate the DB, run \`gsd recover --confirm\``,
871
871
  ].join("\n");
872
872
  }
873
873
  case "plan-slice":
@@ -879,7 +879,7 @@ export function buildLoopRemediationSteps(
879
879
  : relSliceFile(base, mid, sid, "RESEARCH");
880
880
  return [
881
881
  ` 1. Write ${artifactRel} manually (or with the LLM in interactive mode)`,
882
- ` 2. Run \`gsd recover\` to rebuild DB state from disk`,
882
+ ` 2. Run \`gsd recover --confirm\` to import the markdown into the DB`,
883
883
  ` 3. Resume auto-mode`,
884
884
  ].join("\n");
885
885
  }
@@ -888,7 +888,7 @@ export function buildLoopRemediationSteps(
888
888
  return [
889
889
  ` 1. Run \`gsd reset-slice ${mid}/${sid}\` to reset the slice and all its tasks`,
890
890
  ` 2. Resume auto-mode — it will re-execute incomplete tasks and re-complete the slice`,
891
- ` 3. If the slice keeps failing, run \`gsd recover\` to rebuild DB state from disk`,
891
+ ` 3. If the slice keeps failing and markdown should repopulate the DB, run \`gsd recover --confirm\``,
892
892
  ].join("\n");
893
893
  }
894
894
  case "validate-milestone": {
@@ -896,7 +896,7 @@ export function buildLoopRemediationSteps(
896
896
  const artifactRel = relMilestoneFile(base, mid, "VALIDATION");
897
897
  return [
898
898
  ` 1. Write ${artifactRel} with verdict: pass`,
899
- ` 2. Run \`gsd recover\` to rebuild DB state from disk`,
899
+ ` 2. Run \`gsd recover --confirm\` to import the markdown into the DB`,
900
900
  ` 3. Resume auto-mode`,
901
901
  ].join("\n");
902
902
  }
@@ -60,12 +60,13 @@ import { getAutoWorktreePath, isInAutoWorktree, checkoutBranchWithStashGuard } f
60
60
  import { readResourceVersion, cleanStaleRuntimeUnits } from "./auto-worktree.js";
61
61
  import { worktreePath as getWorktreeDir, isInsideWorktreesDir } from "./worktree-manager.js";
62
62
  import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
63
+ import { queryJournal } from "./journal.js";
63
64
  import { initMetrics } from "./metrics.js";
64
65
  import { initRoutingHistory } from "./routing-history.js";
65
66
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
66
67
  import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
67
68
  import { snapshotSkills } from "./skill-discovery.js";
68
- import { isDbAvailable, getMilestone, getAllMilestones, insertMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
69
+ import { isDbAvailable, getMilestone, getAllMilestones, insertMilestone, openDatabase, getDbStatus, updateMilestoneStatus } from "./gsd-db.js";
69
70
  import { isClosedStatus } from "./status-guards.js";
70
71
  import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
71
72
  import { extractVerdict } from "./verdict-parser.js";
@@ -100,6 +101,7 @@ import {
100
101
  } from "./preferences-models.js";
101
102
  import type { WorktreeLifecycle } from "./worktree-lifecycle.js";
102
103
  import { getSessionModelOverride } from "./session-model-override.js";
104
+ import { setAutoActiveStatus } from "./auto-dashboard.js";
103
105
 
104
106
  export interface BootstrapDeps {
105
107
  shouldUseWorktreeIsolation: (basePath?: string) => boolean;
@@ -187,6 +189,40 @@ export function reconcileProjectMilestonesFromDisk(basePath: string): number {
187
189
  }
188
190
  }
189
191
 
192
+ export function reconcileMergedMilestonesFromJournal(basePath: string): number {
193
+ if (!isDbAvailable()) return 0;
194
+
195
+ const mergedAtByMilestone = new Map<string, string>();
196
+ for (const entry of queryJournal(basePath, { eventType: "worktree-merged" })) {
197
+ const data = entry.data ?? {};
198
+ const milestoneId = typeof data.milestoneId === "string" ? data.milestoneId : null;
199
+ if (!milestoneId) continue;
200
+ if (data.conflict === true) continue;
201
+
202
+ const endedAt = typeof data.endedAt === "string" ? data.endedAt : entry.ts;
203
+ const previous = mergedAtByMilestone.get(milestoneId);
204
+ if (!previous || endedAt > previous) mergedAtByMilestone.set(milestoneId, endedAt);
205
+ }
206
+
207
+ let closed = 0;
208
+ for (const [milestoneId, completedAt] of mergedAtByMilestone) {
209
+ const existing = getMilestone(milestoneId);
210
+ if (!existing) {
211
+ insertMilestone({ id: milestoneId, title: milestoneId, status: "complete" });
212
+ updateMilestoneStatus(milestoneId, "complete", completedAt);
213
+ closed++;
214
+ continue;
215
+ }
216
+ if (!isClosedStatus(existing.status)) {
217
+ updateMilestoneStatus(milestoneId, "complete", completedAt);
218
+ closed++;
219
+ }
220
+ }
221
+
222
+ if (closed > 0) invalidateAllCaches();
223
+ return closed;
224
+ }
225
+
190
226
  /**
191
227
  * Audit for orphaned milestone branches at bootstrap.
192
228
  *
@@ -258,6 +294,7 @@ export interface OrphanAuditAction {
258
294
  message: string;
259
295
  severity: "info" | "warning";
260
296
  branch?: string;
297
+ mainBranch?: string;
261
298
  commitsAhead?: number;
262
299
  dirtyWorktree?: boolean;
263
300
  worktreeDirExists?: boolean;
@@ -276,6 +313,27 @@ function isBlockingStrandedWorkAction(action: OrphanAuditAction): boolean {
276
313
  return action.kind === "in-progress-stranded-work" && action.blocksAuto;
277
314
  }
278
315
 
316
+ function strandedWorkEvidence(args: {
317
+ branch?: string;
318
+ commitsAhead: number;
319
+ mainBranch: string;
320
+ dirtyWorktree: boolean;
321
+ }): string[] {
322
+ const evidence: string[] = [];
323
+ if (args.branch && args.commitsAhead > 0) {
324
+ evidence.push(
325
+ `branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`,
326
+ );
327
+ }
328
+ if (args.dirtyWorktree) {
329
+ evidence.push("the worktree has uncommitted changes");
330
+ }
331
+ if (evidence.length === 0) {
332
+ evidence.push("physical git evidence exists");
333
+ }
334
+ return evidence;
335
+ }
336
+
279
337
  function detectWorktreeEvidence(
280
338
  basePath: string,
281
339
  milestoneId: string,
@@ -307,18 +365,7 @@ function strandedWorkMessage(args: {
307
365
  worktreeDirExists: boolean;
308
366
  recoveryMode: StrandedWorkRecoveryMode;
309
367
  }): string {
310
- const evidence: string[] = [];
311
- if (args.branch && args.commitsAhead > 0) {
312
- evidence.push(
313
- `branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`,
314
- );
315
- }
316
- if (args.dirtyWorktree) {
317
- evidence.push("the worktree has uncommitted changes");
318
- }
319
- if (evidence.length === 0) {
320
- evidence.push("physical git evidence exists");
321
- }
368
+ const evidence = strandedWorkEvidence(args);
322
369
 
323
370
  const wtSuffix = args.worktreeDirExists
324
371
  ? ` Worktree directory at .gsd/worktrees/${args.milestoneId}/ holds live work.`
@@ -334,6 +381,45 @@ function strandedWorkMessage(args: {
334
381
  );
335
382
  }
336
383
 
384
+ function formatStrandedWorkRecoveryMessage(action: OrphanAuditAction): string {
385
+ const recoveryMode = action.recoveryMode === "worktree"
386
+ ? "existing worktree"
387
+ : "milestone branch";
388
+ const evidence = strandedWorkEvidence({
389
+ branch: action.branch,
390
+ commitsAhead: action.commitsAhead ?? 0,
391
+ mainBranch: action.mainBranch ?? "main",
392
+ dirtyWorktree: action.dirtyWorktree ?? false,
393
+ });
394
+ const wtSuffix = action.worktreeDirExists
395
+ ? ` Worktree directory at .gsd/worktrees/${action.milestoneId}/ holds live work.`
396
+ : "";
397
+ return (
398
+ `Resuming saved milestone work for ${action.milestoneId}: ${evidence.join("; ")}.` +
399
+ wtSuffix +
400
+ ` Adopting the ${recoveryMode} before dispatching new units. Park or discard explicitly if abandoning.`
401
+ );
402
+ }
403
+
404
+ function formatStrandedWorkBlockerMessage(
405
+ action: OrphanAuditAction,
406
+ activeMilestoneId: string | null,
407
+ ): string {
408
+ const target = action.milestoneId;
409
+ const mode = action.recoveryMode === "worktree" ? "existing worktree" : "milestone branch";
410
+ const intro = activeMilestoneId
411
+ ? `Stranded work for ${target} blocks auto-mode before ${activeMilestoneId}.`
412
+ : `Stranded work for ${target} blocks auto-mode, but that milestone is not active in project state.`;
413
+
414
+ return [
415
+ intro,
416
+ "Choose one explicit next step:",
417
+ `1. Recover it: run \`/gsd auto ${target}\` to adopt the ${mode}.`,
418
+ `2. Defer it: run \`/gsd park ${target} "reason"\`, then rerun \`/gsd auto\`.`,
419
+ `3. Abandon it: run \`/gsd rethink\` and explicitly discard ${target}.`,
420
+ ].join("\n");
421
+ }
422
+
337
423
  export function auditOrphanedMilestoneBranches(
338
424
  basePath: string,
339
425
  _isolationMode: "worktree" | "branch" | "none",
@@ -435,6 +521,7 @@ export function auditOrphanedMilestoneBranches(
435
521
  kind: "in-progress-stranded-work",
436
522
  milestoneId,
437
523
  branch,
524
+ mainBranch,
438
525
  commitsAhead,
439
526
  dirtyWorktree: worktreeEvidence.dirty,
440
527
  worktreeDirExists: worktreeEvidence.dirExists,
@@ -589,6 +676,7 @@ export function auditOrphanedMilestoneBranches(
589
676
  pushAction({
590
677
  kind: "in-progress-stranded-work",
591
678
  milestoneId: m.id,
679
+ mainBranch,
592
680
  commitsAhead: 0,
593
681
  dirtyWorktree: true,
594
682
  worktreeDirExists: worktreeEvidence.dirExists,
@@ -1066,6 +1154,7 @@ export async function bootstrapAutoSession(
1066
1154
  await openProjectDbIfPresent(base);
1067
1155
  registerAutoWorkerForSession(base);
1068
1156
  reconcileProjectMilestonesFromDisk(base);
1157
+ reconcileMergedMilestonesFromJournal(base);
1069
1158
 
1070
1159
  // Clean stale runtime unit files for completed milestones (#887).
1071
1160
  // DB-authoritative: when DB is available, require DB status to be closed
@@ -1103,7 +1192,13 @@ export async function bootstrapAutoSession(
1103
1192
  for (const msg of auditResult.recovered) {
1104
1193
  ctx.ui.notify(`Orphan audit: ${msg}`, "info");
1105
1194
  }
1195
+ const deferredStrandedMessages = new Set(
1196
+ auditResult.actions
1197
+ .filter(isBlockingStrandedWorkAction)
1198
+ .map((action) => action.message),
1199
+ );
1106
1200
  for (const msg of auditResult.warnings) {
1201
+ if (deferredStrandedMessages.has(msg)) continue;
1107
1202
  const prefix = msg.startsWith("Stranded work") ? "" : "Orphan audit: ";
1108
1203
  ctx.ui.notify(`${prefix}${msg}`, "warning");
1109
1204
  }
@@ -1177,21 +1272,21 @@ export async function bootstrapAutoSession(
1177
1272
  if (blockingStrandedRecoveryAction) {
1178
1273
  if (!state.activeMilestone) {
1179
1274
  ctx.ui.notify(
1180
- `Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode, but that milestone is not active in project state. Park or discard it explicitly before continuing.`,
1275
+ formatStrandedWorkBlockerMessage(blockingStrandedRecoveryAction, null),
1181
1276
  "error",
1182
1277
  );
1183
1278
  return releaseLockAndReturn();
1184
1279
  }
1185
1280
  if (state.activeMilestone.id !== blockingStrandedRecoveryAction.milestoneId) {
1186
1281
  ctx.ui.notify(
1187
- `Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode before ${state.activeMilestone.id}. Recover, park, or discard ${blockingStrandedRecoveryAction.milestoneId} explicitly before continuing.`,
1282
+ formatStrandedWorkBlockerMessage(blockingStrandedRecoveryAction, state.activeMilestone.id),
1188
1283
  "error",
1189
1284
  );
1190
1285
  return releaseLockAndReturn();
1191
1286
  }
1192
1287
  strandedRecoveryAction = blockingStrandedRecoveryAction;
1193
1288
  ctx.ui.notify(
1194
- `Recovering stranded work for ${strandedRecoveryAction.milestoneId} before dispatching new units.`,
1289
+ formatStrandedWorkRecoveryMessage(strandedRecoveryAction),
1195
1290
  "info",
1196
1291
  );
1197
1292
  }
@@ -1663,7 +1758,7 @@ export async function bootstrapAutoSession(
1663
1758
  snapshotSkills();
1664
1759
  }
1665
1760
 
1666
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
1761
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
1667
1762
  ctx.ui.setWidget("gsd-health", undefined);
1668
1763
  const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
1669
1764
  const pendingCount = (state.registry ?? []).filter(
@@ -1,4 +1,5 @@
1
1
  import { parseUnitId } from "./unit-id.js";
2
+ import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "./tool-presentation-plan.js";
2
3
 
3
4
  export const RUN_UAT_BROWSER_TOOL_NAMES = [
4
5
  "browser_navigate",
@@ -44,7 +45,7 @@ export const AUTO_UNIT_SCOPED_TOOLS: Record<string, readonly string[]> = {
44
45
  "execute-task": ["gsd_task_complete", "gsd_decision_save"],
45
46
  "execute-task-simple": ["gsd_task_complete", "gsd_decision_save"],
46
47
  "reactive-execute": ["gsd_task_complete", "gsd_decision_save"],
47
- "run-uat": ["gsd_summary_save", ...RUN_UAT_BROWSER_TOOL_NAMES],
48
+ "run-uat": [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
48
49
  "gate-evaluate": ["gsd_save_gate_result"],
49
50
  "rewrite-docs": ["gsd_summary_save", "gsd_decision_save"],
50
51
  "workflow-preferences": ["gsd_summary_save"],
@@ -205,6 +205,7 @@ import {
205
205
  updateProgressWidget as _updateProgressWidget,
206
206
  setCompletionProgressWidget,
207
207
  setAutoOutcomeWidget,
208
+ setAutoActiveStatus,
208
209
  updateSliceProgressCache,
209
210
  clearSliceProgressCache,
210
211
  describeNextUnit as _describeNextUnit,
@@ -254,7 +255,12 @@ import {
254
255
  postUnitPreVerification,
255
256
  postUnitPostVerification,
256
257
  } from "./auto-post-unit.js";
257
- import { bootstrapAutoSession, openProjectDbIfPresent, type BootstrapDeps } from "./auto-start.js";
258
+ import {
259
+ bootstrapAutoSession,
260
+ openProjectDbIfPresent,
261
+ reconcileMergedMilestonesFromJournal,
262
+ type BootstrapDeps,
263
+ } from "./auto-start.js";
258
264
  import { initHealthWidget } from "./health-widget.js";
259
265
  import { runLegacyAutoLoop, runUokKernelLoop } from "./auto/loop.js";
260
266
  import { resolveAgentEnd, resolveAgentEndCancelled, _resetPendingResolve, isSessionSwitchInFlight } from "./auto/resolve.js";
@@ -2102,6 +2108,28 @@ export function createWiredDispatchAdapter(
2102
2108
  return null;
2103
2109
  }
2104
2110
 
2111
+ function shouldAdoptActiveMilestone(
2112
+ state: GSDState,
2113
+ activeSession: AutoSession | undefined,
2114
+ activeDispatchBasePath: string,
2115
+ ): boolean {
2116
+ const activeMilestoneId = state.activeMilestone?.id;
2117
+ const currentMilestoneId = activeSession?.currentMilestoneId;
2118
+ if (!activeSession || !activeMilestoneId || !currentMilestoneId || activeMilestoneId === currentMilestoneId) {
2119
+ return false;
2120
+ }
2121
+
2122
+ const scopedWorktreeMilestone =
2123
+ (activeSession.basePath ? detectWorktreeName(activeSession.basePath) : null) ??
2124
+ detectWorktreeName(activeDispatchBasePath);
2125
+ if (scopedWorktreeMilestone && scopedWorktreeMilestone !== activeMilestoneId) {
2126
+ return false;
2127
+ }
2128
+
2129
+ const currentMilestone = state.registry.find((milestone) => milestone.id === currentMilestoneId);
2130
+ return !!currentMilestone && isClosedStatus(currentMilestone.status);
2131
+ }
2132
+
2105
2133
  return {
2106
2134
  async decideNextUnit(input) {
2107
2135
  const state = input.stateSnapshot;
@@ -2110,6 +2138,9 @@ export function createWiredDispatchAdapter(
2110
2138
 
2111
2139
  const activeSession = input.session ?? session;
2112
2140
  const activeDispatchBasePath = activeSession?.basePath || dispatchBasePath;
2141
+ if (activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
2142
+ activeSession.currentMilestoneId = active.id;
2143
+ }
2113
2144
  const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
2114
2145
 
2115
2146
  // Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
@@ -2955,6 +2986,7 @@ export async function startAuto(
2955
2986
  if (!getLedger()) initMetrics(base);
2956
2987
  if (s.currentMilestoneId) setActiveMilestoneId(base, s.currentMilestoneId);
2957
2988
  await openProjectDbIfPresent(base);
2989
+ reconcileMergedMilestonesFromJournal(base);
2958
2990
  registerAutoWorkerForSession(s, base);
2959
2991
 
2960
2992
  // Re-register health level notification callback lost across process restart
@@ -2991,7 +3023,7 @@ export async function startAuto(
2991
3023
  ensureOrchestrationModule(ctx, pi, s.basePath || base);
2992
3024
  registerSigtermHandler(lockBase());
2993
3025
 
2994
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
3026
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
2995
3027
  ctx.ui.setWidget("gsd-health", undefined);
2996
3028
  ctx.ui.notify(
2997
3029
  s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.",
@@ -3320,7 +3352,7 @@ export async function dispatchHookUnit(
3320
3352
  await pauseAuto(ctx, pi);
3321
3353
  }, hookHardTimeoutMs);
3322
3354
 
3323
- ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
3355
+ setAutoActiveStatus(ctx, s.stepMode ? "next" : "auto");
3324
3356
  ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
3325
3357
 
3326
3358
  debugLog("dispatchHookUnit", {
@@ -413,6 +413,92 @@ export function registerDbTools(pi: ExtensionAPI): void {
413
413
  pi.registerTool(summarySaveTool);
414
414
  registerAlias(pi, summarySaveTool, "gsd_save_summary", "gsd_summary_save");
415
415
 
416
+ // ─── gsd_uat_result_save ─────────────────────────────────────────────────
417
+
418
+ const uatResultSaveExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
419
+ const { executeUatResultSave } = await loadWorkflowExecutors();
420
+ return executeUatResultSave(params, resolveWorkflowToolBasePath(_ctx, params));
421
+ };
422
+
423
+ const uatEvidenceRef = Type.Object({
424
+ kind: StringEnum(["gsd_uat_exec", "gsd_exec", "screenshot", "log", "url", "browser"], { description: "Evidence kind" }),
425
+ ref: Type.String({ description: "Evidence ID, approved .gsd path, or URL" }),
426
+ note: Type.Optional(Type.String({ description: "Short evidence note" })),
427
+ });
428
+
429
+ const uatCheck = Type.Object({
430
+ id: Type.String({ description: "Stable check ID from the UAT spec" }),
431
+ description: Type.String({ description: "Check description" }),
432
+ mode: StringEnum(["artifact", "runtime", "browser", "human-follow-up"], { description: "Evidence mode" }),
433
+ result: StringEnum(["PASS", "FAIL", "NEEDS-HUMAN"], { description: "Check result" }),
434
+ evidence: Type.Optional(Type.Array(uatEvidenceRef, { description: "Objective evidence references" })),
435
+ notes: Type.Optional(Type.String({ description: "Observed result, failure notes, or human instruction" })),
436
+ nonAutomatable: Type.Optional(Type.Boolean({ description: "True when the check is explicitly non-automatable" })),
437
+ });
438
+
439
+ const toolPresentationBlock = Type.Object({
440
+ surface: StringEnum(["provider-tools", "claude-code-sdk", "mcp", "hybrid"], { description: "Tool presentation surface" }),
441
+ model: Type.Optional(Type.Object({
442
+ provider: Type.Optional(Type.String()),
443
+ api: Type.Optional(Type.String()),
444
+ id: Type.Optional(Type.String()),
445
+ })),
446
+ presentedTools: Type.Array(Type.String(), { description: "Tool names actually presented to the model" }),
447
+ blockedTools: Type.Array(Type.Object({
448
+ name: Type.String(),
449
+ reason: Type.String(),
450
+ }), { description: "Tool names blocked from the model with reasons" }),
451
+ aliases: Type.Optional(Type.Array(Type.Object({
452
+ requested: Type.String(),
453
+ canonical: Type.String(),
454
+ }))),
455
+ fallbackToolsUsed: Type.Optional(Type.Array(Type.String())),
456
+ toolPresentationPlanId: Type.Optional(Type.String()),
457
+ notes: Type.Optional(Type.String()),
458
+ });
459
+
460
+ const uatResultSaveTool = {
461
+ name: "gsd_uat_result_save",
462
+ label: "Save UAT Result",
463
+ description:
464
+ "Save a structured UAT result for a slice. Validates evidence, writes the ASSESSMENT artifact, " +
465
+ "records attempt history, and saves the aggregate UAT gate result.",
466
+ promptSnippet: "Save structured UAT checks, evidence, verdict, and tool-presentation proof",
467
+ promptGuidelines: [
468
+ "Call gsd_uat_result_save once after all UAT checks have been executed.",
469
+ "Every PASS or FAIL check must cite objective evidence, preferably a gsd_uat_exec evidence ID.",
470
+ "Include the presented and blocked tool set in presentation so tool timing is auditable.",
471
+ "Do not use raw gsd_summary_save as a substitute for UAT results.",
472
+ ],
473
+ parameters: Type.Object({
474
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
475
+ sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
476
+ uatType: StringEnum(["artifact-driven", "browser-executable", "runtime-executable", "live-runtime", "mixed", "human-experience"], { description: "Declared UAT mode" }),
477
+ verdict: StringEnum(["PASS", "FAIL", "PARTIAL"], { description: "Overall UAT verdict" }),
478
+ checks: Type.Array(uatCheck, { description: "Structured check results" }),
479
+ presentation: toolPresentationBlock,
480
+ notes: Type.Optional(Type.String({ description: "Overall verdict rationale" })),
481
+ attempt: Type.Optional(Type.String({ description: "Attempt number or auto" })),
482
+ previousAttemptId: Type.Optional(Type.String({ description: "Prior attempt ID, when retrying" })),
483
+ }),
484
+ execute: uatResultSaveExecute,
485
+ renderCall(args: any, theme: any) {
486
+ let text = theme.fg("toolTitle", theme.bold("uat_result_save "));
487
+ text += theme.fg("accent", `${args.milestoneId ?? "?"}/${args.sliceId ?? "?"}`);
488
+ if (args.verdict) text += theme.fg("dim", ` → ${args.verdict}`);
489
+ return new Text(text, 0, 0);
490
+ },
491
+ renderResult(result: any, _options: any, theme: any) {
492
+ const d = readDetails(result);
493
+ if (result.isError || d?.error) {
494
+ return new Text(theme.fg("error", formatToolErrorText(result, d)), 0, 0);
495
+ }
496
+ return new Text(theme.fg("success", `UAT ${d?.sliceId ?? ""}: ${d?.verdict ?? "saved"}`), 0, 0);
497
+ },
498
+ };
499
+
500
+ pi.registerTool(uatResultSaveTool);
501
+
416
502
  // ─── gsd_milestone_generate_id (formerly gsd_generate_milestone_id) ────
417
503
 
418
504
  const milestoneGenerateIdExecute = async (_toolCallId: string, _params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {