@opengsd/gsd-pi 1.2.0-dev.d6c5343c → 1.2.0-dev.ddc97c10

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 (225) hide show
  1. package/dist/mcp-server.js +2 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/orchestrator.js +28 -10
  4. package/dist/resources/extensions/gsd/auto/phases.js +47 -4
  5. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +3 -2
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -2
  8. package/dist/resources/extensions/gsd/auto-model-selection.js +11 -7
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +18 -6
  10. package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
  11. package/dist/resources/extensions/gsd/auto-verification.js +14 -2
  12. package/dist/resources/extensions/gsd/auto.js +37 -1
  13. package/dist/resources/extensions/gsd/blocked-models.js +28 -0
  14. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
  15. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  16. package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
  17. package/dist/resources/extensions/gsd/commands/context.js +16 -2
  18. package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
  19. package/dist/resources/extensions/gsd/consent-question.js +16 -0
  20. package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
  21. package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -3
  22. package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
  23. package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
  24. package/dist/resources/extensions/gsd/gsd-db.js +2 -1
  25. package/dist/resources/extensions/gsd/guided-flow.js +6 -3
  26. package/dist/resources/extensions/gsd/milestone-closeout.js +73 -2
  27. package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
  28. package/dist/resources/extensions/gsd/projection-flush.js +7 -0
  29. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  30. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  43. package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
  44. package/dist/resources/extensions/gsd/session-lock.js +1 -1
  45. package/dist/resources/extensions/gsd/tool-contract.js +14 -3
  46. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  47. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
  48. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  49. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
  50. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
  51. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  53. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
  54. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
  55. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
  56. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
  57. package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
  58. package/dist/resources/extensions/shared/gsd-browser-cli.js +21 -2
  59. package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
  60. package/dist/resources/shared/package-manager-detection.js +1 -1
  61. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  62. package/dist/update-check.d.ts +2 -0
  63. package/dist/update-check.js +24 -1
  64. package/dist/update-cmd.js +20 -3
  65. package/dist/web/standalone/.next/BUILD_ID +1 -1
  66. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  67. package/dist/web/standalone/.next/build-manifest.json +2 -2
  68. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  69. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  93. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  94. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  96. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  97. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  98. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  99. package/package.json +1 -1
  100. package/packages/cloud-mcp-gateway/package.json +2 -2
  101. package/packages/contracts/package.json +1 -1
  102. package/packages/daemon/package.json +4 -4
  103. package/packages/gsd-agent-core/package.json +5 -5
  104. package/packages/gsd-agent-modes/package.json +7 -7
  105. package/packages/mcp-server/dist/cli.js +10 -5
  106. package/packages/mcp-server/dist/cli.js.map +1 -1
  107. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
  108. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
  109. package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
  110. package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
  111. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  112. package/packages/mcp-server/dist/server.js +4 -0
  113. package/packages/mcp-server/dist/server.js.map +1 -1
  114. package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
  115. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  116. package/packages/mcp-server/dist/workflow-tools.js +99 -38
  117. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  118. package/packages/mcp-server/package.json +5 -4
  119. package/packages/native/package.json +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/index.d.ts +2 -0
  122. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/index.js +2 -0
  124. package/packages/pi-ai/dist/index.js.map +1 -1
  125. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/anthropic.js +12 -7
  127. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  128. package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
  129. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  130. package/packages/pi-ai/dist/providers/google-shared.js +12 -3
  131. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  132. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  133. package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
  134. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  135. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
  136. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
  137. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
  138. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
  139. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  140. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
  141. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  142. package/packages/pi-ai/package.json +1 -1
  143. package/packages/pi-coding-agent/package.json +7 -7
  144. package/packages/pi-tui/package.json +2 -2
  145. package/packages/rpc-client/package.json +2 -2
  146. package/pkg/package.json +1 -1
  147. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +11 -0
  148. package/src/resources/extensions/gsd/auto/orchestrator.ts +28 -10
  149. package/src/resources/extensions/gsd/auto/phases.ts +63 -24
  150. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  151. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +10 -16
  152. package/src/resources/extensions/gsd/auto-dispatch.ts +11 -10
  153. package/src/resources/extensions/gsd/auto-model-selection.ts +16 -7
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +21 -6
  155. package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
  156. package/src/resources/extensions/gsd/auto-verification.ts +18 -2
  157. package/src/resources/extensions/gsd/auto.ts +44 -1
  158. package/src/resources/extensions/gsd/blocked-models.ts +49 -0
  159. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
  160. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  161. package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
  162. package/src/resources/extensions/gsd/commands/context.ts +16 -2
  163. package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
  164. package/src/resources/extensions/gsd/consent-question.ts +15 -0
  165. package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
  166. package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -3
  167. package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
  168. package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
  169. package/src/resources/extensions/gsd/gsd-db.ts +4 -3
  170. package/src/resources/extensions/gsd/guided-flow.ts +21 -26
  171. package/src/resources/extensions/gsd/milestone-closeout.ts +97 -2
  172. package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
  173. package/src/resources/extensions/gsd/projection-flush.ts +20 -0
  174. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  176. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  177. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  178. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  180. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  181. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  182. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  183. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  184. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/run-uat.md +1 -1
  186. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  187. package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  188. package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
  189. package/src/resources/extensions/gsd/session-lock.ts +1 -1
  190. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +69 -0
  191. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +97 -0
  192. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
  193. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
  194. package/src/resources/extensions/gsd/tests/consent-question.test.ts +15 -0
  195. package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
  196. package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
  197. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
  198. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
  199. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  200. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
  201. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
  202. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
  203. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
  204. package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
  205. package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
  206. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
  207. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
  208. package/src/resources/extensions/gsd/tool-contract.ts +38 -3
  209. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  210. package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
  211. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
  212. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
  213. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
  214. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
  215. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  216. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
  217. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
  218. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
  219. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
  220. package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
  221. package/src/resources/extensions/shared/gsd-browser-cli.ts +23 -2
  222. package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
  223. package/src/resources/shared/package-manager-detection.ts +1 -1
  224. /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_buildManifest.js +0 -0
  225. /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_ssgManifest.js +0 -0
@@ -4,10 +4,13 @@
4
4
  // - preflight: dispatch git clean before complete-milestone agent (auto-dispatch)
5
5
  // - postUnit: git commit, artifact verify, DB settle, then GitHub finalize
6
6
  // - recovery: DB repair from artifacts, then GitHub finalize
7
+ import { existsSync } from "node:fs";
7
8
  import { loadFile } from "./files.js";
8
9
  import { resolveMilestoneFile } from "./paths.js";
9
- import { getMilestone, getClosedSliceIds, isDbAvailable } from "./gsd-db.js";
10
+ import { getMilestone, getClosedSliceIds, getLatestAssessmentByScope, getMilestoneSlices, isDbAvailable, } from "./gsd-db.js";
10
11
  import { isClosedStatus } from "./status-guards.js";
12
+ import { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
13
+ import { handleCompleteMilestone } from "./tools/complete-milestone.js";
11
14
  import { runSafely } from "./auto-utils.js";
12
15
  import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js";
13
16
  import { uatSignoffBlockerGuidance } from "./guidance.js";
@@ -19,6 +22,60 @@ import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
19
22
  import { commitPendingMilestoneCloseoutChanges, findMissingSummaries, isVerificationNotApplicable, readUatGateVerdict, } from "./auto-dispatch.js";
20
23
  const COMPLETE_MILESTONE_DB_SETTLE_MS = 1500;
21
24
  const COMPLETE_MILESTONE_DB_SETTLE_POLL_MS = 100;
25
+ /**
26
+ * True when a milestone is terminal for git cleanup (orphaned worktrees, stale branches).
27
+ * DB-authoritative (ADR-017): closed status, or validation-pass with all slices closed.
28
+ * When the DB is unavailable we cannot make this decision and conservatively
29
+ * return false so callers leave the worktree/branch alone instead of cleaning
30
+ * up based on parsed projections.
31
+ */
32
+ export async function isCompletedMilestoneTerminal(_basePath, milestoneId) {
33
+ if (!isDbAvailable())
34
+ return false;
35
+ const milestone = getMilestone(milestoneId);
36
+ if (!milestone)
37
+ return false;
38
+ if (isClosedStatus(milestone.status)) {
39
+ return true;
40
+ }
41
+ const validation = getLatestAssessmentByScope(milestoneId, "milestone-validation");
42
+ if (validation?.status !== "pass") {
43
+ return false;
44
+ }
45
+ const slices = getMilestoneSlices(milestoneId);
46
+ if (slices.length === 0)
47
+ return false;
48
+ return slices.every((slice) => isClosedStatus(slice.status));
49
+ }
50
+ /** Write a missing milestone SUMMARY projection when canonical DB closeout already settled. */
51
+ export async function repairMissingMilestoneSummaryProjection(basePath, milestoneId) {
52
+ const milestone = getMilestone(milestoneId);
53
+ if (!milestone) {
54
+ return { ok: false, error: `milestone not found: ${milestoneId}` };
55
+ }
56
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, milestoneId);
57
+ const summaryPath = resolveExpectedArtifactPath("complete-milestone", milestoneId, artifactBasePath);
58
+ if (summaryPath && existsSync(summaryPath)) {
59
+ return { ok: true };
60
+ }
61
+ const result = await handleCompleteMilestone({
62
+ milestoneId,
63
+ title: milestone.title,
64
+ oneLiner: "Canonical closeout completed; summary projection repaired automatically.",
65
+ narrative: "The workflow database recorded this milestone as complete, but the milestone SUMMARY artifact was missing on disk. " +
66
+ "Dispatch policy repaired the projection so closeout proof and cleanup can proceed.",
67
+ verificationPassed: true,
68
+ triggerReason: "closeout-projection-repair",
69
+ }, basePath);
70
+ if ("error" in result) {
71
+ return { ok: false, error: result.error };
72
+ }
73
+ const writtenSummaryPath = result.summaryPath;
74
+ if (result.stale || !writtenSummaryPath || !existsSync(writtenSummaryPath)) {
75
+ return { ok: false, error: "milestone SUMMARY projection write failed" };
76
+ }
77
+ return { ok: true };
78
+ }
22
79
  /**
23
80
  * True when the milestone is closed in the DB and the completion summary artifact exists.
24
81
  * Polls briefly so post-unit verification can observe the tool's DB write.
@@ -65,7 +122,21 @@ export async function evaluateCompleteMilestoneDispatch(ctx) {
65
122
  if (isDbAvailable()) {
66
123
  const milestone = getMilestone(mid);
67
124
  if (milestone && isClosedStatus(milestone.status)) {
68
- return { action: "skip" };
125
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, mid);
126
+ const summaryPath = resolveExpectedArtifactPath("complete-milestone", mid, artifactBasePath);
127
+ const summaryMissing = !summaryPath || !existsSync(summaryPath);
128
+ if (summaryMissing) {
129
+ const repair = await repairMissingMilestoneSummaryProjection(basePath, mid);
130
+ if (!repair.ok) {
131
+ logWarning("dispatch", `Milestone ${mid} is closed in DB but SUMMARY repair failed: ${repair.error}. Dispatching complete-milestone to retry.`);
132
+ }
133
+ else {
134
+ return { action: "skip" };
135
+ }
136
+ }
137
+ else {
138
+ return { action: "skip" };
139
+ }
69
140
  }
70
141
  }
71
142
  const closeoutGitStop = commitPendingMilestoneCloseoutChanges(basePath, mid);
@@ -5,7 +5,7 @@ import { isClosedStatus } from "./status-guards.js";
5
5
  import { transaction, getMilestone, getMilestoneSlices, getSlice, insertMilestone, insertSlice, upsertMilestonePlanning, upsertSlicePlanning, } from "./gsd-db.js";
6
6
  import { invalidateStateCache } from "./state.js";
7
7
  import { renderRoadmapFromDb } from "./markdown-renderer.js";
8
- import { renderAllProjections } from "./workflow-projections.js";
8
+ import { flushWorkflowProjections } from "./projection-flush.js";
9
9
  import { writeManifest } from "./workflow-manifest.js";
10
10
  import { appendEvent } from "./workflow-events.js";
11
11
  import { logWarning } from "./workflow-logger.js";
@@ -109,7 +109,7 @@ async function renderPlanArtifacts(basePath, params) {
109
109
  }
110
110
  async function runPostPlanHooks(basePath, params) {
111
111
  try {
112
- await renderAllProjections(basePath, params.milestoneId);
112
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
113
113
  writeManifest(basePath);
114
114
  appendEvent(basePath, {
115
115
  cmd: "plan-milestone",
@@ -0,0 +1,7 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Single workflow projection flush seam for mutation exits.
3
+ import { renderAllProjections } from "./workflow-projections.js";
4
+ export async function flushWorkflowProjections(basePath, scope) {
5
+ await renderAllProjections(basePath, scope.milestoneId);
6
+ return { milestoneId: scope.milestoneId };
7
+ }
@@ -49,4 +49,4 @@ Use `subagent` only when useful: reviewer, security, or tester. Apply findings b
49
49
 
50
50
  **You MUST call `gsd_slice_complete` with summary and UAT content only after verification passes.**
51
51
 
52
- When done, say: "Slice {{sliceId}} complete."
52
+ When done, say: "Slice {{sliceId}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -76,4 +76,4 @@ Keep about **{{verificationBudget}}** for verification and summary. If context i
76
76
 
77
77
  **You MUST call `gsd_task_complete` before finishing, including when the stale-path safety rule stops execution.**
78
78
 
79
- When done, say: "Task {{taskId}} complete."
79
+ When done, say: "Task {{taskId}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -118,4 +118,4 @@ If external API keys or secrets are required, use the inlined **Secrets Manifest
118
118
 
119
119
  If no external API keys or secrets are required, skip this step; do not create an empty manifest.
120
120
 
121
- When done, say: "Milestone {{milestoneId}} planned."
121
+ When done, say: "Milestone {{milestoneId}} planned." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -57,4 +57,4 @@ The slice directory already exists. Do not mkdir.
57
57
 
58
58
  **You MUST call `gsd_plan_slice` to persist planning state before finishing, unless the stale-path safety rule above stops the unit before safe planning can occur.**
59
59
 
60
- When done, say: "Slice {{sliceId}} planned."
60
+ When done, say: "Slice {{sliceId}} planned." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -37,4 +37,4 @@ You are executing a GSD quick task — a lightweight, focused unit of work outsi
37
37
  - <what was tested/verified>
38
38
  ```
39
39
 
40
- When done, say: "Quick task {{taskNum}} complete."
40
+ When done, say: "Quick task {{taskNum}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -67,4 +67,4 @@ If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, up
67
67
 
68
68
  **DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')`. Use `gsd_milestone_status` to read current milestone and slice state. All roadmap mutations go through `gsd_reassess_roadmap` — the tool writes to the DB and re-renders ROADMAP.md atomically.
69
69
 
70
- When done, say: "Roadmap reassessed."
70
+ When done, say: "Roadmap reassessed." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -77,4 +77,4 @@ The slice directory and tasks/ subdirectory already exist. Do NOT mkdir.
77
77
 
78
78
  **You MUST call `gsd_plan_slice` to persist planning state before finishing.** After success, the pipeline clears the sketch flag on next state derivation; the on-disk PLAN file is the signal.
79
79
 
80
- When done, say: "Slice {{sliceId}} refined."
80
+ When done, say: "Slice {{sliceId}} refined." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -38,4 +38,4 @@ Consider these captures when rewriting the remaining tasks — they represent th
38
38
  4. If any incomplete task had a `T0x-PLAN.md`, remove or rewrite it to match the new task description.
39
39
  5. Do not commit manually — the system auto-commits your changes after this unit completes.
40
40
 
41
- When done, say: "Slice {{sliceId}} replanned."
41
+ When done, say: "Slice {{sliceId}} replanned." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -46,4 +46,4 @@ Then research the codebase and relevant technologies. Narrate key findings and s
46
46
 
47
47
  **You MUST call `gsd_summary_save` with the research content before finishing.**
48
48
 
49
- When done, say: "Milestone {{milestoneId}} researched."
49
+ When done, say: "Milestone {{milestoneId}} researched." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -55,4 +55,4 @@ The slice directory already exists at `{{slicePath}}/`. Do NOT mkdir.
55
55
 
56
56
  **You MUST call `gsd_summary_save` with the research content before finishing.**
57
57
 
58
- When done, say: "Slice {{sliceId}} researched."
58
+ When done, say: "Slice {{sliceId}} researched." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -30,4 +30,4 @@ An override was issued by the user that changes a fundamental decision or approa
30
30
 
31
31
  **You MUST update the relevant documents AND mark overrides as resolved in `{{overridesPath}}` before finishing.**
32
32
 
33
- When done, say: "Override applied across all documents."
33
+ When done, say: "Override applied across all documents." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -100,4 +100,4 @@ checks: [{
100
100
 
101
101
  **You MUST call `gsd_uat_result_save` before finishing. Do not write the assessment file directly, and do not call `gsd_summary_save` as a substitute.**
102
102
 
103
- When done, say: "UAT {{sliceId}} complete."
103
+ When done, say: "UAT {{sliceId}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -65,4 +65,4 @@ Classify each capture as one of:
65
65
 
66
66
  **Important:** Do NOT execute any resolutions. Only classify and update CAPTURES.md. Resolution execution happens separately (in auto-mode dispatch or manually by the user).
67
67
 
68
- When done, say: "Triage complete."
68
+ When done, say: "Triage complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -87,4 +87,4 @@ If verdict is `needs-remediation`:
87
87
 
88
88
  **File system safety:** When scanning milestone directories, use `ls` or `find` first. Never pass a directory path such as `tasks/` or `slices/` directly to `read`; it only accepts files.
89
89
 
90
- When done, say: "Milestone {{milestoneId}} validation complete — verdict: <verdict>."
90
+ When done, say: "Milestone {{milestoneId}} validation complete — verdict: <verdict>." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -161,6 +161,26 @@ function parseTableSlices(section) {
161
161
  }
162
162
  return slices;
163
163
  }
164
+ function looksLikeTable(section) {
165
+ const lines = section.split("\n");
166
+ // Checkbox format takes precedence — embedded demo tables must not switch mode (#721).
167
+ if (lines.some(line => /^\s*-\s+\[[ xX]\]/.test(line))) {
168
+ return false;
169
+ }
170
+ const pipeLines = lines.filter(line => /^\s*\|/.test(line));
171
+ if (pipeLines.length < 2)
172
+ return false;
173
+ const hasSeparatorRow = pipeLines.some((line, index) => {
174
+ if (index === 0)
175
+ return false;
176
+ const cells = line.split("|").map(c => c.trim()).filter(Boolean);
177
+ return /^\s*\|[\s:-]+\|/.test(line) && cells.length >= 2 && cells.every(c => /^[\s:-]+$/.test(c));
178
+ });
179
+ if (hasSeparatorRow)
180
+ return true;
181
+ // Tables without a separator row still expose slice IDs in data rows.
182
+ return pipeLines.some(line => /\bS\d+\b/.test(line));
183
+ }
164
184
  export function parseRoadmapSlices(content) {
165
185
  const slicesSection = extractSlicesSection(content);
166
186
  if (!slicesSection) {
@@ -171,9 +191,11 @@ export function parseRoadmapSlices(content) {
171
191
  }
172
192
  // Try table format first — if the section contains pipe-delimited rows with
173
193
  // slice IDs, parse them as a table (#1736).
174
- const tableSlices = parseTableSlices(slicesSection);
175
- if (tableSlices.length > 0) {
176
- return tableSlices;
194
+ if (looksLikeTable(slicesSection)) {
195
+ const tableSlices = parseTableSlices(slicesSection);
196
+ if (tableSlices.length > 0) {
197
+ return tableSlices;
198
+ }
177
199
  }
178
200
  // Standard checkbox format
179
201
  const slices = [];
@@ -329,7 +329,7 @@ export function acquireSessionLock(basePath) {
329
329
  // #3218: Provide actionable workaround when lock recovery fails
330
330
  const lockDirPath = lockTarget + ".lock";
331
331
  const reason = existingPid
332
- ? `Another auto-mode session (PID ${existingPid}) appears to be running.\nStop it with \`kill ${existingPid}\` before starting a new session.`
332
+ ? `Another auto-mode session (PID ${existingPid}) appears to be running.\nRun \`/gsd stop\` for graceful shutdown, or choose "Force start" from \`/gsd auto\` to terminate it.`
333
333
  : `Another auto-mode session lock is stuck on this project.\nRun: rm -rf "${lockDirPath}" && rm -f "${lp}"`;
334
334
  return { acquired: false, reason, existingPid };
335
335
  }
@@ -1,8 +1,8 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: ADR-015 Tool Contract module for Unit prompt, policy, and tool parity.
3
3
  import { resolveManifest, } from "./unit-context-manifest.js";
4
- import { getRequiredWorkflowToolsForAutoUnit } from "./workflow-mcp.js";
5
- import { getUnitToolSurfaceContract } from "./unit-tool-contracts.js";
4
+ import { getWorkflowTransportSupportError, } from "./workflow-mcp.js";
5
+ import { getRequiredWorkflowToolsForUnit, getUnitToolSurfaceContract, } from "./unit-tool-contracts.js";
6
6
  import { WHOLE_FILE_OBSERVATION_MAX_BYTES, WHOLE_FILE_OBSERVATION_MAX_LINES, } from "./source-observations.js";
7
7
  export function compileUnitContextContract(unitType) {
8
8
  const manifest = resolveManifest(unitType);
@@ -15,6 +15,17 @@ export function compileUnitContextContract(unitType) {
15
15
  }
16
16
  return { ok: true, contract: buildPromptContextContract(unitType, manifest) };
17
17
  }
18
+ export function getUnitWorkflowDispatchReadinessError(input) {
19
+ return getWorkflowTransportSupportError(input.provider, getRequiredWorkflowToolsForUnit(input.unitType), {
20
+ projectRoot: input.projectRoot,
21
+ env: input.env,
22
+ surface: input.surface,
23
+ unitType: input.unitType,
24
+ authMode: input.authMode,
25
+ baseUrl: input.baseUrl,
26
+ activeTools: input.activeTools,
27
+ });
28
+ }
18
29
  export function compileUnitToolContract(unitType) {
19
30
  const manifest = resolveManifest(unitType);
20
31
  const surfaceContract = getUnitToolSurfaceContract(unitType);
@@ -25,7 +36,7 @@ export function compileUnitToolContract(unitType) {
25
36
  detail: `No Unit manifest is registered for ${unitType}`,
26
37
  };
27
38
  }
28
- const requiredWorkflowTools = getRequiredWorkflowToolsForAutoUnit(unitType);
39
+ const requiredWorkflowTools = getRequiredWorkflowToolsForUnit(unitType);
29
40
  const forbiddenWorkflowTools = Object.entries(surfaceContract?.forbiddenGsdTools ?? {})
30
41
  .map(([name, reason]) => ({ name, reason }));
31
42
  const closeoutTools = requiredWorkflowTools.filter((tool) => /^gsd_(?:task|slice|milestone|complete|validate|save|summary|uat)/.test(tool));
@@ -16,7 +16,8 @@ import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
16
16
  import { isClosedStatus } from "../status-guards.js";
17
17
  import { saveFile, clearParseCache } from "../files.js";
18
18
  import { invalidateStateCache } from "../state.js";
19
- import { renderAllProjections, stripIdPrefix } from "../workflow-projections.js";
19
+ import { stripIdPrefix } from "../workflow-projections.js";
20
+ import { flushWorkflowProjections } from "../projection-flush.js";
20
21
  import { writeManifest } from "../workflow-manifest.js";
21
22
  import { appendEvent } from "../workflow-events.js";
22
23
  import { logWarning, logError } from "../workflow-logger.js";
@@ -166,7 +167,7 @@ export async function handleCompleteMilestone(params, basePath) {
166
167
  // Separate try/catch per step so a projection failure doesn't prevent
167
168
  // the event log entry (critical for worktree reconciliation).
168
169
  try {
169
- await renderAllProjections(artifactBasePath, params.milestoneId);
170
+ await flushWorkflowProjections(artifactBasePath, { milestoneId: params.milestoneId });
170
171
  }
171
172
  catch (projErr) {
172
173
  logWarning("tool", `complete-milestone projection warning: ${projErr.message}`);
@@ -21,7 +21,7 @@ import { classifyUatContent, escalatesArtifactUatToBrowser } from "../uat-policy
21
21
  import { invalidateStateCache } from "../state.js";
22
22
  import { renderRoadmapFromDb, roadmapRenderMarksSliceDone } from "../markdown-renderer.js";
23
23
  import { isStaleWrite } from "../auto/turn-epoch.js";
24
- import { renderAllProjections } from "../workflow-projections.js";
24
+ import { flushWorkflowProjections } from "../projection-flush.js";
25
25
  import { writeManifest } from "../workflow-manifest.js";
26
26
  import { appendEvent } from "../workflow-events.js";
27
27
  import { logWarning, logError } from "../workflow-logger.js";
@@ -448,7 +448,7 @@ export async function handleCompleteSlice(params, basePath) {
448
448
  // Separate try/catch per step so a projection failure doesn't prevent
449
449
  // the event log entry (critical for worktree reconciliation).
450
450
  try {
451
- await renderAllProjections(artifactBasePath, params.milestoneId);
451
+ await flushWorkflowProjections(artifactBasePath, { milestoneId: params.milestoneId });
452
452
  }
453
453
  catch (projErr) {
454
454
  logWarning("tool", `complete-slice projection warning for ${params.milestoneId}/${params.sliceId}: ${projErr.message}`);
@@ -18,7 +18,8 @@ import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
18
18
  import { saveFile, clearParseCache } from "../files.js";
19
19
  import { invalidateStateCache } from "../state.js";
20
20
  import { renderPlanCheckboxes } from "../markdown-renderer.js";
21
- import { renderAllProjections, renderSummaryContent } from "../workflow-projections.js";
21
+ import { renderSummaryContent } from "../workflow-projections.js";
22
+ import { flushWorkflowProjections } from "../projection-flush.js";
22
23
  import { writeManifest } from "../workflow-manifest.js";
23
24
  import { appendEvent } from "../workflow-events.js";
24
25
  import { logWarning, logError } from "../workflow-logger.js";
@@ -358,7 +359,7 @@ export async function handleCompleteTask(params, basePath) {
358
359
  // Separate try/catch per step so a projection failure doesn't prevent
359
360
  // the event log entry (critical for worktree reconciliation).
360
361
  try {
361
- await renderAllProjections(artifactBasePath, params.milestoneId);
362
+ await flushWorkflowProjections(artifactBasePath, { milestoneId: params.milestoneId });
362
363
  }
363
364
  catch (projErr) {
364
365
  logWarning("tool", `complete-task projection warning: ${projErr.message}`);
@@ -7,7 +7,7 @@ import { getGateIdsForTurn } from "../gate-registry.js";
7
7
  import { transaction, getMilestone, getSlice, getSliceTasks, insertTask, upsertSlicePlanning, upsertTaskPlanning, insertGateRow, updateSliceStatus, setSliceSketchFlag, deleteTask, deleteArtifactByPath, } from "../gsd-db.js";
8
8
  import { invalidateStateCache } from "../state.js";
9
9
  import { renderPlanFromDb } from "../markdown-renderer.js";
10
- import { renderAllProjections } from "../workflow-projections.js";
10
+ import { flushWorkflowProjections } from "../projection-flush.js";
11
11
  import { writeManifest } from "../workflow-manifest.js";
12
12
  import { appendEvent } from "../workflow-events.js";
13
13
  import { logWarning } from "../workflow-logger.js";
@@ -367,7 +367,7 @@ export async function handlePlanSlice(rawParams, basePath) {
367
367
  clearParseCache();
368
368
  // ── Post-mutation hook: projections, manifest, event log ─────────────
369
369
  try {
370
- await renderAllProjections(basePath, params.milestoneId);
370
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
371
371
  writeManifest(basePath);
372
372
  appendEvent(basePath, {
373
373
  cmd: "plan-slice",
@@ -4,7 +4,7 @@ import { isNonEmptyString, validateStringArray } from "../validation.js";
4
4
  import { transaction, getSlice, getTask, insertTask, upsertTaskPlanning } from "../gsd-db.js";
5
5
  import { invalidateStateCache } from "../state.js";
6
6
  import { renderTaskPlanFromDb } from "../markdown-renderer.js";
7
- import { renderAllProjections } from "../workflow-projections.js";
7
+ import { flushWorkflowProjections } from "../projection-flush.js";
8
8
  import { writeManifest } from "../workflow-manifest.js";
9
9
  import { appendEvent } from "../workflow-events.js";
10
10
  import { logWarning } from "../workflow-logger.js";
@@ -110,7 +110,7 @@ export async function handlePlanTask(rawParams, basePath) {
110
110
  clearParseCache();
111
111
  // ── Post-mutation hook: projections, manifest, event log ─────────────
112
112
  try {
113
- await renderAllProjections(basePath, params.milestoneId);
113
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
114
114
  writeManifest(basePath);
115
115
  appendEvent(basePath, {
116
116
  cmd: "plan-task",
@@ -6,7 +6,7 @@ import { isNonEmptyString } from "../validation.js";
6
6
  import { transaction, getMilestone, getMilestoneSlices, getSlice, insertSlice, updateSliceFields, insertAssessment, deleteAssessmentByScope, deleteSlice, } from "../gsd-db.js";
7
7
  import { invalidateStateCache } from "../state.js";
8
8
  import { renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-renderer.js";
9
- import { renderAllProjections } from "../workflow-projections.js";
9
+ import { flushWorkflowProjections } from "../projection-flush.js";
10
10
  import { writeManifest } from "../workflow-manifest.js";
11
11
  import { appendEvent } from "../workflow-events.js";
12
12
  import { logWarning } from "../workflow-logger.js";
@@ -234,7 +234,7 @@ export async function handleReassessRoadmap(rawParams, basePath) {
234
234
  clearParseCache();
235
235
  // ── Post-mutation hook: projections, manifest, event log ─────
236
236
  try {
237
- await renderAllProjections(basePath, params.milestoneId);
237
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
238
238
  writeManifest(basePath);
239
239
  appendEvent(basePath, {
240
240
  cmd: "reassess-roadmap",
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { getMilestoneSlices, getSliceTasks, reopenMilestoneCascade, } from "../gsd-db.js";
11
11
  import { invalidateStateCache } from "../state.js";
12
- import { renderAllProjections } from "../workflow-projections.js";
12
+ import { flushWorkflowProjections } from "../projection-flush.js";
13
13
  import { writeManifest } from "../workflow-manifest.js";
14
14
  import { appendEvent } from "../workflow-events.js";
15
15
  import { logWarning } from "../workflow-logger.js";
@@ -74,7 +74,7 @@ export async function handleReopenMilestone(params, basePath) {
74
74
  clearPathCache();
75
75
  // ── Post-mutation hook ───────────────────────────────────────────────────
76
76
  try {
77
- await renderAllProjections(basePath, params.milestoneId);
77
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
78
78
  writeManifest(basePath);
79
79
  appendEvent(basePath, {
80
80
  cmd: "reopen-milestone",
@@ -11,7 +11,7 @@
11
11
  // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
12
12
  import { getSliceTasks, reopenSliceCascade, } from "../gsd-db.js";
13
13
  import { invalidateStateCache } from "../state.js";
14
- import { renderAllProjections } from "../workflow-projections.js";
14
+ import { flushWorkflowProjections } from "../projection-flush.js";
15
15
  import { writeManifest } from "../workflow-manifest.js";
16
16
  import { appendEvent } from "../workflow-events.js";
17
17
  import { logWarning } from "../workflow-logger.js";
@@ -72,7 +72,7 @@ export async function handleReopenSlice(params, basePath) {
72
72
  clearPathCache();
73
73
  // ── Post-mutation hook ───────────────────────────────────────────────────
74
74
  try {
75
- await renderAllProjections(basePath, params.milestoneId);
75
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
76
76
  writeManifest(basePath);
77
77
  appendEvent(basePath, {
78
78
  cmd: "reopen-slice",
@@ -11,7 +11,7 @@
11
11
  import { getMilestone, getSlice, getTask, updateTaskStatus, transaction, } from "../gsd-db.js";
12
12
  import { invalidateStateCache } from "../state.js";
13
13
  import { isClosedStatus } from "../status-guards.js";
14
- import { renderAllProjections } from "../workflow-projections.js";
14
+ import { flushWorkflowProjections } from "../projection-flush.js";
15
15
  import { writeManifest } from "../workflow-manifest.js";
16
16
  import { appendEvent } from "../workflow-events.js";
17
17
  import { logWarning } from "../workflow-logger.js";
@@ -83,7 +83,7 @@ export async function handleReopenTask(params, basePath) {
83
83
  clearPathCache();
84
84
  // ── Post-mutation hook ───────────────────────────────────────────────────
85
85
  try {
86
- await renderAllProjections(basePath, params.milestoneId);
86
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
87
87
  writeManifest(basePath);
88
88
  appendEvent(basePath, {
89
89
  cmd: "reopen-task",
@@ -4,7 +4,7 @@ import { invalidateStateCache } from "../state.js";
4
4
  import { isClosedStatus } from "../status-guards.js";
5
5
  import { isNonEmptyString } from "../validation.js";
6
6
  import { renderPlanFromDb, renderReplanFromDb } from "../markdown-renderer.js";
7
- import { renderAllProjections } from "../workflow-projections.js";
7
+ import { flushWorkflowProjections } from "../projection-flush.js";
8
8
  import { writeManifest } from "../workflow-manifest.js";
9
9
  import { appendEvent } from "../workflow-events.js";
10
10
  import { logWarning } from "../workflow-logger.js";
@@ -162,7 +162,7 @@ export async function handleReplanSlice(rawParams, basePath) {
162
162
  clearParseCache();
163
163
  // ── Post-mutation hook: projections, manifest, event log ─────
164
164
  try {
165
- await renderAllProjections(basePath, params.milestoneId);
165
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
166
166
  writeManifest(basePath);
167
167
  appendEvent(basePath, {
168
168
  cmd: "replan-slice",
@@ -6,9 +6,10 @@ import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot, should
6
6
  import { getActiveRequirements, getAllMilestones, getMilestone, getSliceStatusSummary, getSliceTaskCounts, insertMilestone, insertAssessment, insertGateRun, readTransaction, saveGateResult, upsertQualityGate, } from "../gsd-db.js";
7
7
  import { GATE_REGISTRY } from "../gate-registry.js";
8
8
  import { generateRequirementsMd, saveArtifactToDb } from "../db-writer.js";
9
- import { clearPathCache, relSliceFile, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
9
+ import { clearPathCache, normalizeRealPath, relSliceFile, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
10
10
  import { saveFile, clearParseCache } from "../files.js";
11
11
  import { unlinkSync } from "node:fs";
12
+ import { hostname } from "node:os";
12
13
  import { join } from "node:path";
13
14
  import { handleCompleteMilestone } from "./complete-milestone.js";
14
15
  import { handleCompleteTask } from "./complete-task.js";
@@ -25,9 +26,11 @@ import { logError, logWarning } from "../workflow-logger.js";
25
26
  import { invalidateStateCache } from "../state.js";
26
27
  import { loadEffectiveGSDPreferences } from "../preferences.js";
27
28
  import { parseProject } from "../schemas/parsers.js";
28
- import { getAutoRuntimeSnapshot } from "../auto-runtime-state.js";
29
+ import { autoSession, getAutoRuntimeSnapshot, isAutoActive } from "../auto-runtime-state.js";
29
30
  import { renderPlanFromDb } from "../markdown-renderer.js";
30
31
  import { prepareUatRun, saveUatAttemptArtifact, } from "../uat-run.js";
32
+ import { registerAutoWorker, markWorkerStopping, getAutoWorker } from "../db/auto-workers.js";
33
+ import { claimMilestoneLease, releaseMilestoneLease, getMilestoneLease, refreshMilestoneLease, milestoneLeaseTtlSeconds, } from "../db/milestone-leases.js";
31
34
  export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [
32
35
  "SUMMARY",
33
36
  "RESEARCH",
@@ -61,6 +64,19 @@ function blockIfWrongAutoUnit(requiredUnitType, operation) {
61
64
  isError: true,
62
65
  };
63
66
  }
67
+ function milestoneLeaseConflictResult(milestoneId, byWorker, expiresAt) {
68
+ return {
69
+ content: [{ type: "text", text: `Milestone ${milestoneId} is currently leased by ${byWorker}. Retry after ${expiresAt}.` }],
70
+ details: {
71
+ operation: "plan_milestone",
72
+ error: "milestone_lease_conflict",
73
+ milestoneId,
74
+ byWorker,
75
+ expiresAt,
76
+ },
77
+ isError: true,
78
+ };
79
+ }
64
80
  function registerProjectMilestoneSequence(content) {
65
81
  const parsed = parseProject(content);
66
82
  const registered = [];
@@ -1066,7 +1082,45 @@ export async function executePlanMilestone(params, basePath = process.cwd()) {
1066
1082
  isError: true,
1067
1083
  };
1068
1084
  }
1085
+ let workerId = null;
1086
+ let acquiredToken = null;
1087
+ let leaseRefreshTimer;
1069
1088
  try {
1089
+ // Re-read at the gate so a peer-created milestone is not treated as fresh.
1090
+ const milestoneExists = getMilestone(params.milestoneId) !== null;
1091
+ if (milestoneExists) {
1092
+ const heldLease = getMilestoneLease(params.milestoneId);
1093
+ if (heldLease?.status === "held" && Date.parse(heldLease.expires_at) > Date.now()) {
1094
+ const holder = getAutoWorker(heldLease.worker_id);
1095
+ // Let the one-shot claim path recover stale same-process worker rows.
1096
+ const projectRoot = normalizeRealPath(basePath);
1097
+ const isOurAutoLease = isAutoActive() && heldLease.worker_id === autoSession.workerId;
1098
+ const holderIsOneShotReentrantPeer = !isAutoActive()
1099
+ && !!holder
1100
+ && holder.host === hostname()
1101
+ && holder.pid === process.pid
1102
+ && holder.project_root_realpath === projectRoot;
1103
+ if (holder?.status === "active" && !isOurAutoLease && !holderIsOneShotReentrantPeer) {
1104
+ return milestoneLeaseConflictResult(params.milestoneId, heldLease.worker_id, heldLease.expires_at);
1105
+ }
1106
+ }
1107
+ }
1108
+ // Fresh creation cannot claim a lease because the FK row does not exist.
1109
+ // In-process auto already owns its lease; re-claiming would bump its token.
1110
+ if (!isAutoActive() && milestoneExists) {
1111
+ workerId = registerAutoWorker({ projectRootRealpath: normalizeRealPath(basePath) });
1112
+ const lease = claimMilestoneLease(workerId, params.milestoneId);
1113
+ if (!lease.ok) {
1114
+ return milestoneLeaseConflictResult(params.milestoneId, lease.byWorker, lease.expiresAt);
1115
+ }
1116
+ acquiredToken = lease.token;
1117
+ const leaseRefreshMs = (milestoneLeaseTtlSeconds() / 2) * 1000;
1118
+ leaseRefreshTimer = setInterval(() => {
1119
+ if (acquiredToken !== null && workerId !== null) {
1120
+ refreshMilestoneLease(workerId, params.milestoneId, acquiredToken);
1121
+ }
1122
+ }, leaseRefreshMs);
1123
+ }
1070
1124
  const result = await handlePlanMilestone(params, basePath);
1071
1125
  if ("error" in result) {
1072
1126
  return {
@@ -1093,6 +1147,17 @@ export async function executePlanMilestone(params, basePath = process.cwd()) {
1093
1147
  isError: true,
1094
1148
  };
1095
1149
  }
1150
+ finally {
1151
+ if (leaseRefreshTimer !== undefined) {
1152
+ clearInterval(leaseRefreshTimer);
1153
+ }
1154
+ if (workerId !== null && acquiredToken !== null) {
1155
+ releaseMilestoneLease(workerId, params.milestoneId, acquiredToken);
1156
+ }
1157
+ if (workerId !== null) {
1158
+ markWorkerStopping(workerId);
1159
+ }
1160
+ }
1096
1161
  }
1097
1162
  export async function executePlanSlice(params, basePath = process.cwd()) {
1098
1163
  const dbAvailable = await ensureDbOpen(basePath);