@opengsd/gsd-pi 1.4.0-dev.5fc22c6f → 1.4.0-dev.c18009cd

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 (135) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +58 -4
  3. package/dist/resources/extensions/gsd/artifact-verification.js +17 -5
  4. package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
  5. package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
  6. package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
  8. package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
  9. package/dist/resources/extensions/gsd/auto.js +10 -3
  10. package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
  11. package/dist/resources/extensions/gsd/doctor-engine-checks.js +174 -16
  12. package/dist/resources/extensions/gsd/gsd-db.js +52 -0
  13. package/dist/resources/extensions/gsd/migrate-external.js +13 -4
  14. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
  15. package/dist/resources/extensions/gsd/repo-identity.js +16 -0
  16. package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
  17. package/dist/resources/extensions/gsd/unit-registry.js +27 -2
  18. package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
  19. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  20. package/dist/web/standalone/.next/BUILD_ID +1 -1
  21. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  22. package/dist/web/standalone/.next/build-manifest.json +2 -2
  23. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  24. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.html +1 -1
  41. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  48. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  50. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  51. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  52. package/integrations/hermes/CONTEXT.md +47 -0
  53. package/integrations/hermes/README.md +21 -0
  54. package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
  55. package/integrations/hermes/docs/setup.md +4 -0
  56. package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
  57. package/integrations/hermes/open_gsd_hermes/config.py +2 -0
  58. package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
  59. package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
  60. package/integrations/hermes/plugin.yaml +4 -0
  61. package/integrations/hermes/tests/test_config.py +6 -0
  62. package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
  63. package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
  64. package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
  65. package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
  66. package/integrations/hermes/tests/test_milestone_client.py +447 -0
  67. package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
  68. package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
  69. package/package.json +1 -1
  70. package/packages/cloud-mcp-gateway/package.json +2 -2
  71. package/packages/contracts/package.json +1 -1
  72. package/packages/daemon/dist/session-manager.js +1 -1
  73. package/packages/daemon/dist/session-manager.js.map +1 -1
  74. package/packages/daemon/package.json +4 -4
  75. package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
  76. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
  77. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
  78. package/packages/gsd-agent-core/package.json +5 -5
  79. package/packages/gsd-agent-modes/package.json +7 -7
  80. package/packages/mcp-server/dist/server.d.ts +18 -5
  81. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  82. package/packages/mcp-server/dist/server.js +46 -5
  83. package/packages/mcp-server/dist/server.js.map +1 -1
  84. package/packages/mcp-server/dist/session-manager.js +1 -1
  85. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  86. package/packages/mcp-server/package.json +4 -4
  87. package/packages/native/package.json +1 -1
  88. package/packages/pi-agent-core/package.json +1 -1
  89. package/packages/pi-ai/package.json +1 -1
  90. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
  91. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
  95. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  96. package/packages/pi-coding-agent/package.json +7 -7
  97. package/packages/pi-tui/package.json +2 -2
  98. package/packages/rpc-client/package.json +2 -2
  99. package/pkg/package.json +1 -1
  100. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +64 -3
  101. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +212 -39
  102. package/src/resources/extensions/gsd/artifact-verification.ts +16 -4
  103. package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
  104. package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
  105. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  106. package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
  107. package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
  108. package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
  109. package/src/resources/extensions/gsd/auto.ts +9 -3
  110. package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
  111. package/src/resources/extensions/gsd/doctor-engine-checks.ts +230 -21
  112. package/src/resources/extensions/gsd/gsd-db.ts +72 -0
  113. package/src/resources/extensions/gsd/migrate-external.ts +14 -4
  114. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
  115. package/src/resources/extensions/gsd/repo-identity.ts +17 -0
  116. package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
  117. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
  118. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
  119. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  120. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +335 -0
  121. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
  122. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
  123. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
  124. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
  125. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
  126. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
  127. package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
  128. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
  129. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
  130. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
  131. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
  132. package/src/resources/extensions/gsd/unit-registry.ts +34 -2
  133. package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
  134. /package/dist/web/standalone/.next/static/{kIEFsCryyGy6RzifULbJl → L2UMqXELiDH3oz-7yguXp}/_buildManifest.js +0 -0
  135. /package/dist/web/standalone/.next/static/{kIEFsCryyGy6RzifULbJl → L2UMqXELiDH3oz-7yguXp}/_ssgManifest.js +0 -0
@@ -20,8 +20,9 @@
20
20
  //
21
21
  // Not yet declared here (remaining ADR-033 steps): the manifest data
22
22
  // (`UNIT_MANIFESTS` stays in unit-context-manifest.ts, already type-enforced
23
- // against the registry's `UnitType`) and prompt-template association (still
24
- // implicit in auto-prompts.ts builders).
23
+ // against the registry's `UnitType`). Prompt-template association is declared
24
+ // for direct one-template units; conditional/composite prompt selection stays
25
+ // with the prompt builders until it has a richer registry shape.
25
26
 
26
27
  import type { CanonicalWorkflowToolName } from "@opengsd/contracts";
27
28
  import { BROWSER_CONTRACT_TOOL_NAMES } from "../shared/browser-contract.js";
@@ -61,6 +62,12 @@ export interface UnitDescriptor {
61
62
  * resolves against session defaults).
62
63
  */
63
64
  readonly phaseChain: readonly GSDModelPhaseKey[] | null;
65
+ /**
66
+ * Prompt template id passed to loadPrompt for units with a direct
67
+ * one-template builder. Omitted for conditional, composite, or not-yet
68
+ * verified prompt associations.
69
+ */
70
+ readonly promptTemplate?: string;
64
71
  /** `null` = the unit has no scoped gsd-tool contract. */
65
72
  readonly toolContract: UnitToolSurfaceContract | null;
66
73
  }
@@ -97,6 +104,7 @@ export const UNIT_REGISTRY = {
97
104
  kind: "primary",
98
105
  scopeClass: "standard",
99
106
  phaseChain: ["research"],
107
+ promptTemplate: "research-milestone",
100
108
  toolContract: {
101
109
  allowedGsdTools: [
102
110
  "gsd_summary_save",
@@ -112,6 +120,7 @@ export const UNIT_REGISTRY = {
112
120
  kind: "primary",
113
121
  scopeClass: "standard",
114
122
  phaseChain: ["planning"],
123
+ promptTemplate: "plan-milestone",
115
124
  toolContract: {
116
125
  allowedGsdTools: [
117
126
  "gsd_milestone_status",
@@ -160,6 +169,7 @@ export const UNIT_REGISTRY = {
160
169
  kind: "primary",
161
170
  scopeClass: "section-close",
162
171
  phaseChain: ["validation", "planning"],
172
+ promptTemplate: "validate-milestone",
163
173
  toolContract: {
164
174
  allowedGsdTools: [
165
175
  "gsd_milestone_status",
@@ -177,6 +187,7 @@ export const UNIT_REGISTRY = {
177
187
  kind: "primary",
178
188
  scopeClass: "standard",
179
189
  phaseChain: ["completion"],
190
+ promptTemplate: "complete-milestone",
180
191
  toolContract: {
181
192
  allowedGsdTools: [
182
193
  "gsd_milestone_status",
@@ -200,6 +211,7 @@ export const UNIT_REGISTRY = {
200
211
  kind: "primary",
201
212
  scopeClass: "standard",
202
213
  phaseChain: ["research"],
214
+ promptTemplate: "research-slice",
203
215
  toolContract: {
204
216
  allowedGsdTools: [
205
217
  "gsd_milestone_status",
@@ -216,6 +228,7 @@ export const UNIT_REGISTRY = {
216
228
  kind: "primary",
217
229
  scopeClass: "standard",
218
230
  phaseChain: ["planning"],
231
+ promptTemplate: "plan-slice",
219
232
  toolContract: {
220
233
  allowedGsdTools: [
221
234
  "gsd_milestone_status",
@@ -234,6 +247,7 @@ export const UNIT_REGISTRY = {
234
247
  kind: "primary",
235
248
  scopeClass: "standard",
236
249
  phaseChain: ["planning"],
250
+ promptTemplate: "refine-slice",
237
251
  toolContract: {
238
252
  allowedGsdTools: [
239
253
  "gsd_milestone_status",
@@ -250,6 +264,7 @@ export const UNIT_REGISTRY = {
250
264
  kind: "primary",
251
265
  scopeClass: "standard",
252
266
  phaseChain: ["planning"],
267
+ promptTemplate: "replan-slice",
253
268
  toolContract: {
254
269
  allowedGsdTools: ["gsd_replan_slice", "gsd_decision_save"],
255
270
  requiredWorkflowTools: ["gsd_replan_slice"],
@@ -259,6 +274,7 @@ export const UNIT_REGISTRY = {
259
274
  kind: "primary",
260
275
  scopeClass: "section-close",
261
276
  phaseChain: ["completion"],
277
+ promptTemplate: "complete-slice",
262
278
  toolContract: {
263
279
  allowedGsdTools: [
264
280
  "gsd_milestone_status",
@@ -293,6 +309,7 @@ export const UNIT_REGISTRY = {
293
309
  kind: "primary",
294
310
  scopeClass: "standard",
295
311
  phaseChain: ["validation", "planning"],
312
+ promptTemplate: "reassess-roadmap",
296
313
  toolContract: {
297
314
  allowedGsdTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
298
315
  requiredWorkflowTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
@@ -302,6 +319,7 @@ export const UNIT_REGISTRY = {
302
319
  kind: "primary",
303
320
  scopeClass: "execute-task",
304
321
  phaseChain: ["execution"],
322
+ promptTemplate: "execute-task",
305
323
  toolContract: {
306
324
  allowedGsdTools: [
307
325
  "gsd_task_complete",
@@ -346,6 +364,7 @@ export const UNIT_REGISTRY = {
346
364
  kind: "primary",
347
365
  scopeClass: "execute-task",
348
366
  phaseChain: ["execution"],
367
+ promptTemplate: "reactive-execute",
349
368
  toolContract: {
350
369
  allowedGsdTools: [
351
370
  "gsd_milestone_status",
@@ -361,6 +380,7 @@ export const UNIT_REGISTRY = {
361
380
  kind: "primary",
362
381
  scopeClass: "standard",
363
382
  phaseChain: ["uat", "completion"],
383
+ promptTemplate: "run-uat",
364
384
  toolContract: {
365
385
  allowedGsdTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent"],
366
386
  requiredWorkflowTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES],
@@ -375,6 +395,7 @@ export const UNIT_REGISTRY = {
375
395
  kind: "primary",
376
396
  scopeClass: "standard",
377
397
  phaseChain: ["validation", "planning"],
398
+ promptTemplate: "gate-evaluate",
378
399
  toolContract: {
379
400
  allowedGsdTools: ["gsd_save_gate_result"],
380
401
  requiredWorkflowTools: ["gsd_save_gate_result"],
@@ -384,6 +405,7 @@ export const UNIT_REGISTRY = {
384
405
  kind: "primary",
385
406
  scopeClass: "standard",
386
407
  phaseChain: ["validation", "planning"],
408
+ promptTemplate: "rewrite-docs",
387
409
  toolContract: {
388
410
  allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
389
411
  requiredWorkflowTools: [],
@@ -408,6 +430,7 @@ export const UNIT_REGISTRY = {
408
430
  kind: "primary",
409
431
  scopeClass: "standard",
410
432
  phaseChain: ["discuss", "planning"],
433
+ promptTemplate: "guided-workflow-preferences",
411
434
  toolContract: {
412
435
  allowedGsdTools: ["gsd_summary_save"],
413
436
  requiredWorkflowTools: [],
@@ -417,6 +440,7 @@ export const UNIT_REGISTRY = {
417
440
  kind: "primary",
418
441
  scopeClass: "standard",
419
442
  phaseChain: ["discuss", "planning"],
443
+ promptTemplate: "guided-discuss-project",
420
444
  toolContract: {
421
445
  allowedGsdTools: ["gsd_summary_save", "gsd_decision_save", "gsd_requirement_save"],
422
446
  requiredWorkflowTools: ["ask_user_questions", "gsd_summary_save"],
@@ -426,6 +450,7 @@ export const UNIT_REGISTRY = {
426
450
  kind: "primary",
427
451
  scopeClass: "standard",
428
452
  phaseChain: ["discuss", "planning"],
453
+ promptTemplate: "guided-discuss-requirements",
429
454
  toolContract: {
430
455
  allowedGsdTools: ["gsd_requirement_save", "gsd_summary_save"],
431
456
  requiredWorkflowTools: ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"],
@@ -435,6 +460,7 @@ export const UNIT_REGISTRY = {
435
460
  kind: "primary",
436
461
  scopeClass: "standard",
437
462
  phaseChain: ["discuss", "planning"],
463
+ promptTemplate: "guided-research-decision",
438
464
  toolContract: {
439
465
  allowedGsdTools: ["gsd_summary_save"],
440
466
  requiredWorkflowTools: ["ask_user_questions"],
@@ -447,6 +473,7 @@ export const UNIT_REGISTRY = {
447
473
  kind: "primary",
448
474
  scopeClass: "standard",
449
475
  phaseChain: ["research"],
476
+ promptTemplate: "guided-research-project",
450
477
  toolContract: {
451
478
  allowedGsdTools: [],
452
479
  requiredWorkflowTools: [],
@@ -494,3 +521,8 @@ export function getUnitDescriptor(unitType: string): UnitDescriptor | undefined
494
521
  export function getUnitPhaseChain(unitType: string): readonly GSDModelPhaseKey[] | null {
495
522
  return getUnitDescriptor(unitType)?.phaseChain ?? null;
496
523
  }
524
+
525
+ /** Prompt template id for units with a direct one-template builder. */
526
+ export function getUnitPromptTemplate(unitType: string): string | undefined {
527
+ return getUnitDescriptor(unitType)?.promptTemplate;
528
+ }
@@ -21,6 +21,8 @@ import { existsSync, readFileSync, unlinkSync } from "node:fs";
21
21
  import { randomUUID } from "node:crypto";
22
22
  import { join } from "node:path";
23
23
 
24
+ import type { PreflightResult, PostflightResult } from "./clean-root-preflight.js";
25
+
24
26
  import type { AutoSession } from "./auto/session.js";
25
27
  import { debugLog } from "./debug-logger.js";
26
28
  import { logWarning } from "./workflow-logger.js";
@@ -153,11 +155,11 @@ export interface WorktreeLifecycleDeps {
153
155
  /**
154
156
  * Milestone Merge Transaction Module runner.
155
157
  *
156
- * The field name is preserved for existing test fixtures, but production
157
- * wiring now supplies the named transaction wrapper rather than the raw
158
- * squash-merge primitive directly.
158
+ * The field name describes the lifecycle seam; production wiring supplies
159
+ * the named transaction wrapper rather than the raw legacy
160
+ * auto-worktree primitive directly.
159
161
  */
160
- mergeMilestoneToMain: MilestoneMergeTransactionRunner;
162
+ mergeMilestone: MilestoneMergeTransactionRunner;
161
163
 
162
164
  // ADR-016 phase 2 / C1 + C2 + C3 + C4 inlined the following fields as
163
165
  // direct imports — leaf primitives that did not vary across callers:
@@ -238,7 +240,106 @@ export interface StrandedMilestoneAdoptionOptions {
238
240
 
239
241
  export type ExitResult =
240
242
  | { ok: true; merged: boolean; codeFilesChanged: boolean }
241
- | { ok: false; reason: "merge-conflict" | "teardown-failed"; cause?: unknown };
243
+ | {
244
+ ok: false;
245
+ reason:
246
+ | "merge-conflict"
247
+ | "teardown-failed"
248
+ | "preflight-dirty-overlap"
249
+ | "preflight-unmerged-conflicts"
250
+ | "merge-failed"
251
+ | "postflight-stash-restore-failed";
252
+ cause?: unknown;
253
+ postflight?: PostflightResult;
254
+ };
255
+
256
+ export interface GuardedMilestoneMergeDeps {
257
+ preflightCleanRoot: (
258
+ basePath: string,
259
+ milestoneId: string,
260
+ notify: NotifyCtx["notify"],
261
+ ) => PreflightResult;
262
+ postflightPopStash: (
263
+ basePath: string,
264
+ milestoneId: string,
265
+ stashMarker: string | undefined,
266
+ notify: NotifyCtx["notify"],
267
+ ) => PostflightResult;
268
+ }
269
+
270
+ /**
271
+ * Optional `exitMilestone(..., { merge: true, guardedMerge })` contract for callers
272
+ * that need root-clean preflight and postflight stash restoration around the
273
+ * lifecycle-owned merge attempt. `projectRoot` is the workspace root whose
274
+ * user changes are protected by the supplied guard functions.
275
+ */
276
+ export interface GuardedMilestoneMergeOptions extends GuardedMilestoneMergeDeps {
277
+ projectRoot: string;
278
+ }
279
+
280
+ type GuardedMilestoneMergeResult = ExitResult;
281
+
282
+ function preflightBlockedMergeReason(
283
+ blockedReason: PreflightResult["blockedReason"],
284
+ ): Extract<ExitResult, { ok: false }>["reason"] {
285
+ if (blockedReason?.startsWith("unmerged-conflicts")) {
286
+ return "preflight-unmerged-conflicts";
287
+ }
288
+ return "preflight-dirty-overlap";
289
+ }
290
+
291
+ /**
292
+ * Run a milestone merge behind the Worktree Lifecycle seam with the root-clean
293
+ * stash guard that protects user changes around merge.
294
+ *
295
+ * The helper owns the invariant ordering (preflight -> merge callback ->
296
+ * postflight on every attempted merge path) and returns the same typed result
297
+ * shape as `exitMilestone`. Auto closeout remains responsible for translating
298
+ * that result into loop policy (pause vs. stop, visible transcript
299
+ * preservation, projection rebuild).
300
+ */
301
+ function runGuardedMilestoneMerge(request: {
302
+ merge: () => ExitResult;
303
+ guard: GuardedMilestoneMergeOptions;
304
+ milestoneId: string;
305
+ notify: NotifyCtx["notify"];
306
+ }): GuardedMilestoneMergeResult {
307
+ const { merge, guard, milestoneId, notify } = request;
308
+ const preflight = guard.preflightCleanRoot(guard.projectRoot, milestoneId, notify);
309
+ if (preflight.blocked) {
310
+ return {
311
+ ok: false,
312
+ reason: preflightBlockedMergeReason(preflight.blockedReason),
313
+ };
314
+ }
315
+
316
+ let mergeError: unknown = null;
317
+ const exitResult = merge();
318
+ if (!exitResult.ok) {
319
+ mergeError = exitResult.cause ?? new Error(`exit ${exitResult.reason}`);
320
+ }
321
+
322
+ let postflight: PostflightResult | undefined;
323
+ if (preflight.stashPushed) {
324
+ postflight = guard.postflightPopStash(
325
+ guard.projectRoot,
326
+ milestoneId,
327
+ preflight.stashMarker,
328
+ notify,
329
+ );
330
+ }
331
+
332
+ if (mergeError instanceof MergeConflictError) {
333
+ return { ok: false, reason: "merge-conflict", cause: mergeError, postflight };
334
+ }
335
+ if (mergeError) {
336
+ return { ok: false, reason: "merge-failed", cause: mergeError, postflight };
337
+ }
338
+ if (postflight?.needsManualRecovery) {
339
+ return { ok: false, reason: "postflight-stash-restore-failed", postflight };
340
+ }
341
+ return exitResult;
342
+ }
242
343
 
243
344
  /**
244
345
  * Session-less merge entry context. Per ADR-016 phase 2 / A1 (#5616), the
@@ -1046,7 +1147,7 @@ function _mergeWorktreeModeImpl(
1046
1147
  };
1047
1148
  }
1048
1149
 
1049
- const mergeResult = deps.mergeMilestoneToMain(
1150
+ const mergeResult = deps.mergeMilestone(
1050
1151
  originalBasePath,
1051
1152
  milestoneId,
1052
1153
  roadmapResolution.content,
@@ -1213,7 +1314,7 @@ function _mergeBranchModeImpl(
1213
1314
  };
1214
1315
  }
1215
1316
 
1216
- const mergeResult = deps.mergeMilestoneToMain(
1317
+ const mergeResult = deps.mergeMilestone(
1217
1318
  worktreeBasePath,
1218
1319
  milestoneId,
1219
1320
  roadmapResolution.content,
@@ -1444,18 +1545,41 @@ export class WorktreeLifecycle {
1444
1545
  * With `opts.merge === false`, runs auto-commit and teardown without
1445
1546
  * merging to main.
1446
1547
  *
1548
+ * When `opts.guardedMerge` is present with `opts.merge === true`, the
1549
+ * lifecycle verb also owns the root-clean guard around the merge: preflight
1550
+ * runs before the inner merge, postflight stash restore runs after every
1551
+ * attempted merge path, and guard failures are returned as typed
1552
+ * `ExitResult` reasons.
1553
+ *
1447
1554
  * Returns a typed `ExitResult`. `MergeConflictError` is surfaced as
1448
1555
  * `{ ok: false, reason: "merge-conflict", cause }` instead of thrown,
1449
- * giving callers a typed branch for the expected failure path.
1450
- * Unexpected failures (filesystem, git permissions, etc.) are wrapped
1451
- * as `{ ok: false, reason: "teardown-failed", cause }` so callers always
1556
+ * giving callers a typed branch for the expected failure path. Guarded merge
1557
+ * failures surface as `preflight-dirty-overlap`,
1558
+ * `preflight-unmerged-conflicts`, `merge-failed`, or
1559
+ * `postflight-stash-restore-failed`. Unexpected failures (filesystem, git
1560
+ * permissions, etc.) are wrapped as
1561
+ * `{ ok: false, reason: "teardown-failed", cause }` so callers always
1452
1562
  * receive a discriminated union — no exceptions for any expected outcome.
1453
1563
  */
1454
1564
  exitMilestone(
1455
1565
  milestoneId: string,
1456
- opts: { merge: boolean; preserveBranch?: boolean; preserveWorktree?: boolean },
1566
+ opts: {
1567
+ merge: boolean;
1568
+ preserveBranch?: boolean;
1569
+ preserveWorktree?: boolean;
1570
+ guardedMerge?: GuardedMilestoneMergeOptions;
1571
+ },
1457
1572
  ctx: NotifyCtx,
1458
1573
  ): ExitResult {
1574
+ if (opts.merge && opts.guardedMerge) {
1575
+ return runGuardedMilestoneMerge({
1576
+ merge: () => this.exitMilestone(milestoneId, { merge: true }, ctx),
1577
+ guard: opts.guardedMerge,
1578
+ milestoneId,
1579
+ notify: ctx.notify,
1580
+ });
1581
+ }
1582
+
1459
1583
  if (opts.merge) {
1460
1584
  try {
1461
1585
  const merged = this._mergeAndExit(milestoneId, ctx);