@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
@@ -37,6 +37,7 @@ import { rowToActiveDecision, rowToActiveRequirement, rowToDecision, rowToRequir
37
37
  import { rowToGate } from "./db-gate-rows.js";
38
38
  import { rowToArtifact, rowToMilestone } from "./db-milestone-artifact-rows.js";
39
39
  import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
40
+ import { isClosedStatus } from "./status-guards.js";
40
41
  import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, applyMigrationV29RepositoryTargets, } from "./db-migration-steps.js";
41
42
  import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
42
43
  import { createDbOpenState } from "./db-open-state.js";
@@ -1419,15 +1420,47 @@ export function setMilestoneQueueOrder(order) {
1419
1420
  throw err;
1420
1421
  }
1421
1422
  }
1423
+ function getMilestoneStatusForUpdate(milestoneId) {
1424
+ if (!currentDb)
1425
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1426
+ const row = currentDb.prepare("SELECT status FROM milestones WHERE id = :id").get({ ":id": milestoneId });
1427
+ return typeof row?.["status"] === "string" ? row["status"] : null;
1428
+ }
1429
+ function writeMilestoneStatus(milestoneId, status, completedAt) {
1430
+ if (!currentDb)
1431
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1432
+ currentDb.prepare(`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
1433
+ }
1422
1434
  /**
1423
1435
  * Update a milestone's status in the database.
1424
- * Used by park/unpark to keep the DB in sync with the filesystem marker.
1425
- * See: https://github.com/open-gsd/gsd-pi/issues/2694
1436
+ *
1437
+ * Generic status updates may close milestones, park/unpark open milestones, or
1438
+ * advance planned milestones. They may not reopen a closed milestone; callers
1439
+ * must use reopenMilestoneStatus(), which is reserved for gsd_milestone_reopen.
1426
1440
  */
1427
1441
  export function updateMilestoneStatus(milestoneId, status, completedAt) {
1428
1442
  if (!currentDb)
1429
1443
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1430
- currentDb.prepare(`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
1444
+ const currentStatus = getMilestoneStatusForUpdate(milestoneId);
1445
+ if (currentStatus && isClosedStatus(currentStatus) && !isClosedStatus(status)) {
1446
+ throw new Error(`Cannot update closed milestone ${milestoneId} from ${currentStatus} to ${status}; use gsd_milestone_reopen for an explicit reopen.`);
1447
+ }
1448
+ writeMilestoneStatus(milestoneId, status, completedAt);
1449
+ }
1450
+ /**
1451
+ * Explicit closed -> active transition for gsd_milestone_reopen only.
1452
+ */
1453
+ export function reopenMilestoneStatus(milestoneId) {
1454
+ if (!currentDb)
1455
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1456
+ const currentStatus = getMilestoneStatusForUpdate(milestoneId);
1457
+ if (!currentStatus) {
1458
+ throw new Error(`Cannot reopen missing milestone ${milestoneId}`);
1459
+ }
1460
+ if (!isClosedStatus(currentStatus)) {
1461
+ throw new Error(`Cannot reopen milestone ${milestoneId} from status ${currentStatus}; milestone is not closed.`);
1462
+ }
1463
+ writeMilestoneStatus(milestoneId, "active", null);
1431
1464
  }
1432
1465
  export function getActiveMilestoneFromDb() {
1433
1466
  if (!currentDb)
@@ -2240,7 +2273,7 @@ export function deleteArtifactByPath(path) {
2240
2273
  }
2241
2274
  /**
2242
2275
  * Drop hierarchy rows in dependency order inside a transaction. Used by
2243
- * `gsd recover` to rebuild engine state from markdown.
2276
+ * `gsd recover --confirm` to rebuild engine state from markdown.
2244
2277
  */
2245
2278
  export function clearEngineHierarchy() {
2246
2279
  if (!currentDb)
@@ -1908,7 +1908,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1908
1908
  const result = await checkMarkdownHierarchyAgainstDb(basePath);
1909
1909
  if (result.action === "recovery-required") {
1910
1910
  ctx.ui.notify(result.message ??
1911
- `Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "/gsd recover"}\` to import markdown explicitly.`, "warning");
1911
+ `Markdown planning artifacts do not match the authoritative DB. Run \`${result.recoveryCommand ?? "/gsd recover --confirm"}\` to import markdown explicitly.`, "warning");
1912
1912
  }
1913
1913
  }
1914
1914
  catch (err) {
@@ -24,14 +24,17 @@ function collectServerEntries(servers) {
24
24
  export function discoverMcpServers(projectDir) {
25
25
  const mcpJsonPath = resolve(projectDir, ".mcp.json");
26
26
  const settingsPath = resolve(projectDir, ".claude", "settings.json");
27
+ const localSettingsPath = resolve(projectDir, ".claude", "settings.local.json");
27
28
  const mcpJson = readJsonFile(mcpJsonPath);
28
29
  const settings = readJsonFile(settingsPath, true);
30
+ const localSettings = readJsonFile(localSettingsPath, true);
29
31
  const seen = new Set();
30
32
  const discovered = [];
31
33
  for (const entry of [
32
34
  ...collectServerEntries(mcpJson?.mcpServers),
33
35
  ...collectServerEntries(mcpJson?.servers),
34
36
  ...collectServerEntries(settings?.mcpServers),
37
+ ...collectServerEntries(localSettings?.mcpServers),
35
38
  ]) {
36
39
  if (seen.has(entry.name))
37
40
  continue;
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import { basename, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -157,6 +157,62 @@ function readExistingConfig(configPath) {
157
157
  throw new Error(`Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
158
158
  }
159
159
  }
160
+ function readExistingClaudeCodeSettings(settingsPath) {
161
+ if (!existsSync(settingsPath))
162
+ return {};
163
+ const raw = readFileSync(settingsPath, "utf-8");
164
+ try {
165
+ const parsed = JSON.parse(raw);
166
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
167
+ }
168
+ catch (err) {
169
+ throw new Error(`Failed to parse ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
170
+ }
171
+ }
172
+ export function ensureClaudeCodeMcpJsonServersEnabled(projectRoot, serverNames) {
173
+ const resolvedProjectRoot = resolve(projectRoot);
174
+ assertSafeDirectory(resolvedProjectRoot);
175
+ const targetServerNames = [...new Set(serverNames.filter((name) => name.trim().length > 0))];
176
+ if (targetServerNames.length === 0)
177
+ return false;
178
+ const settingsDir = resolve(resolvedProjectRoot, ".claude");
179
+ const settingsPath = resolve(settingsDir, "settings.local.json");
180
+ const existing = readExistingClaudeCodeSettings(settingsPath);
181
+ const enabled = Array.isArray(existing.enabledMcpjsonServers)
182
+ ? [...existing.enabledMcpjsonServers]
183
+ : [];
184
+ const enabledNames = new Set(enabled.filter((value) => typeof value === "string"));
185
+ let changed = !Array.isArray(existing.enabledMcpjsonServers);
186
+ for (const serverName of targetServerNames) {
187
+ if (!enabledNames.has(serverName)) {
188
+ enabled.push(serverName);
189
+ enabledNames.add(serverName);
190
+ changed = true;
191
+ }
192
+ }
193
+ let nextDisabled = existing.disabledMcpjsonServers;
194
+ if (Array.isArray(existing.disabledMcpjsonServers)) {
195
+ const blockedNames = new Set(targetServerNames);
196
+ const filtered = existing.disabledMcpjsonServers.filter((value) => !blockedNames.has(String(value)));
197
+ if (filtered.length !== existing.disabledMcpjsonServers.length) {
198
+ nextDisabled = filtered;
199
+ changed = true;
200
+ }
201
+ }
202
+ if (!changed)
203
+ return false;
204
+ const nextSettings = {
205
+ ...existing,
206
+ enabledMcpjsonServers: enabled,
207
+ ...(Array.isArray(existing.disabledMcpjsonServers) ? { disabledMcpjsonServers: nextDisabled } : {}),
208
+ };
209
+ mkdirSync(settingsDir, { recursive: true });
210
+ writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`, "utf-8");
211
+ return true;
212
+ }
213
+ export function ensureClaudeCodeMcpJsonServerEnabled(projectRoot, serverName) {
214
+ return ensureClaudeCodeMcpJsonServersEnabled(projectRoot, [serverName]);
215
+ }
160
216
  export function ensureProjectWorkflowMcpConfig(projectRoot, env = process.env) {
161
217
  const resolvedProjectRoot = resolve(projectRoot);
162
218
  assertSafeDirectory(resolvedProjectRoot);
@@ -170,10 +226,18 @@ export function ensureProjectWorkflowMcpConfig(projectRoot, env = process.env) {
170
226
  };
171
227
  const desiredServerNames = Object.keys(desiredServers);
172
228
  const alreadyPresent = existsSync(configPath);
173
- const unchanged = desiredServerNames.every((serverName) => (JSON.stringify(previousServers[serverName] ?? null)
229
+ const mcpConfigUnchanged = desiredServerNames.every((serverName) => (JSON.stringify(previousServers[serverName] ?? null)
174
230
  === JSON.stringify(desiredServers[serverName])))
175
231
  && existing.mcpServers !== undefined;
176
- if (unchanged) {
232
+ if (!mcpConfigUnchanged) {
233
+ const nextConfig = {
234
+ ...existing,
235
+ mcpServers: nextServers,
236
+ };
237
+ writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
238
+ }
239
+ const localSettingsChanged = ensureClaudeCodeMcpJsonServersEnabled(resolvedProjectRoot, desiredServerNames);
240
+ if (mcpConfigUnchanged && !localSettingsChanged) {
177
241
  return {
178
242
  configPath,
179
243
  serverName: workflowServerName,
@@ -181,11 +245,6 @@ export function ensureProjectWorkflowMcpConfig(projectRoot, env = process.env) {
181
245
  status: "unchanged",
182
246
  };
183
247
  }
184
- const nextConfig = {
185
- ...existing,
186
- mcpServers: nextServers,
187
- };
188
- writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
189
248
  return {
190
249
  configPath,
191
250
  serverName: workflowServerName,
@@ -78,9 +78,9 @@ export async function checkMarkdownHierarchyAgainstDb(basePath) {
78
78
  markdown,
79
79
  beforeDb,
80
80
  afterDb: beforeDb,
81
- recoveryCommand: "/gsd recover",
81
+ recoveryCommand: "/gsd recover --confirm",
82
82
  message: `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
83
83
  `do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). ` +
84
- "Runtime startup will not import markdown automatically; run `/gsd recover` if markdown should repopulate the database.",
84
+ "Runtime startup will not import markdown automatically; run `/gsd recover --confirm` if markdown should repopulate the database.",
85
85
  };
86
86
  }
@@ -37,7 +37,13 @@ You are the UAT runner. Execute every check defined in `{{uatPath}}` as deeply a
37
37
 
38
38
  Choose the lightest tool that proves the check honestly:
39
39
 
40
- - Run shell commands with `bash`
40
+ - Run automated checks with `gsd_uat_exec`
41
+ - Use `uat-artifact-check` as `intent` for static file, grep, structure, or artifact checks.
42
+ - Use `uat-runtime-check` as `intent` for executing tests, scripts, or runtime assertions.
43
+ - Use `uat-browser-check` as `intent` for browser interaction or screenshot-backed UI checks.
44
+ - Use `uat-service-start` as `intent` only when starting or connecting to an app/service.
45
+ - Use `uat-log-inspection` as `intent` for checking logs or captured output files.
46
+ - The result-table evidence mode is separate; do not use `artifact`, `runtime`, or `human-follow-up` as `intent`.
41
47
  - Run `grep` / `rg` checks against files
42
48
  - Run `node` / other script invocations
43
49
  - Read files and verify their contents
@@ -48,7 +54,7 @@ Choose the lightest tool that proves the check honestly:
48
54
  For each check, record:
49
55
  - The check description (from the UAT file)
50
56
  - The evidence mode used: `artifact`, `runtime`, or `human-follow-up`
51
- - The command or action taken
57
+ - The command or action taken, including the `gsd_uat_exec` evidence ID for automated checks
52
58
  - The actual result observed
53
59
  - `PASS`, `FAIL`, or `NEEDS-HUMAN`
54
60
 
@@ -57,7 +63,7 @@ After running all checks, compute the **overall verdict**:
57
63
  - `FAIL` — one or more automatable checks failed
58
64
  - `PARTIAL` — one or more automatable checks were skipped or returned inconclusive results (not the same as `NEEDS-HUMAN` — use PARTIAL only when the agent itself could not determine pass/fail for a check it was supposed to automate)
59
65
 
60
- Call `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `slice_id: {{sliceId}}`, `artifact_type: "ASSESSMENT"`, and the full UAT result markdown as `content` the tool computes the file path and persists to both DB and disk. The content should follow this format:
66
+ Call `gsd_summary_save` with `milestone_id: "{{milestoneId}}"`, `slice_id: "{{sliceId}}"`, `artifact_type: "ASSESSMENT"`, and the full UAT result markdown as `content`. The tool computes the assessment path, persists to DB/disk, and saves the aggregate UAT gate. The content should follow this logical shape:
61
67
 
62
68
  ```markdown
63
69
  ---
@@ -86,6 +92,6 @@ date: <ISO 8601 timestamp>
86
92
 
87
93
  ---
88
94
 
89
- **You MUST call `gsd_summary_save` with the UAT result content before finishing.**
95
+ **You MUST call `gsd_summary_save` with `artifact_type: "ASSESSMENT"` and the UAT result content before finishing. Do not write the assessment file directly.**
90
96
 
91
97
  When done, say: "UAT {{sliceId}} complete."
@@ -32,7 +32,7 @@ GSD ships with bundled skills. Installed skills are listed in `<available_skills
32
32
  - Never print, echo, log, or restate secrets or credentials. Report only key names and applied/skipped status.
33
33
  - Never ask the user to edit `.env` files or set secrets manually. Use `secure_env_collect`.
34
34
  - In enduring files, write current state only unless the file is explicitly historical.
35
- - **Never take outward-facing actions on GitHub or external services without explicit user confirmation.** This includes creating/closing issues, merging/approving/commenting on PRs, pushing remote branches, publishing packages, or any state change outside local filesystem. Read-only listing/viewing/diffing is fine. Present intent and get a clear "yes" first. **Non-bypassable:** no response, ambiguity, or `ask_user_questions` failure means re-ask; never rationalize past the block. Missing "yes" means "no."
35
+ - **Never take outward-facing actions on GitHub or external services without explicit user confirmation.** This includes creating/closing issues, merging/approving/commenting on PRs, pushing remote branches, publishing packages, terragrunt/aws/kubectl mutations, or any state change outside local filesystem. Read-only listing/viewing/diffing is fine. Present intent and get a clear "yes" first. **Non-bypassable:** no response, ambiguity, or `ask_user_questions` failure means re-ask; never rationalize past the block. Missing "yes" means "no."
36
36
 
37
37
  If a `GSD Skill Preferences` block appears below, treat it as durable guidance for skills to use, prefer, or avoid unless it conflicts with artifact rules, verification, or higher-priority instructions.
38
38
 
@@ -160,4 +160,6 @@ Fix root causes, not symptoms. If applying temporary mitigation, label it and pr
160
160
  - When debugging, stay curious. Problems are puzzles. Say what's interesting about the failure before reaching for fixes.
161
161
  - After completing a task, give a brief summary and 2-4 numbered next-step options; last option is always "Other". Omit the list for strict output formats.
162
162
 
163
+ If any next step is destructive/outward-facing, present it via `ask_user_questions` and wait for the user's answer before execution. Do not execute a next-step item from a prior plain-text numbered list without fresh confirmation.
164
+
163
165
  Good narration states a decision or finding: "Three handlers follow a middleware pattern - using that instead of a custom wrapper." Bad narration just announces the next call ("Reading the file now.") or emits compressed planner notes ("Need create plan artifact maybe read existing plans.").
@@ -16,6 +16,9 @@ const DESTRUCTIVE_PATTERNS = [
16
16
  { pattern: /\btruncate\s+table\b/i, label: "SQL truncate" },
17
17
  { pattern: /\bchmod\s+777\b/, label: "world-writable permissions" },
18
18
  { pattern: /\bcurl\s.*\|\s*(bash|sh|zsh)\b/, label: "pipe to shell" },
19
+ { pattern: /\bterra(form|grunt)\s+(apply|destroy)/i, label: "IaC apply/destroy" },
20
+ { pattern: /\baws\s+\w+\s+(delete|create|put|remove|terminate)\b/i, label: "AWS mutation" },
21
+ { pattern: /\bkubectl\s+(delete|apply)\b/i, label: "kubectl mutation" },
19
22
  ];
20
23
  /**
21
24
  * Classify a bash command for destructive operations.
@@ -41,6 +41,16 @@ function tokenizeSkillContext(...parts) {
41
41
  }
42
42
  return tokens;
43
43
  }
44
+ function tokenizeUnitType(unitType) {
45
+ const tokens = new Set();
46
+ const value = unitType?.trim().toLowerCase();
47
+ if (!value)
48
+ return tokens;
49
+ tokens.add(value);
50
+ tokens.add(value.replace(/[-_]+/g, " "));
51
+ tokens.add(value.replace(/[-_\s]+/g, ""));
52
+ return tokens;
53
+ }
44
54
  function skillMatchesContext(skill, contextTokens) {
45
55
  const haystacks = [
46
56
  skill.name.toLowerCase(),
@@ -63,13 +73,20 @@ function ruleMatchesContext(when, contextTokens) {
63
73
  const whenTokens = tokenizeSkillContext(when);
64
74
  return [...whenTokens].some(token => contextTokens.has(token) || [...contextTokens].some(ctx => ctx.includes(token) || token.includes(ctx)));
65
75
  }
66
- function resolveSkillRuleMatches(prefs, contextTokens, base) {
76
+ function ruleMatchesUnitType(when, unitType) {
77
+ if (!unitType)
78
+ return false;
79
+ const whenTokens = tokenizeSkillContext(when);
80
+ const unitTokens = tokenizeUnitType(unitType);
81
+ return [...unitTokens].some(token => whenTokens.has(token));
82
+ }
83
+ function resolveSkillRuleMatches(prefs, contextTokens, base, unitType) {
67
84
  if (!prefs?.skill_rules?.length)
68
85
  return { include: [], avoid: [] };
69
86
  const include = [];
70
87
  const avoid = [];
71
88
  for (const rule of prefs.skill_rules) {
72
- if (!ruleMatchesContext(rule.when, contextTokens))
89
+ if (!ruleMatchesContext(rule.when, contextTokens) && !ruleMatchesUnitType(rule.when, unitType))
73
90
  continue;
74
91
  include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
75
92
  avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
@@ -141,7 +158,7 @@ export function buildSkillActivationBlock(params) {
141
158
  for (const name of resolvePreferenceSkillNames(prefs?.always_use_skills ?? [], params.base)) {
142
159
  matched.add(name);
143
160
  }
144
- const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
161
+ const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base, params.unitType);
145
162
  for (const name of ruleMatches.include)
146
163
  matched.add(name);
147
164
  for (const name of ruleMatches.avoid)
@@ -313,7 +313,8 @@ export function repairArtifactDbDrift(record, ctx) {
313
313
  throw new Error(`Artifact/DB status drift in ${record.milestoneId}` +
314
314
  `${record.sliceId ? `/${record.sliceId}` : ""}` +
315
315
  `${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
316
- "Runtime will not silently import completion artifacts into DB state; run explicit recovery/repair after review.");
316
+ "Runtime will not silently import completion artifacts into DB state. " +
317
+ "Run `/gsd rebuild markdown` after review to quarantine stale projections and re-render from the DB; use `/gsd recover --confirm` only when markdown should repopulate a lost or corrupt DB.");
317
318
  }
318
319
  export function describeArtifactDbDriftBlocker(record) {
319
320
  if (record.kind === "disk-slice-id-divergence") {
@@ -329,7 +330,8 @@ export function describeArtifactDbDriftBlocker(record) {
329
330
  return (`Artifact/DB status drift in ${record.milestoneId}` +
330
331
  `${record.sliceId ? `/${record.sliceId}` : ""}` +
331
332
  `${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
332
- "Runtime will not silently import completion artifacts into DB state; run explicit recovery/repair after review.");
333
+ "Runtime will not silently import completion artifacts into DB state. " +
334
+ "Run `/gsd rebuild markdown` after review to quarantine stale projections and re-render from the DB; use `/gsd recover --confirm` only when markdown should repopulate a lost or corrupt DB.");
333
335
  }
334
336
  export const diskSliceIdDivergenceHandler = {
335
337
  kind: "disk-slice-id-divergence",
@@ -36,7 +36,7 @@ export function detectUnregisteredMilestoneDrift(_state, ctx) {
36
36
  */
37
37
  export function repairUnregisteredMilestone(record, _ctx) {
38
38
  throw new Error(`Milestone ${record.milestoneId} exists only as markdown projection. ` +
39
- "Runtime reconciliation will not import markdown into the authoritative DB; run `/gsd recover` if this markdown should repopulate the database.");
39
+ "Runtime reconciliation will not import markdown into the authoritative DB; run `/gsd recover --confirm` if this markdown should repopulate the database.");
40
40
  }
41
41
  export const unregisteredMilestoneHandler = {
42
42
  kind: "unregistered-milestone",
@@ -4,7 +4,7 @@
4
4
  // checkboxes) and the DB slice rows for that milestone, then re-renders the
5
5
  // ROADMAP projection from the authoritative DB rows.
6
6
  import { existsSync, readFileSync } from "node:fs";
7
- import { getMilestone, getMilestoneSlices, isDbAvailable, } from "../../gsd-db.js";
7
+ import { getMilestone, getMilestoneSlices, getSliceTasks, isDbAvailable, } from "../../gsd-db.js";
8
8
  import { renderRoadmapFromDb } from "../../markdown-renderer.js";
9
9
  import { findMilestoneIds } from "../../milestone-ids.js";
10
10
  import { parseRoadmap } from "../../parsers-legacy.js";
@@ -18,6 +18,15 @@ function arraysEqual(a, b) {
18
18
  return false;
19
19
  return true;
20
20
  }
21
+ function getSlicesReadyForDivergenceCheck(milestoneId, dbSlices) {
22
+ const ready = new Set();
23
+ for (const slice of dbSlices) {
24
+ if (isClosedStatus(slice.status) || getSliceTasks(milestoneId, slice.id).length > 0) {
25
+ ready.add(slice.id);
26
+ }
27
+ }
28
+ return ready;
29
+ }
21
30
  function milestoneHasDivergence(basePath, milestoneId) {
22
31
  const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
23
32
  if (!roadmapPath || !existsSync(roadmapPath))
@@ -31,6 +40,10 @@ function milestoneHasDivergence(basePath, milestoneId) {
31
40
  }
32
41
  const dbSlices = getMilestoneSlices(milestoneId);
33
42
  const dbSliceMap = new Map(dbSlices.map((s) => [s.id, s]));
43
+ const readySliceIds = getSlicesReadyForDivergenceCheck(milestoneId, dbSlices);
44
+ if (dbSlices.length > 0 && readySliceIds.size === 0) {
45
+ return false;
46
+ }
34
47
  const roadmapSliceIds = new Set();
35
48
  for (let i = 0; i < roadmap.slices.length; i++) {
36
49
  const roadmapSlice = roadmap.slices[i];
@@ -39,6 +52,8 @@ function milestoneHasDivergence(basePath, milestoneId) {
39
52
  const dbSlice = dbSliceMap.get(roadmapSlice.id);
40
53
  if (!dbSlice)
41
54
  return true; // Roadmap has a slice the DB doesn't.
55
+ if (!readySliceIds.has(dbSlice.id))
56
+ continue;
42
57
  if (dbSlice.sequence !== expectedSequence)
43
58
  return true;
44
59
  if (!arraysEqual(dbSlice.depends, roadmapSlice.depends))
@@ -47,6 +62,8 @@ function milestoneHasDivergence(basePath, milestoneId) {
47
62
  return true;
48
63
  }
49
64
  for (const dbSlice of dbSlices) {
65
+ if (!readySliceIds.has(dbSlice.id))
66
+ continue;
50
67
  if (!roadmapSliceIds.has(dbSlice.id))
51
68
  return true;
52
69
  }
@@ -2,6 +2,7 @@
2
2
  // File Purpose: ADR-017 drift-driven State Reconciliation Module entry point.
3
3
  // reconcileBeforeDispatch runs before every Dispatch decision and worker spawn.
4
4
  import { deriveState as defaultDeriveState, invalidateStateCache as defaultInvalidate, } from "../state.js";
5
+ import { clearParseCache as defaultClearParseCache } from "../files.js";
5
6
  import { ReconciliationFailedError, } from "./errors.js";
6
7
  import { DRIFT_REGISTRY } from "./registry.js";
7
8
  export { ReconciliationFailedError } from "./errors.js";
@@ -10,6 +11,7 @@ const MAX_PASSES = 2;
10
11
  const defaultDeps = {
11
12
  invalidateStateCache: defaultInvalidate,
12
13
  deriveState: defaultDeriveState,
14
+ clearParseCache: defaultClearParseCache,
13
15
  };
14
16
  /**
15
17
  * Drift-driven pre-dispatch reconciliation per ADR-017.
@@ -27,6 +29,7 @@ const defaultDeps = {
27
29
  */
28
30
  export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
29
31
  const registry = deps.registry ?? DRIFT_REGISTRY;
32
+ const clearParseCache = deps.clearParseCache ?? defaultClearParseCache;
30
33
  const repaired = [];
31
34
  for (let pass = 0; pass < MAX_PASSES; pass++) {
32
35
  deps.invalidateStateCache();
@@ -67,6 +70,9 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
67
70
  failures.push({ drift: record, cause });
68
71
  }
69
72
  }
73
+ if (repairedThisPass) {
74
+ clearParseCache();
75
+ }
70
76
  if (blockers.length > 0) {
71
77
  let blockerState = stateSnapshot;
72
78
  if (repairedThisPass) {
@@ -35,7 +35,7 @@ function formatNeedsRemediationBlocker(milestoneId) {
35
35
  return [
36
36
  `Milestone ${milestoneId} is blocked because milestone validation returned needs-remediation, but all slices are complete.`,
37
37
  `Fix options:`,
38
- `1. Add remediation slices with \`gsd_reassess_roadmap\`, then run \`/gsd auto\``,
38
+ `1. Run \`/gsd dispatch reassess\` to add remediation slices, then run \`/gsd auto\``,
39
39
  `2. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
40
40
  `3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
41
41
  ].join("\n");
@@ -204,10 +204,16 @@ export function invalidateStateCache() {
204
204
  /**
205
205
  * Returns the ID of the first incomplete milestone, or null if all are complete.
206
206
  */
207
+ function getRequestedMilestoneLock() {
208
+ const lock = process.env.GSD_MILESTONE_LOCK?.trim();
209
+ return lock || undefined;
210
+ }
207
211
  export async function getActiveMilestoneId(basePath) {
208
- // Parallel worker isolation. Normal DB state derivation remains DB-only;
209
- // lock env vars are execution routing for explicit worker processes.
210
- const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
212
+ // Milestone-scoped execution. Parallel workers and explicit solo commands
213
+ // such as `/gsd auto M002` both set GSD_MILESTONE_LOCK; state derivation must
214
+ // honor it so recovery/adoption sees the requested milestone, not the first
215
+ // open milestone in queue order.
216
+ const milestoneLock = getRequestedMilestoneLock();
211
217
  if (milestoneLock) {
212
218
  if (isDbAvailable()) {
213
219
  const locked = getAllMilestones().find(m => m.id === milestoneLock);
@@ -573,7 +579,7 @@ function checkReplanTrigger(basePath, milestoneId, sliceId) {
573
579
  export async function deriveStateFromDb(basePath, artifactReadRoot = basePath) {
574
580
  const requirements = getRequirementCounts();
575
581
  const allMilestones = getAllMilestones();
576
- const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
582
+ const milestoneLock = getRequestedMilestoneLock();
577
583
  const milestones = milestoneLock
578
584
  ? allMilestones.filter(m => m.id === milestoneLock)
579
585
  : allMilestones;
@@ -779,13 +785,10 @@ export async function _deriveStateImpl(basePath, opts) {
779
785
  const diskIds = findMilestoneIds(basePath);
780
786
  const customOrder = loadQueueOrder(basePath);
781
787
  const milestoneIds = sortByQueueOrder(diskIds, customOrder);
782
- // ── Parallel worker isolation ──────────────────────────────────────────
783
- // When GSD_PARALLEL_WORKER and GSD_MILESTONE_LOCK are set, this process is a parallel worker
784
- // scoped to a single milestone. Filter the milestone list so this worker
785
- // only sees its assigned milestone (all others are treated as if they
786
- // don't exist). This gives each worker complete isolation without
787
- // modifying any other state derivation logic.
788
- const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
788
+ // ── Milestone-scoped execution ─────────────────────────────────────────
789
+ // Parallel workers and explicit solo recovery both scope auto-mode to one
790
+ // milestone through GSD_MILESTONE_LOCK.
791
+ const milestoneLock = getRequestedMilestoneLock();
789
792
  if (milestoneLock && milestoneIds.includes(milestoneLock)) {
790
793
  milestoneIds.length = 0;
791
794
  milestoneIds.push(milestoneLock);
@@ -0,0 +1,120 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Resolve phase-aware tool surfaces for GSD model presentations.
3
+ export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
4
+ "gsd_uat_exec",
5
+ "gsd_uat_result_save",
6
+ "gsd_resume",
7
+ "gsd_milestone_status",
8
+ "gsd_journal_query",
9
+ ];
10
+ export const RUN_UAT_FORBIDDEN_TOOL_NAMES = [
11
+ "edit",
12
+ "write",
13
+ "gsd_exec",
14
+ "gsd_summary_save",
15
+ "gsd_save_gate_result",
16
+ "search-the-web",
17
+ "WebSearch",
18
+ "Bash",
19
+ "Write",
20
+ "Edit",
21
+ "mcp__gsd-workflow__*",
22
+ ];
23
+ export const RUN_UAT_CLAUDE_NATIVE_TOOL_NAMES = [
24
+ "Read",
25
+ "Glob",
26
+ "Grep",
27
+ ];
28
+ const WORKFLOW_ALIAS_TO_CANONICAL = {
29
+ gsd_save_decision: "gsd_decision_save",
30
+ gsd_update_requirement: "gsd_requirement_update",
31
+ gsd_save_requirement: "gsd_requirement_save",
32
+ gsd_save_summary: "gsd_summary_save",
33
+ gsd_generate_milestone_id: "gsd_milestone_generate_id",
34
+ gsd_milestone_plan: "gsd_plan_milestone",
35
+ gsd_slice_plan: "gsd_plan_slice",
36
+ gsd_task_plan: "gsd_plan_task",
37
+ gsd_slice_replan: "gsd_replan_slice",
38
+ gsd_complete_slice: "gsd_slice_complete",
39
+ gsd_milestone_complete: "gsd_complete_milestone",
40
+ gsd_milestone_validate: "gsd_validate_milestone",
41
+ gsd_roadmap_reassess: "gsd_reassess_roadmap",
42
+ gsd_complete_task: "gsd_task_complete",
43
+ gsd_reopen_task: "gsd_task_reopen",
44
+ gsd_reopen_slice: "gsd_slice_reopen",
45
+ gsd_reopen_milestone: "gsd_milestone_reopen",
46
+ };
47
+ export function canonicalWorkflowToolName(toolName) {
48
+ const mcp = parseMcpToolName(toolName);
49
+ const baseName = mcp?.tool ?? toolName;
50
+ return WORKFLOW_ALIAS_TO_CANONICAL[baseName] ?? baseName;
51
+ }
52
+ export function parseMcpToolName(toolName) {
53
+ if (!toolName.startsWith("mcp__"))
54
+ return null;
55
+ const toolSeparator = toolName.indexOf("__", "mcp__".length);
56
+ if (toolSeparator < 0)
57
+ return null;
58
+ return {
59
+ server: toolName.slice("mcp__".length, toolSeparator),
60
+ tool: toolName.slice(toolSeparator + 2),
61
+ };
62
+ }
63
+ export function toWorkflowMcpToolName(serverName, toolName) {
64
+ return `mcp__${serverName}__${canonicalWorkflowToolName(toolName)}`;
65
+ }
66
+ function dedupe(values) {
67
+ return [...new Set(values)];
68
+ }
69
+ function addBlockedTool(blocked, name, reason) {
70
+ if (!blocked.some((entry) => entry.name === name)) {
71
+ blocked.push({ name, reason });
72
+ }
73
+ }
74
+ export function buildRunUatCanonicalToolNames(options = {}) {
75
+ return dedupe([
76
+ ...RUN_UAT_WORKFLOW_TOOL_NAMES,
77
+ ...(options.includeBrowserTools ?? []),
78
+ ]);
79
+ }
80
+ export function resolveToolPresentationPlan(options) {
81
+ const requested = options.requestedToolNames ?? (options.phase === "run-uat"
82
+ ? buildRunUatCanonicalToolNames({ includeBrowserTools: options.includeBrowserTools })
83
+ : []);
84
+ const available = new Set(options.availableToolNames ?? requested);
85
+ const aliases = [];
86
+ const blockedToolNames = [];
87
+ const allowed = [];
88
+ for (const name of requested) {
89
+ const canonical = canonicalWorkflowToolName(name);
90
+ if (canonical !== name)
91
+ aliases.push({ requested: name, canonical });
92
+ if (!available.has(name) && !available.has(canonical)) {
93
+ addBlockedTool(blockedToolNames, canonical, "not registered or provider-incompatible");
94
+ continue;
95
+ }
96
+ allowed.push(canonical);
97
+ }
98
+ const allowedToolNames = dedupe(allowed);
99
+ const workflowServerName = options.workflowMcpServerName || "gsd-workflow";
100
+ const presentedToolNames = options.surface === "claude-code-sdk" || options.surface === "mcp"
101
+ ? allowedToolNames.map((name) => name.startsWith("gsd_") || name === "ask_user_questions"
102
+ ? toWorkflowMcpToolName(workflowServerName, name)
103
+ : name)
104
+ : allowedToolNames;
105
+ if (options.phase === "run-uat") {
106
+ for (const forbidden of RUN_UAT_FORBIDDEN_TOOL_NAMES) {
107
+ addBlockedTool(blockedToolNames, forbidden, "forbidden during run-uat");
108
+ }
109
+ }
110
+ return {
111
+ phase: options.phase,
112
+ surface: options.surface,
113
+ model: options.model,
114
+ allowedToolNames,
115
+ presentedToolNames,
116
+ blockedToolNames,
117
+ aliases,
118
+ diagnostics: [],
119
+ };
120
+ }