gsd-pi 2.81.0 → 2.82.0-dev.20138ae42
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.
- package/README.md +64 -33
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +10 -1
- package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
- package/dist/resources/extensions/cmux/index.js +5 -0
- package/dist/resources/extensions/gsd/auto/infra-errors.js +9 -3
- package/dist/resources/extensions/gsd/auto/loop.js +116 -13
- package/dist/resources/extensions/gsd/auto/orchestrator.js +124 -6
- package/dist/resources/extensions/gsd/auto/phases.js +220 -98
- package/dist/resources/extensions/gsd/auto/run-unit.js +66 -3
- package/dist/resources/extensions/gsd/auto/session.js +9 -0
- package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
- package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +12 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +247 -178
- package/dist/resources/extensions/gsd/auto-dispatch.js +27 -17
- package/dist/resources/extensions/gsd/auto-model-selection.js +2 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +240 -128
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -5
- package/dist/resources/extensions/gsd/auto-recovery.js +34 -179
- package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
- package/dist/resources/extensions/gsd/auto-start.js +105 -35
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
- package/dist/resources/extensions/gsd/auto-verification.js +38 -26
- package/dist/resources/extensions/gsd/auto-worktree.js +119 -1
- package/dist/resources/extensions/gsd/auto.js +400 -108
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +38 -12
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +9 -8
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -4
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +21 -9
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -2
- package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +38 -1
- package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
- package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
- package/dist/resources/extensions/gsd/context-store.js +112 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +31 -5
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +3 -2
- package/dist/resources/extensions/gsd/db-writer.js +150 -84
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +28 -11
- package/dist/resources/extensions/gsd/doctor.js +2 -28
- package/dist/resources/extensions/gsd/export-html.js +27 -425
- package/dist/resources/extensions/gsd/git-service.js +41 -2
- package/dist/resources/extensions/gsd/gsd-db.js +8 -23
- package/dist/resources/extensions/gsd/guided-flow.js +93 -111
- package/dist/resources/extensions/gsd/guided-unit-context.js +23 -0
- package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
- package/dist/resources/extensions/gsd/health-widget.js +4 -10
- package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
- package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
- package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
- package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +6 -96
- package/dist/resources/extensions/gsd/md-importer.js +1 -1
- package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
- package/dist/resources/extensions/gsd/migrate/command.js +5 -0
- package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
- package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
- package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
- package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +12 -17
- package/dist/resources/extensions/gsd/milestone-actions.js +11 -4
- package/dist/resources/extensions/gsd/native-git-bridge.js +62 -26
- package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
- package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
- package/dist/resources/extensions/gsd/pending-auto-start.js +52 -0
- package/dist/resources/extensions/gsd/post-execution-checks.js +73 -2
- package/dist/resources/extensions/gsd/pre-execution-checks.js +28 -1
- package/dist/resources/extensions/gsd/prompt-loader.js +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +28 -10
- package/dist/resources/extensions/gsd/prompts/discuss.md +29 -11
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -4
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
- package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
- package/dist/resources/extensions/gsd/session-lock.js +40 -0
- package/dist/resources/extensions/gsd/smart-entry-routing.js +36 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +252 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +45 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +82 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
- package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
- package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
- package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
- package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
- package/dist/resources/extensions/gsd/status-guards.js +4 -0
- package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/dist/resources/extensions/gsd/templates/plan.md +8 -5
- package/dist/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -8
- package/dist/resources/extensions/gsd/tools/complete-slice.js +6 -8
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +89 -14
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
- package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +32 -10
- package/dist/resources/extensions/gsd/validation.js +23 -1
- package/dist/resources/extensions/gsd/verification-gate.js +68 -7
- package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
- package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
- package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workflow-projections.js +6 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +769 -319
- package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -1
- package/dist/resources/extensions/shared/html-shell.js +388 -0
- package/dist/resources/extensions/subagent/index.js +448 -78
- package/dist/resources/extensions/subagent/launch.js +77 -0
- package/dist/resources/extensions/subagent/run-store.js +148 -0
- package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
- package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/dist/resources/extensions/visual-brief/index.js +5 -0
- package/dist/resources/extensions/visual-brief/page-contract.js +124 -0
- package/dist/resources/extensions/visual-brief/prompts.js +140 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -7
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -5
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +5 -8
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -8
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -5
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -5
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/chunks/4266.js +2 -0
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
- package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-8c10ec293ae0f1d5.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/page-752f1e2ebdaa3e45.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-9a4db269f9ed63ad.js} +1 -1
- package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/welcome-screen.d.ts +0 -7
- package/dist/welcome-screen.js +60 -69
- package/package.json +6 -5
- package/packages/contracts/dist/rpc.test.js +7 -0
- package/packages/contracts/dist/rpc.test.js.map +1 -1
- package/packages/contracts/dist/workflow.d.ts +21 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +24 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/src/rpc.test.ts +8 -0
- package/packages/contracts/src/workflow.ts +24 -0
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/README.md +14 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +80 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
- package/packages/mcp-server/src/workflow-tools.test.ts +23 -1
- package/packages/mcp-server/src/workflow-tools.ts +168 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/native/tsconfig.json +2 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -2
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -1
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +82 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts +2 -4
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +5 -6
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/simple-options.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js +50 -0
- package/packages/pi-ai/dist/providers/simple-options.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/src/index.ts +7 -2
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +63 -0
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +91 -1
- package/packages/pi-ai/src/providers/simple-options.test.ts +60 -0
- package/packages/pi-ai/src/providers/simple-options.ts +5 -6
- package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js +66 -0
- package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +76 -9
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +13 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +33 -42
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +86 -82
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-thinking-level.test.ts +79 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +113 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +33 -44
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -104
- package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
- package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
- package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.js +9 -6
- package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +5 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
- package/packages/pi-tui/src/overlay-layout.ts +10 -7
- package/packages/pi-tui/src/tui.ts +6 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +105 -1
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +27 -26
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +10 -1
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +2 -1
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +19 -2
- package/src/resources/extensions/cmux/index.ts +6 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +59 -16
- package/src/resources/extensions/gsd/auto/infra-errors.ts +9 -3
- package/src/resources/extensions/gsd/auto/loop-deps.ts +9 -5
- package/src/resources/extensions/gsd/auto/loop.ts +121 -14
- package/src/resources/extensions/gsd/auto/orchestrator.ts +129 -6
- package/src/resources/extensions/gsd/auto/phases.ts +180 -20
- package/src/resources/extensions/gsd/auto/run-unit.ts +69 -4
- package/src/resources/extensions/gsd/auto/session.ts +10 -0
- package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
- package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +13 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +301 -183
- package/src/resources/extensions/gsd/auto-dispatch.ts +29 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +2 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +273 -140
- package/src/resources/extensions/gsd/auto-prompts.ts +13 -5
- package/src/resources/extensions/gsd/auto-recovery.ts +36 -209
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +114 -31
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
- package/src/resources/extensions/gsd/auto-verification.ts +46 -38
- package/src/resources/extensions/gsd/auto-worktree.ts +127 -1
- package/src/resources/extensions/gsd/auto.ts +441 -107
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +54 -12
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +9 -8
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +16 -4
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +19 -7
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +19 -3
- package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +41 -1
- package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
- package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
- package/src/resources/extensions/gsd/context-store.ts +120 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +30 -4
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +4 -3
- package/src/resources/extensions/gsd/db-writer.ts +167 -84
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +25 -13
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -27
- package/src/resources/extensions/gsd/export-html.ts +27 -427
- package/src/resources/extensions/gsd/git-service.ts +47 -1
- package/src/resources/extensions/gsd/gsd-db.ts +10 -23
- package/src/resources/extensions/gsd/guided-flow.ts +126 -128
- package/src/resources/extensions/gsd/guided-unit-context.ts +30 -0
- package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
- package/src/resources/extensions/gsd/health-widget.ts +6 -10
- package/src/resources/extensions/gsd/journal.ts +2 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
- package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
- package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
- package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -96
- package/src/resources/extensions/gsd/md-importer.ts +1 -1
- package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
- package/src/resources/extensions/gsd/migrate/command.ts +5 -0
- package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
- package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
- package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
- package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +15 -23
- package/src/resources/extensions/gsd/milestone-actions.ts +10 -4
- package/src/resources/extensions/gsd/native-git-bridge.ts +68 -25
- package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
- package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
- package/src/resources/extensions/gsd/pending-auto-start.ts +79 -0
- package/src/resources/extensions/gsd/post-execution-checks.ts +87 -2
- package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +1 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +28 -10
- package/src/resources/extensions/gsd/prompts/discuss.md +29 -11
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/queue.md +4 -4
- package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
- package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
- package/src/resources/extensions/gsd/session-lock.ts +41 -0
- package/src/resources/extensions/gsd/smart-entry-routing.ts +77 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +344 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +66 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +101 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
- package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
- package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
- package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
- package/src/resources/extensions/gsd/status-guards.ts +5 -0
- package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/src/resources/extensions/gsd/templates/plan.md +8 -5
- package/src/resources/extensions/gsd/templates/task-plan.md +4 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +783 -176
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +487 -4
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +292 -4
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +53 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +12 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +15 -1
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +20 -5
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +69 -1
- package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +11 -2
- package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +5 -9
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -2
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +28 -1
- package/src/resources/extensions/gsd/tests/db-authority-regression.test.ts +208 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +79 -4
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +59 -11
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/guided-tool-contract.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +17 -7
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/hook-model-resolution.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +129 -1
- package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
- package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
- package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
- package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +46 -11
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +26 -18
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
- package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -217
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +55 -1
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +29 -5
- package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +225 -1
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +81 -3
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/post-unit-git-failure.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/prompt-loader.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +46 -2
- package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/smart-entry-routing.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +53 -2
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +71 -58
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +1048 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +86 -7
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
- package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +211 -59
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +597 -118
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +72 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +8 -10
- package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -8
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +5 -1
- package/src/resources/extensions/gsd/tools/plan-slice.ts +98 -12
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
- package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
- package/src/resources/extensions/gsd/types.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +47 -11
- package/src/resources/extensions/gsd/validation.ts +23 -1
- package/src/resources/extensions/gsd/verification-gate.ts +78 -6
- package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
- package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
- package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workflow-projections.ts +6 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +1196 -518
- package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
- package/src/resources/extensions/shared/html-shell.ts +412 -0
- package/src/resources/extensions/subagent/index.ts +567 -103
- package/src/resources/extensions/subagent/launch.ts +131 -0
- package/src/resources/extensions/subagent/run-store.ts +218 -0
- package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
- package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
- package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
- package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/src/resources/extensions/visual-brief/index.ts +8 -0
- package/src/resources/extensions/visual-brief/page-contract.ts +136 -0
- package/src/resources/extensions/visual-brief/prompts.ts +183 -0
- package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +212 -0
- package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
- package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-9ecfd95f343793f0.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-200592a7f3baf579.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
- package/dist/web/standalone/.next/static/css/de70bee13400563f.css +0 -1
- package/dist/web/standalone/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/747892c23ea88013-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/8d697b304b401681-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
- package/dist/web/standalone/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1544
- /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → xRy4LqKSNKdT7y6ATYyEl}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → xRy4LqKSNKdT7y6ATYyEl}/_ssgManifest.js +0 -0
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
* a circular reference. Both classes share the body until the Resolver retires.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { existsSync, unlinkSync } from "node:fs";
|
|
20
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
21
21
|
import { randomUUID } from "node:crypto";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
|
|
24
24
|
import type { AutoSession } from "./auto/session.js";
|
|
25
25
|
import { debugLog } from "./debug-logger.js";
|
|
26
|
+
import { logWarning } from "./workflow-logger.js";
|
|
26
27
|
import { emitJournalEvent } from "./journal.js";
|
|
27
28
|
import { emitWorktreeCreated, emitWorktreeMerged } from "./worktree-telemetry.js";
|
|
28
29
|
import {
|
|
@@ -35,14 +36,53 @@ import {
|
|
|
35
36
|
releaseMilestoneLease,
|
|
36
37
|
} from "./db/milestone-leases.js";
|
|
37
38
|
import { MergeConflictError } from "./git-service.js";
|
|
39
|
+
import type { GitPreferences } from "./git-service.js";
|
|
38
40
|
import {
|
|
39
41
|
getCollapseCadence,
|
|
40
42
|
getMilestoneResquash,
|
|
41
43
|
resquashMilestoneOnMain,
|
|
42
44
|
} from "./slice-cadence.js";
|
|
43
|
-
|
|
45
|
+
// ADR-016 phase 2 / C3 (#5626): cache + preferences + path helpers inlined
|
|
46
|
+
// as direct imports. They are leaf-level functions that do not vary across
|
|
47
|
+
// callers — production wiring previously injected them via deps; the seam
|
|
48
|
+
// added type churn without enabling test variation.
|
|
49
|
+
import { loadEffectiveGSDPreferences, getIsolationMode } from "./preferences.js";
|
|
50
|
+
import { invalidateAllCaches } from "./cache.js";
|
|
51
|
+
import { resolveMilestoneFile } from "./paths.js";
|
|
44
52
|
import type { WorktreeStateProjection } from "./worktree-state-projection.js";
|
|
45
53
|
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
54
|
+
// ADR-016 phase 2 / C1 (#5624): file-system + git-CLI leaf primitives
|
|
55
|
+
// inlined as direct imports rather than injected through `WorktreeLifecycleDeps`.
|
|
56
|
+
// These four symbols (`readFileSync` from node:fs, `getCurrentBranch` and
|
|
57
|
+
// `autoCommitCurrentBranch` from `./worktree.js`, `nativeCheckoutBranch` from
|
|
58
|
+
// `./native-git-bridge.js`) are leaf-level primitives — no environment varies
|
|
59
|
+
// across callers — so the dependency-injection seam they used to inhabit was
|
|
60
|
+
// adding type churn without enabling any test variation.
|
|
61
|
+
import {
|
|
62
|
+
autoCommitCurrentBranch,
|
|
63
|
+
getCurrentBranch,
|
|
64
|
+
} from "./worktree.js";
|
|
65
|
+
import { nativeCheckoutBranch } from "./native-git-bridge.js";
|
|
66
|
+
// ADR-016 phase 2 / C2 (#5625): worktree-manager helpers inlined from
|
|
67
|
+
// `./auto-worktree.js`. These seven functions are not real seams — Lifecycle
|
|
68
|
+
// is the only Module that calls them, and they live alongside the Module's
|
|
69
|
+
// other primitives in `auto-worktree.ts`.
|
|
70
|
+
import {
|
|
71
|
+
autoWorktreeBranch,
|
|
72
|
+
createAutoWorktree,
|
|
73
|
+
enterAutoWorktree,
|
|
74
|
+
enterBranchModeForMilestone,
|
|
75
|
+
getAutoWorktreePath,
|
|
76
|
+
isInAutoWorktree,
|
|
77
|
+
teardownAutoWorktree,
|
|
78
|
+
} from "./auto-worktree.js";
|
|
79
|
+
|
|
80
|
+
const recentWorktreeMergeFailures = new Map<string, number>();
|
|
81
|
+
const MERGE_FAILURE_DEDUPE_MS = 60_000;
|
|
82
|
+
|
|
83
|
+
export function resetRecentWorktreeMergeFailuresForTest(): void {
|
|
84
|
+
recentWorktreeMergeFailures.clear();
|
|
85
|
+
}
|
|
46
86
|
|
|
47
87
|
// ─── Types ───────────────────────────────────────────────────────────────
|
|
48
88
|
|
|
@@ -64,19 +104,16 @@ export interface NotifyCtx {
|
|
|
64
104
|
* recursion retires; shrinking it now would force a parallel migration.
|
|
65
105
|
*/
|
|
66
106
|
export interface WorktreeLifecycleDeps {
|
|
67
|
-
// ──
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
loadEffectiveGSDPreferences: () =>
|
|
78
|
-
| { preferences?: { git?: Record<string, unknown> } }
|
|
79
|
-
| undefined;
|
|
107
|
+
// ── Git service factory (ADR-016 phase 2 / C4) ───────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Build a fresh `GitService` instance bound to `basePath`.
|
|
110
|
+
*
|
|
111
|
+
* Hides the constructor shape (new GitServiceImpl(basePath, gitConfig))
|
|
112
|
+
* and the gitConfig load from Lifecycle. The factory takes only a
|
|
113
|
+
* `basePath` and is responsible for loading any config it needs.
|
|
114
|
+
* Tests substitute fakes by passing a function that returns a stub.
|
|
115
|
+
*/
|
|
116
|
+
gitServiceFactory: (basePath: string) => AutoSession["gitService"];
|
|
80
117
|
|
|
81
118
|
// ── State Projection Module (ADR-016 one-way edge) ───────────────────
|
|
82
119
|
/**
|
|
@@ -85,44 +122,60 @@ export interface WorktreeLifecycleDeps {
|
|
|
85
122
|
*/
|
|
86
123
|
worktreeProjection: WorktreeStateProjection;
|
|
87
124
|
|
|
88
|
-
// ──
|
|
89
|
-
isInAutoWorktree: (basePath: string) => boolean;
|
|
90
|
-
autoCommitCurrentBranch: (
|
|
91
|
-
basePath: string,
|
|
92
|
-
reason: string,
|
|
93
|
-
milestoneId: string,
|
|
94
|
-
) => void;
|
|
95
|
-
autoWorktreeBranch: (milestoneId: string) => string;
|
|
96
|
-
teardownAutoWorktree: (
|
|
97
|
-
basePath: string,
|
|
98
|
-
milestoneId: string,
|
|
99
|
-
opts?: { preserveBranch?: boolean },
|
|
100
|
-
) => void;
|
|
101
|
-
mergeMilestoneToMain: (
|
|
102
|
-
basePath: string,
|
|
103
|
-
milestoneId: string,
|
|
104
|
-
roadmapContent: string,
|
|
105
|
-
) => { pushed: boolean; codeFilesChanged: boolean };
|
|
106
|
-
getCurrentBranch: (basePath: string) => string;
|
|
125
|
+
// ── Merge primitive ──────────────────────────────────────────────────
|
|
107
126
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
127
|
+
* Inner squash-merge primitive (`auto-worktree.ts:mergeMilestoneToMain`).
|
|
128
|
+
*
|
|
129
|
+
* **Module-internal seam — do not construct your own.** Only the wiring
|
|
130
|
+
* factory `auto.ts:buildWorktreeLifecycleDeps()` is permitted to populate
|
|
131
|
+
* this field. The primitive is `@internal`; production callers reach the
|
|
132
|
+
* merge body through `WorktreeLifecycle.exitMilestone({ merge: true })`,
|
|
133
|
+
* never by calling this dep directly.
|
|
111
134
|
*/
|
|
112
|
-
|
|
113
|
-
resolveMilestoneFile: (
|
|
135
|
+
mergeMilestoneToMain: (
|
|
114
136
|
basePath: string,
|
|
115
137
|
milestoneId: string,
|
|
116
|
-
|
|
117
|
-
) =>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
roadmapContent: string,
|
|
139
|
+
) => {
|
|
140
|
+
pushed: boolean;
|
|
141
|
+
codeFilesChanged: boolean;
|
|
142
|
+
commitMessage?: string;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// ADR-016 phase 2 / C1 + C2 + C3 + C4 inlined the following fields as
|
|
146
|
+
// direct imports — leaf primitives that did not vary across callers:
|
|
147
|
+
// C1 (#5624): readFileSync, getCurrentBranch, checkoutBranch,
|
|
148
|
+
// autoCommitCurrentBranch
|
|
149
|
+
// C2 (#5625): enterAutoWorktree, createAutoWorktree,
|
|
150
|
+
// enterBranchModeForMilestone, getAutoWorktreePath,
|
|
151
|
+
// teardownAutoWorktree, isInAutoWorktree, autoWorktreeBranch
|
|
152
|
+
// C3 (#5626): invalidateAllCaches, loadEffectiveGSDPreferences,
|
|
153
|
+
// getIsolationMode, resolveMilestoneFile
|
|
154
|
+
// C4 (#5627): GitServiceImpl constructor → gitServiceFactory above
|
|
155
|
+
//
|
|
156
|
+
// ADR-016 phase 3 (#5693) deleted the @deprecated optional fields that
|
|
157
|
+
// remained on this Interface for legacy test fixtures. Tests that need to
|
|
158
|
+
// substitute primitive implementations cast their deps to
|
|
159
|
+
// `WorktreeLifecycleTestOverrides` (exported below) — the test seam now
|
|
160
|
+
// lives outside the public Interface.
|
|
161
|
+
//
|
|
162
|
+
// Final dep bag: 3 fields. The ADR's envisioned shape was ≤6.
|
|
124
163
|
}
|
|
125
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Test-only override shim. Production callers do not use this type — it
|
|
167
|
+
* exists so legacy test fixtures can substitute the primitive implementations
|
|
168
|
+
* that were inlined into Lifecycle in ADR-016 phase 2 (C1-C4). Pass an object
|
|
169
|
+
* typed `WorktreeLifecycleDeps & WorktreeLifecycleTestOverrides` to the
|
|
170
|
+
* `WorktreeLifecycle` constructor; Lifecycle reads the overrides through the
|
|
171
|
+
* structural-typing escape hatch in `primitiveOverrides()`.
|
|
172
|
+
*
|
|
173
|
+
* The fields here intentionally duplicate the C1-C4-inlined primitive
|
|
174
|
+
* signatures. Adding new fields is fine when a test needs to vary a primitive
|
|
175
|
+
* that has no other seam.
|
|
176
|
+
*/
|
|
177
|
+
export type WorktreeLifecycleTestOverrides = WorktreeLifecyclePrimitiveOverrides;
|
|
178
|
+
|
|
126
179
|
/**
|
|
127
180
|
* Internal sentinel — thrown by `_mergeBranchMode` when it has already
|
|
128
181
|
* emitted a user-visible error. The outer `mergeAndExit` catches the type
|
|
@@ -166,6 +219,49 @@ export type ExitResult =
|
|
|
166
219
|
| { ok: true; merged: boolean; codeFilesChanged: boolean }
|
|
167
220
|
| { ok: false; reason: "merge-conflict" | "teardown-failed"; cause?: unknown };
|
|
168
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Session-less merge entry context. Per ADR-016 phase 2 / A1 (#5616), the
|
|
224
|
+
* merge body is structurally session-less — it reads project root, worktree
|
|
225
|
+
* path, and milestoneId. Single-loop callers (`_mergeAndExit`) build a
|
|
226
|
+
* MergeContext from `this.s`. Parallel callers (`parallel-merge.ts`) build
|
|
227
|
+
* one directly without an `AutoSession`.
|
|
228
|
+
*/
|
|
229
|
+
export interface MergeContext {
|
|
230
|
+
/** Project root — merge target (where `git merge --squash` lands). */
|
|
231
|
+
originalBasePath: string;
|
|
232
|
+
/**
|
|
233
|
+
* Current worktree path or project root when in branch mode. Used as the
|
|
234
|
+
* cwd anchor for `mergeMilestoneToMain` and the source for
|
|
235
|
+
* `Projection.finalizeProjectionForMerge`.
|
|
236
|
+
*/
|
|
237
|
+
worktreeBasePath: string;
|
|
238
|
+
milestoneId: string;
|
|
239
|
+
/**
|
|
240
|
+
* When true, `mergeMilestoneStandalone` returns `{ merged: false,
|
|
241
|
+
* mode: "skipped" }` immediately (mirrors the single-loop guard). Default
|
|
242
|
+
* `false` for parallel callers, which never run with degraded isolation.
|
|
243
|
+
*/
|
|
244
|
+
isolationDegraded?: boolean;
|
|
245
|
+
notify: NotifyCtx["notify"];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Result of `mergeMilestoneStandalone`. `mode` lets callers decide which
|
|
250
|
+
* session-bound side effects to run (worktree-mode → `restoreToProjectRoot`,
|
|
251
|
+
* branch-mode → `rebuildGitService`, skipped → none).
|
|
252
|
+
*/
|
|
253
|
+
export interface MergeStandaloneResult {
|
|
254
|
+
merged: boolean;
|
|
255
|
+
mode: "worktree" | "branch" | "skipped";
|
|
256
|
+
codeFilesChanged: boolean;
|
|
257
|
+
pushed: boolean;
|
|
258
|
+
/**
|
|
259
|
+
* Commit message produced by the squash merge, if available. Forwarded
|
|
260
|
+
* from `mergeMilestoneToMain`. Only populated when `merged === true`.
|
|
261
|
+
*/
|
|
262
|
+
commitMessage?: string;
|
|
263
|
+
}
|
|
264
|
+
|
|
169
265
|
// ─── Validation ──────────────────────────────────────────────────────────
|
|
170
266
|
|
|
171
267
|
function isValidMilestoneId(milestoneId: string): boolean {
|
|
@@ -178,6 +274,206 @@ function invalidMilestoneIdError(milestoneId: string): Error {
|
|
|
178
274
|
);
|
|
179
275
|
}
|
|
180
276
|
|
|
277
|
+
type WorktreeLifecyclePrimitiveOverrides = {
|
|
278
|
+
readFileSync?: (path: string, encoding: BufferEncoding) => string;
|
|
279
|
+
getCurrentBranch?: (basePath: string) => string;
|
|
280
|
+
checkoutBranch?: (basePath: string, branch: string) => void;
|
|
281
|
+
autoCommitCurrentBranch?: (
|
|
282
|
+
basePath: string,
|
|
283
|
+
unitType: string,
|
|
284
|
+
unitId: string,
|
|
285
|
+
taskContext?: unknown,
|
|
286
|
+
) => string | null;
|
|
287
|
+
getAutoWorktreePath?: (
|
|
288
|
+
basePath: string,
|
|
289
|
+
milestoneId: string,
|
|
290
|
+
) => string | null;
|
|
291
|
+
// ADR-016 phase 2 / C2-inlined worktree-manager primitives. Tests still
|
|
292
|
+
// stub these via the structural-typing escape hatch on `WorktreeLifecycleDeps`,
|
|
293
|
+
// so the call sites below check for an override first and fall back to the
|
|
294
|
+
// imported direct primitive.
|
|
295
|
+
isInAutoWorktree?: (basePath: string) => boolean;
|
|
296
|
+
autoWorktreeBranch?: (milestoneId: string) => string;
|
|
297
|
+
teardownAutoWorktree?: (
|
|
298
|
+
basePath: string,
|
|
299
|
+
milestoneId: string,
|
|
300
|
+
opts?: { preserveBranch?: boolean },
|
|
301
|
+
) => void;
|
|
302
|
+
createAutoWorktree?: (basePath: string, milestoneId: string) => string;
|
|
303
|
+
enterAutoWorktree?: (basePath: string, milestoneId: string) => string;
|
|
304
|
+
enterBranchModeForMilestone?: (basePath: string, milestoneId: string) => void;
|
|
305
|
+
// ADR-016 phase 2 / C3-inlined cache + preferences + path helpers.
|
|
306
|
+
getIsolationMode?: (basePath?: string) => "worktree" | "branch" | "none";
|
|
307
|
+
invalidateAllCaches?: () => void;
|
|
308
|
+
resolveMilestoneFile?: (
|
|
309
|
+
basePath: string,
|
|
310
|
+
milestoneId: string,
|
|
311
|
+
fileType: string,
|
|
312
|
+
) => string | null;
|
|
313
|
+
loadEffectiveGSDPreferences?: (basePath?: string) =>
|
|
314
|
+
| { preferences?: { git?: Record<string, unknown> } }
|
|
315
|
+
| null
|
|
316
|
+
| undefined;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
function primitiveOverrides(
|
|
320
|
+
deps: WorktreeLifecycleDeps,
|
|
321
|
+
): WorktreeLifecyclePrimitiveOverrides {
|
|
322
|
+
return deps as WorktreeLifecycleDeps & WorktreeLifecyclePrimitiveOverrides;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function readLifecycleFile(
|
|
326
|
+
deps: WorktreeLifecycleDeps,
|
|
327
|
+
path: string,
|
|
328
|
+
): string {
|
|
329
|
+
return primitiveOverrides(deps).readFileSync?.(path, "utf-8") ??
|
|
330
|
+
readFileSync(path, "utf-8");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function currentLifecycleBranch(
|
|
334
|
+
deps: WorktreeLifecycleDeps,
|
|
335
|
+
basePath: string,
|
|
336
|
+
): string {
|
|
337
|
+
return primitiveOverrides(deps).getCurrentBranch?.(basePath) ??
|
|
338
|
+
getCurrentBranch(basePath);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function checkoutLifecycleBranch(
|
|
342
|
+
deps: WorktreeLifecycleDeps,
|
|
343
|
+
basePath: string,
|
|
344
|
+
branch: string,
|
|
345
|
+
): void {
|
|
346
|
+
const checkoutBranch = primitiveOverrides(deps).checkoutBranch;
|
|
347
|
+
if (checkoutBranch) {
|
|
348
|
+
checkoutBranch(basePath, branch);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
nativeCheckoutBranch(basePath, branch);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function autoCommitLifecycleBranch(
|
|
355
|
+
deps: WorktreeLifecycleDeps,
|
|
356
|
+
basePath: string,
|
|
357
|
+
unitType: string,
|
|
358
|
+
unitId: string,
|
|
359
|
+
): string | null {
|
|
360
|
+
return primitiveOverrides(deps).autoCommitCurrentBranch?.(
|
|
361
|
+
basePath,
|
|
362
|
+
unitType,
|
|
363
|
+
unitId,
|
|
364
|
+
) ?? autoCommitCurrentBranch(basePath, unitType, unitId);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ADR-016 phase 2 / C2-inlined worktree-manager primitives — helpers that
|
|
368
|
+
// honour the structural-typing override pattern so legacy test fixtures keep
|
|
369
|
+
// working without rewriting them onto real-git fixtures.
|
|
370
|
+
function lifecycleIsInAutoWorktree(
|
|
371
|
+
deps: WorktreeLifecycleDeps,
|
|
372
|
+
basePath: string,
|
|
373
|
+
): boolean {
|
|
374
|
+
return primitiveOverrides(deps).isInAutoWorktree?.(basePath) ??
|
|
375
|
+
isInAutoWorktree(basePath);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function lifecycleAutoWorktreeBranch(
|
|
379
|
+
deps: WorktreeLifecycleDeps,
|
|
380
|
+
milestoneId: string,
|
|
381
|
+
): string {
|
|
382
|
+
return primitiveOverrides(deps).autoWorktreeBranch?.(milestoneId) ??
|
|
383
|
+
autoWorktreeBranch(milestoneId);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function lifecycleTeardownAutoWorktree(
|
|
387
|
+
deps: WorktreeLifecycleDeps,
|
|
388
|
+
basePath: string,
|
|
389
|
+
milestoneId: string,
|
|
390
|
+
opts?: { preserveBranch?: boolean },
|
|
391
|
+
): void {
|
|
392
|
+
const override = primitiveOverrides(deps).teardownAutoWorktree;
|
|
393
|
+
if (override) {
|
|
394
|
+
override(basePath, milestoneId, opts);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
teardownAutoWorktree(basePath, milestoneId, opts);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function lifecycleCreateAutoWorktree(
|
|
401
|
+
deps: WorktreeLifecycleDeps,
|
|
402
|
+
basePath: string,
|
|
403
|
+
milestoneId: string,
|
|
404
|
+
): string {
|
|
405
|
+
return primitiveOverrides(deps).createAutoWorktree?.(basePath, milestoneId) ??
|
|
406
|
+
createAutoWorktree(basePath, milestoneId);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function lifecycleEnterAutoWorktree(
|
|
410
|
+
deps: WorktreeLifecycleDeps,
|
|
411
|
+
basePath: string,
|
|
412
|
+
milestoneId: string,
|
|
413
|
+
): string {
|
|
414
|
+
return primitiveOverrides(deps).enterAutoWorktree?.(basePath, milestoneId) ??
|
|
415
|
+
enterAutoWorktree(basePath, milestoneId);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function lifecycleEnterBranchMode(
|
|
419
|
+
deps: WorktreeLifecycleDeps,
|
|
420
|
+
basePath: string,
|
|
421
|
+
milestoneId: string,
|
|
422
|
+
): void {
|
|
423
|
+
const override = primitiveOverrides(deps).enterBranchModeForMilestone;
|
|
424
|
+
if (override) {
|
|
425
|
+
override(basePath, milestoneId);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
enterBranchModeForMilestone(basePath, milestoneId);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ADR-016 phase 2 / C3-inlined cache + preferences + path helpers.
|
|
432
|
+
function lifecycleGetIsolationMode(
|
|
433
|
+
deps: WorktreeLifecycleDeps,
|
|
434
|
+
basePath?: string,
|
|
435
|
+
): "worktree" | "branch" | "none" {
|
|
436
|
+
return primitiveOverrides(deps).getIsolationMode?.(basePath) ??
|
|
437
|
+
getIsolationMode(basePath);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function lifecycleInvalidateAllCaches(deps: WorktreeLifecycleDeps): void {
|
|
441
|
+
const override = primitiveOverrides(deps).invalidateAllCaches;
|
|
442
|
+
if (override) {
|
|
443
|
+
override();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
invalidateAllCaches();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function lifecycleResolveMilestoneFile(
|
|
450
|
+
deps: WorktreeLifecycleDeps,
|
|
451
|
+
basePath: string,
|
|
452
|
+
milestoneId: string,
|
|
453
|
+
fileType: string,
|
|
454
|
+
): string | null {
|
|
455
|
+
return primitiveOverrides(deps).resolveMilestoneFile?.(
|
|
456
|
+
basePath,
|
|
457
|
+
milestoneId,
|
|
458
|
+
fileType,
|
|
459
|
+
) ?? resolveMilestoneFile(basePath, milestoneId, fileType);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function lifecycleLoadPreferences(
|
|
463
|
+
deps: WorktreeLifecycleDeps,
|
|
464
|
+
basePath?: string,
|
|
465
|
+
):
|
|
466
|
+
| { preferences?: { git?: Record<string, unknown> } }
|
|
467
|
+
| null
|
|
468
|
+
| undefined {
|
|
469
|
+
const override = primitiveOverrides(deps).loadEffectiveGSDPreferences;
|
|
470
|
+
if (override) return override(basePath);
|
|
471
|
+
return loadEffectiveGSDPreferences(basePath) as
|
|
472
|
+
| { preferences?: { git?: Record<string, unknown> } }
|
|
473
|
+
| null
|
|
474
|
+
| undefined;
|
|
475
|
+
}
|
|
476
|
+
|
|
181
477
|
/**
|
|
182
478
|
* Throwing variant used by the merge/exit paths that surface failures via
|
|
183
479
|
* the typed `ExitResult` (callers wrap the throw → cause). The enter path
|
|
@@ -237,7 +533,7 @@ export function _enterMilestoneCore(
|
|
|
237
533
|
|
|
238
534
|
// Phase B: claim a milestone lease before any worktree mutation. Two
|
|
239
535
|
// workers cannot enter the same milestone concurrently. Best-effort:
|
|
240
|
-
//
|
|
536
|
+
// warn if no worker registered (single-worker fallback) or skip if DB
|
|
241
537
|
// unavailable; reuse existing lease if we already hold it on this
|
|
242
538
|
// milestone (re-entry within the same session).
|
|
243
539
|
if (s.workerId) {
|
|
@@ -330,13 +626,18 @@ export function _enterMilestoneCore(
|
|
|
330
626
|
});
|
|
331
627
|
}
|
|
332
628
|
}
|
|
629
|
+
} else {
|
|
630
|
+
logWarning(
|
|
631
|
+
"worktree",
|
|
632
|
+
`enterMilestone(${milestoneId}) ran before auto worker registration; milestone lease was not claimed.`,
|
|
633
|
+
);
|
|
333
634
|
}
|
|
334
635
|
|
|
335
636
|
// Resolve the project root for worktree operations via shared helper.
|
|
336
637
|
// Handles the case where originalBasePath is falsy and basePath is itself
|
|
337
638
|
// a worktree path — prevents double-nested worktree paths (#3729).
|
|
338
639
|
const basePath = resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
|
|
339
|
-
const mode =
|
|
640
|
+
const mode = getIsolationMode(basePath);
|
|
340
641
|
|
|
341
642
|
if (mode === "none") {
|
|
342
643
|
debugLog("WorktreeLifecycle", {
|
|
@@ -380,12 +681,12 @@ export function _enterMilestoneCore(
|
|
|
380
681
|
// ── Branch mode: create/checkout milestone branch, stay in project root ──
|
|
381
682
|
if (mode === "branch") {
|
|
382
683
|
try {
|
|
383
|
-
deps
|
|
684
|
+
lifecycleEnterBranchMode(deps, basePath, milestoneId);
|
|
384
685
|
// basePath does not change — no worktree, no chdir.
|
|
385
686
|
// Rebuild GitService so the new HEAD is reflected, then flush any
|
|
386
687
|
// path-keyed caches that may have been populated before the checkout.
|
|
387
688
|
rebuildGitService(s, deps);
|
|
388
|
-
|
|
689
|
+
invalidateAllCaches();
|
|
389
690
|
debugLog("WorktreeLifecycle", {
|
|
390
691
|
action: "enterMilestone",
|
|
391
692
|
milestoneId,
|
|
@@ -421,18 +722,22 @@ export function _enterMilestoneCore(
|
|
|
421
722
|
|
|
422
723
|
// ── Worktree mode ────────────────────────────────────────────────────────
|
|
423
724
|
try {
|
|
424
|
-
const existingPath =
|
|
725
|
+
const existingPath =
|
|
726
|
+
(primitiveOverrides(deps).getAutoWorktreePath ?? getAutoWorktreePath)(
|
|
727
|
+
basePath,
|
|
728
|
+
milestoneId,
|
|
729
|
+
);
|
|
425
730
|
let wtPath: string;
|
|
426
731
|
|
|
427
732
|
if (existingPath) {
|
|
428
|
-
wtPath = deps
|
|
733
|
+
wtPath = lifecycleEnterAutoWorktree(deps, basePath, milestoneId);
|
|
429
734
|
} else {
|
|
430
|
-
wtPath = deps
|
|
735
|
+
wtPath = lifecycleCreateAutoWorktree(deps, basePath, milestoneId);
|
|
431
736
|
}
|
|
432
737
|
|
|
433
738
|
s.basePath = wtPath;
|
|
434
739
|
rebuildGitService(s, deps);
|
|
435
|
-
|
|
740
|
+
invalidateAllCaches();
|
|
436
741
|
|
|
437
742
|
// Per ADR-016: Lifecycle calls Projection on entry, before any Unit
|
|
438
743
|
// dispatches. Build a temporary scope from the new basePath; callers may
|
|
@@ -511,113 +816,618 @@ export function _enterMilestoneCore(
|
|
|
511
816
|
}
|
|
512
817
|
}
|
|
513
818
|
|
|
819
|
+
/**
|
|
820
|
+
* Resolve the basePath to adopt on resume from a paused session.
|
|
821
|
+
*
|
|
822
|
+
* Returns `persistedWorktreePath` when the path is non-null and exists on
|
|
823
|
+
* disk; otherwise falls back to `base`. Used by
|
|
824
|
+
* `WorktreeLifecycle.resumeFromPausedSession` (#5621). Exported as a pure
|
|
825
|
+
* function so unit tests can exercise the path-resolution logic without
|
|
826
|
+
* constructing a `WorktreeLifecycle` instance.
|
|
827
|
+
*
|
|
828
|
+
* The optional `pathExists` parameter exists only for tests that need to
|
|
829
|
+
* substitute a stub for `existsSync`.
|
|
830
|
+
*/
|
|
831
|
+
export function resolvePausedResumeBasePath(
|
|
832
|
+
base: string,
|
|
833
|
+
persistedWorktreePath: string | null | undefined,
|
|
834
|
+
pathExists: (p: string) => boolean = existsSync,
|
|
835
|
+
): string {
|
|
836
|
+
return persistedWorktreePath && pathExists(persistedWorktreePath)
|
|
837
|
+
? persistedWorktreePath
|
|
838
|
+
: base;
|
|
839
|
+
}
|
|
840
|
+
|
|
514
841
|
function rebuildGitService(
|
|
515
842
|
s: AutoSession,
|
|
516
843
|
deps: WorktreeLifecycleDeps,
|
|
517
844
|
): void {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
) as AutoSession["gitService"];
|
|
845
|
+
// ADR-016 phase 2 / C4 (#5627): the gitConfig load and constructor
|
|
846
|
+
// construction live behind `gitServiceFactory`. Lifecycle no longer
|
|
847
|
+
// sees the constructor shape, the gitConfig type, or the unknown→
|
|
848
|
+
// GitService cast.
|
|
849
|
+
s.gitService = deps.gitServiceFactory(s.basePath);
|
|
524
850
|
}
|
|
525
851
|
|
|
526
|
-
|
|
852
|
+
function emitWorktreeMergeFailedOnce(
|
|
853
|
+
basePath: string,
|
|
854
|
+
milestoneId: string,
|
|
855
|
+
err: unknown,
|
|
856
|
+
): void {
|
|
857
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
858
|
+
const errorCategory = err instanceof Error ? err.name : "Error";
|
|
859
|
+
const now = Date.now();
|
|
860
|
+
const key = `${basePath}\0${milestoneId}\0${errorCategory}`;
|
|
861
|
+
const previous = recentWorktreeMergeFailures.get(key);
|
|
862
|
+
if (previous && now - previous < MERGE_FAILURE_DEDUPE_MS) return;
|
|
863
|
+
for (const [candidate, ts] of recentWorktreeMergeFailures) {
|
|
864
|
+
if (now - ts >= MERGE_FAILURE_DEDUPE_MS) {
|
|
865
|
+
recentWorktreeMergeFailures.delete(candidate);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
emitJournalEvent(basePath, {
|
|
869
|
+
ts: new Date().toISOString(),
|
|
870
|
+
flowId: randomUUID(),
|
|
871
|
+
seq: 0,
|
|
872
|
+
eventType: "worktree-merge-failed",
|
|
873
|
+
data: { milestoneId, error: msg },
|
|
874
|
+
});
|
|
875
|
+
recentWorktreeMergeFailures.set(key, now);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// ─── Session-less merge entry (ADR-016 phase 2 / A1) ─────────────────────
|
|
527
879
|
|
|
528
880
|
/**
|
|
529
|
-
* Worktree
|
|
881
|
+
* Worktree-mode merge body. Session-less — operates on a `MergeContext`.
|
|
530
882
|
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
883
|
+
* On error: emits the "worktree-merge-failed" journal event, notifies the
|
|
884
|
+
* user, cleans up stale `SQUASH_MSG` / `MERGE_HEAD` / `MERGE_MSG` files
|
|
885
|
+
* (#1389), and chdirs back to project root before rethrowing. Session-side
|
|
886
|
+
* cleanup (`restoreToProjectRoot`, `gitService` rebuild) is the caller's
|
|
887
|
+
* responsibility.
|
|
534
888
|
*/
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
889
|
+
function _mergeWorktreeModeImpl(
|
|
890
|
+
deps: WorktreeLifecycleDeps,
|
|
891
|
+
mctx: MergeContext,
|
|
892
|
+
): MergeStandaloneResult {
|
|
893
|
+
const { originalBasePath, worktreeBasePath, milestoneId, notify } = mctx;
|
|
894
|
+
if (!originalBasePath) {
|
|
895
|
+
debugLog("WorktreeLifecycle", {
|
|
896
|
+
action: "mergeAndExit",
|
|
897
|
+
milestoneId,
|
|
898
|
+
mode: "worktree",
|
|
899
|
+
skipped: true,
|
|
900
|
+
reason: "missing-original-base",
|
|
901
|
+
});
|
|
902
|
+
return {
|
|
903
|
+
merged: false,
|
|
904
|
+
mode: "worktree",
|
|
905
|
+
codeFilesChanged: false,
|
|
906
|
+
pushed: false,
|
|
907
|
+
};
|
|
542
908
|
}
|
|
543
909
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
910
|
+
try {
|
|
911
|
+
// ADR-016: final projection before teardown. Replaces the legacy
|
|
912
|
+
// syncWorktreeStateBack(originalBase, basePath, milestoneId) call.
|
|
913
|
+
const finalScope = scopeMilestone(
|
|
914
|
+
createWorkspace(worktreeBasePath),
|
|
915
|
+
milestoneId,
|
|
916
|
+
);
|
|
917
|
+
const { synced } = deps.worktreeProjection.finalizeProjectionForMerge(
|
|
918
|
+
finalScope,
|
|
919
|
+
);
|
|
920
|
+
if (synced.length > 0) {
|
|
921
|
+
debugLog("WorktreeLifecycle", {
|
|
922
|
+
action: "mergeAndExit",
|
|
923
|
+
milestoneId,
|
|
924
|
+
phase: "reverse-sync",
|
|
925
|
+
synced: synced.length,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
555
928
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
return { ok: false, reason: "teardown-failed", cause: err };
|
|
929
|
+
// Resolve roadmap — try project root first, then worktree path as
|
|
930
|
+
// fallback. The worktree may hold the only copy when state-back
|
|
931
|
+
// projection silently dropped it or .gsd/ is not symlinked. Without
|
|
932
|
+
// the fallback, a missing roadmap triggers bare teardown which
|
|
933
|
+
// deletes the branch and orphans all milestone commits (#1573).
|
|
934
|
+
let roadmapPath = resolveMilestoneFile(
|
|
935
|
+
originalBasePath,
|
|
936
|
+
milestoneId,
|
|
937
|
+
"ROADMAP",
|
|
938
|
+
);
|
|
939
|
+
if (
|
|
940
|
+
!roadmapPath &&
|
|
941
|
+
!isSamePathPhysical(worktreeBasePath, originalBasePath)
|
|
942
|
+
) {
|
|
943
|
+
roadmapPath = resolveMilestoneFile(
|
|
944
|
+
worktreeBasePath,
|
|
945
|
+
milestoneId,
|
|
946
|
+
"ROADMAP",
|
|
947
|
+
);
|
|
948
|
+
if (roadmapPath) {
|
|
949
|
+
debugLog("WorktreeLifecycle", {
|
|
950
|
+
action: "mergeAndExit",
|
|
951
|
+
milestoneId,
|
|
952
|
+
phase: "roadmap-fallback",
|
|
953
|
+
note: "resolved from worktree path",
|
|
954
|
+
});
|
|
583
955
|
}
|
|
584
956
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
957
|
+
|
|
958
|
+
if (!roadmapPath) {
|
|
959
|
+
// No roadmap at either location — teardown but PRESERVE the branch
|
|
960
|
+
// so commits are not orphaned (#1573).
|
|
961
|
+
lifecycleTeardownAutoWorktree(deps, originalBasePath, milestoneId, {
|
|
962
|
+
preserveBranch: true,
|
|
588
963
|
});
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
964
|
+
notify(
|
|
965
|
+
`Exited worktree for ${milestoneId} (no roadmap found — branch preserved for manual merge).`,
|
|
966
|
+
"warning",
|
|
967
|
+
);
|
|
968
|
+
return {
|
|
969
|
+
merged: false,
|
|
970
|
+
mode: "worktree",
|
|
971
|
+
codeFilesChanged: false,
|
|
972
|
+
pushed: false,
|
|
973
|
+
};
|
|
592
974
|
}
|
|
593
|
-
}
|
|
594
975
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
action: "mergeAndEnterNext",
|
|
608
|
-
currentMilestoneId,
|
|
609
|
-
nextMilestoneId,
|
|
610
|
-
});
|
|
611
|
-
let merged = false;
|
|
612
|
-
let mergeThrew = false;
|
|
976
|
+
const roadmapContent = readLifecycleFile(deps, roadmapPath);
|
|
977
|
+
const mergeResult = deps.mergeMilestoneToMain(
|
|
978
|
+
originalBasePath,
|
|
979
|
+
milestoneId,
|
|
980
|
+
roadmapContent,
|
|
981
|
+
);
|
|
982
|
+
|
|
983
|
+
// #2945 Bug 3: mergeMilestoneToMain performs best-effort worktree
|
|
984
|
+
// cleanup internally (step 12), but it can silently fail on Windows
|
|
985
|
+
// or when the worktree directory is locked. Perform a secondary
|
|
986
|
+
// teardown here to ensure the worktree is properly cleaned up.
|
|
987
|
+
// Idempotent — if already removed, teardownAutoWorktree no-ops.
|
|
613
988
|
try {
|
|
614
|
-
|
|
615
|
-
} catch
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
989
|
+
lifecycleTeardownAutoWorktree(deps, originalBasePath, milestoneId);
|
|
990
|
+
} catch {
|
|
991
|
+
// Best-effort — primary cleanup in mergeMilestoneToMain may have
|
|
992
|
+
// already removed the worktree.
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (mergeResult.codeFilesChanged) {
|
|
996
|
+
notify(
|
|
997
|
+
`Milestone ${milestoneId} merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
998
|
+
"info",
|
|
999
|
+
);
|
|
1000
|
+
} else {
|
|
1001
|
+
// #1906 — milestone produced only .gsd/ metadata. Surface
|
|
1002
|
+
// clearly so the user knows the milestone is not truly complete.
|
|
1003
|
+
notify(
|
|
1004
|
+
`WARNING: Milestone ${milestoneId} merged to main but contained NO code changes — only .gsd/ metadata files. ` +
|
|
1005
|
+
`The milestone summary may describe planned work that was never implemented. ` +
|
|
1006
|
+
`Review the milestone output and re-run if code is missing.`,
|
|
1007
|
+
"warning",
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
return {
|
|
1012
|
+
merged: true,
|
|
1013
|
+
mode: "worktree",
|
|
1014
|
+
codeFilesChanged: mergeResult.codeFilesChanged,
|
|
1015
|
+
pushed: mergeResult.pushed,
|
|
1016
|
+
commitMessage: mergeResult.commitMessage,
|
|
1017
|
+
};
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1020
|
+
debugLog("WorktreeLifecycle", {
|
|
1021
|
+
action: "mergeAndExit",
|
|
1022
|
+
milestoneId,
|
|
1023
|
+
result: "error",
|
|
1024
|
+
error: msg,
|
|
1025
|
+
fallback: "chdir-to-project-root",
|
|
1026
|
+
});
|
|
1027
|
+
emitWorktreeMergeFailedOnce(originalBasePath || worktreeBasePath, milestoneId, err);
|
|
1028
|
+
// Surface a clear, actionable error. Worktree and milestone branch
|
|
1029
|
+
// are intentionally preserved — nothing has been deleted. User can
|
|
1030
|
+
// retry /gsd dispatch complete-milestone or merge manually once the
|
|
1031
|
+
// underlying issue is fixed (#1668, #1891).
|
|
1032
|
+
notify(
|
|
1033
|
+
`Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry with \`/gsd dispatch complete-milestone\` or merge manually.`,
|
|
1034
|
+
"warning",
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
// Clean up stale merge state left by failed squash-merge (#1389)
|
|
1038
|
+
try {
|
|
1039
|
+
const gitDir = join(originalBasePath || worktreeBasePath, ".git");
|
|
1040
|
+
for (const f of ["SQUASH_MSG", "MERGE_HEAD", "MERGE_MSG"]) {
|
|
1041
|
+
const p = join(gitDir, f);
|
|
1042
|
+
if (existsSync(p)) unlinkSync(p);
|
|
1043
|
+
}
|
|
1044
|
+
} catch {
|
|
1045
|
+
/* best-effort */
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Error recovery: chdir back to project root only when no real worktree
|
|
1049
|
+
// path is available. Session-side cleanup (restoreToProjectRoot,
|
|
1050
|
+
// gitService rebuild) is the caller's responsibility.
|
|
1051
|
+
if (originalBasePath && !worktreeBasePath) {
|
|
1052
|
+
try {
|
|
1053
|
+
process.chdir(originalBasePath);
|
|
1054
|
+
} catch {
|
|
1055
|
+
/* best-effort */
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Re-throw: MergeConflictError stops the auto loop (#2330);
|
|
1060
|
+
// non-conflict errors must also propagate so broken states are
|
|
1061
|
+
// diagnosable (#4380).
|
|
1062
|
+
throw err;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Branch-mode merge body. Session-less.
|
|
1068
|
+
*
|
|
1069
|
+
* Session-side `gitService` rebuild after HEAD changes is the caller's
|
|
1070
|
+
* responsibility. The branch-mode `UserNotifiedError` sentinel still flows
|
|
1071
|
+
* through unchanged so the outer caller can suppress duplicate toasts.
|
|
1072
|
+
*/
|
|
1073
|
+
function _mergeBranchModeImpl(
|
|
1074
|
+
deps: WorktreeLifecycleDeps,
|
|
1075
|
+
mctx: MergeContext,
|
|
1076
|
+
): MergeStandaloneResult {
|
|
1077
|
+
const { worktreeBasePath, milestoneId, notify } = mctx;
|
|
1078
|
+
try {
|
|
1079
|
+
const currentBranch = currentLifecycleBranch(deps, worktreeBasePath);
|
|
1080
|
+
const milestoneBranch = lifecycleAutoWorktreeBranch(deps, milestoneId);
|
|
1081
|
+
|
|
1082
|
+
if (currentBranch !== milestoneBranch) {
|
|
1083
|
+
// #5538-followup: previous behaviour was to silently `return false`
|
|
1084
|
+
// when HEAD wasn't on the milestone branch — that let the loop
|
|
1085
|
+
// advance with the milestone's commits stranded on the branch.
|
|
1086
|
+
// Attempt recovery by force-checking-out the milestone branch; if
|
|
1087
|
+
// that fails, throw so the caller pauses auto-mode and the user
|
|
1088
|
+
// sees the failure instead of a silent merge skip.
|
|
1089
|
+
debugLog("WorktreeLifecycle", {
|
|
1090
|
+
action: "mergeAndExit",
|
|
1091
|
+
milestoneId,
|
|
1092
|
+
mode: "branch",
|
|
1093
|
+
recovery: "checkout-milestone-branch",
|
|
1094
|
+
currentBranch,
|
|
1095
|
+
milestoneBranch,
|
|
1096
|
+
});
|
|
1097
|
+
try {
|
|
1098
|
+
checkoutLifecycleBranch(deps, worktreeBasePath, milestoneBranch);
|
|
1099
|
+
} catch (checkoutErr) {
|
|
1100
|
+
const checkoutMsg =
|
|
1101
|
+
checkoutErr instanceof Error
|
|
1102
|
+
? checkoutErr.message
|
|
1103
|
+
: String(checkoutErr);
|
|
1104
|
+
notify(
|
|
1105
|
+
`Cannot merge milestone ${milestoneId}: working tree is on ${currentBranch} and checkout to ${milestoneBranch} failed (${checkoutMsg}). Resolve manually and run /gsd auto to resume.`,
|
|
1106
|
+
"error",
|
|
1107
|
+
);
|
|
1108
|
+
throw new UserNotifiedError(checkoutMsg, checkoutErr);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const reverify = currentLifecycleBranch(deps, worktreeBasePath);
|
|
1112
|
+
if (reverify !== milestoneBranch) {
|
|
1113
|
+
const reverifyMsg = `branch checkout to ${milestoneBranch} reported success but current branch is ${reverify}`;
|
|
1114
|
+
notify(
|
|
1115
|
+
`Cannot merge milestone ${milestoneId}: ${reverifyMsg}. Resolve manually and run /gsd auto to resume.`,
|
|
1116
|
+
"error",
|
|
1117
|
+
);
|
|
1118
|
+
throw new UserNotifiedError(reverifyMsg);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const roadmapPath = resolveMilestoneFile(
|
|
1123
|
+
worktreeBasePath,
|
|
1124
|
+
milestoneId,
|
|
1125
|
+
"ROADMAP",
|
|
1126
|
+
);
|
|
1127
|
+
if (!roadmapPath) {
|
|
1128
|
+
debugLog("WorktreeLifecycle", {
|
|
1129
|
+
action: "mergeAndExit",
|
|
1130
|
+
milestoneId,
|
|
1131
|
+
mode: "branch",
|
|
1132
|
+
skipped: true,
|
|
1133
|
+
reason: "no-roadmap",
|
|
1134
|
+
});
|
|
1135
|
+
return {
|
|
1136
|
+
merged: false,
|
|
1137
|
+
mode: "branch",
|
|
1138
|
+
codeFilesChanged: false,
|
|
1139
|
+
pushed: false,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const roadmapContent = readLifecycleFile(deps, roadmapPath);
|
|
1144
|
+
const mergeResult = deps.mergeMilestoneToMain(
|
|
1145
|
+
worktreeBasePath,
|
|
1146
|
+
milestoneId,
|
|
1147
|
+
roadmapContent,
|
|
1148
|
+
);
|
|
1149
|
+
|
|
1150
|
+
if (mergeResult.codeFilesChanged) {
|
|
1151
|
+
notify(
|
|
1152
|
+
`Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1153
|
+
"info",
|
|
1154
|
+
);
|
|
1155
|
+
} else {
|
|
1156
|
+
notify(
|
|
1157
|
+
`WARNING: Milestone ${milestoneId} merged (branch mode) but contained NO code changes — only .gsd/ metadata. ` +
|
|
1158
|
+
`Review the milestone output and re-run if code is missing.`,
|
|
1159
|
+
"warning",
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
debugLog("WorktreeLifecycle", {
|
|
1163
|
+
action: "mergeAndExit",
|
|
1164
|
+
milestoneId,
|
|
1165
|
+
mode: "branch",
|
|
1166
|
+
result: "success",
|
|
1167
|
+
});
|
|
1168
|
+
return {
|
|
1169
|
+
merged: true,
|
|
1170
|
+
mode: "branch",
|
|
1171
|
+
codeFilesChanged: mergeResult.codeFilesChanged,
|
|
1172
|
+
pushed: mergeResult.pushed,
|
|
1173
|
+
commitMessage: mergeResult.commitMessage,
|
|
1174
|
+
};
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1177
|
+
debugLog("WorktreeLifecycle", {
|
|
1178
|
+
action: "mergeAndExit",
|
|
1179
|
+
milestoneId,
|
|
1180
|
+
mode: "branch",
|
|
1181
|
+
result: "error",
|
|
1182
|
+
error: msg,
|
|
1183
|
+
});
|
|
1184
|
+
if (!(err instanceof UserNotifiedError)) {
|
|
1185
|
+
notify(`Milestone merge failed (branch mode): ${msg}`, "warning");
|
|
1186
|
+
}
|
|
1187
|
+
// Re-throw all errors so callers can apply their own recovery (#4380).
|
|
1188
|
+
throw err;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Session-less merge entry (ADR-016 phase 2 / A1, issue #5618).
|
|
1194
|
+
*
|
|
1195
|
+
* Runs the worktree-mode or branch-mode merge body without touching session
|
|
1196
|
+
* state. Used directly by `parallel-merge.ts` and indirectly (via
|
|
1197
|
+
* `_mergeAndExit`) by the single-loop path. Caller is responsible for any
|
|
1198
|
+
* session-side cleanup based on the returned `mode`.
|
|
1199
|
+
*
|
|
1200
|
+
* **CWD anchor**: anchors `process.cwd()` at `originalBasePath` before
|
|
1201
|
+
* non-worktree merge paths to mirror the single-loop guard against ENOENT
|
|
1202
|
+
* after teardown (de73fb43d). Worktree-mode merge paths keep the real
|
|
1203
|
+
* worktree as cwd because `mergeMilestoneToMain()` infers source worktree
|
|
1204
|
+
* state from `process.cwd()`. Best-effort; silent on failure.
|
|
1205
|
+
*
|
|
1206
|
+
* **Failure handling**: `MergeConflictError` and other unrecoverable errors
|
|
1207
|
+
* propagate to the caller. The caller is responsible for any state restore
|
|
1208
|
+
* (single-loop callers re-`chdir` and `restoreToProjectRoot`; parallel
|
|
1209
|
+
* callers surface to the user as a `MergeResult` with `success: false`).
|
|
1210
|
+
*/
|
|
1211
|
+
export function mergeMilestoneStandalone(
|
|
1212
|
+
deps: WorktreeLifecycleDeps,
|
|
1213
|
+
mctx: MergeContext,
|
|
1214
|
+
): MergeStandaloneResult {
|
|
1215
|
+
const { originalBasePath, worktreeBasePath, milestoneId, notify } = mctx;
|
|
1216
|
+
validateMilestoneId(milestoneId);
|
|
1217
|
+
|
|
1218
|
+
if (mctx.isolationDegraded) {
|
|
1219
|
+
if (originalBasePath) {
|
|
1220
|
+
try {
|
|
1221
|
+
process.chdir(originalBasePath);
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
debugLog("WorktreeLifecycle", {
|
|
1224
|
+
action: "mergeAndExit",
|
|
1225
|
+
phase: "pre-merge-chdir-failed",
|
|
1226
|
+
milestoneId,
|
|
1227
|
+
originalBasePath,
|
|
1228
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
debugLog("WorktreeLifecycle", {
|
|
1233
|
+
action: "mergeAndExit",
|
|
1234
|
+
milestoneId,
|
|
1235
|
+
skipped: true,
|
|
1236
|
+
reason: "isolation-degraded",
|
|
1237
|
+
});
|
|
1238
|
+
notify(
|
|
1239
|
+
`Skipping worktree merge for ${milestoneId} — isolation was degraded (worktree creation failed earlier). Work is on the current branch.`,
|
|
1240
|
+
"info",
|
|
1241
|
+
);
|
|
1242
|
+
return {
|
|
1243
|
+
merged: false,
|
|
1244
|
+
mode: "skipped",
|
|
1245
|
+
codeFilesChanged: false,
|
|
1246
|
+
pushed: false,
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
const mode = getIsolationMode(originalBasePath || worktreeBasePath);
|
|
1251
|
+
debugLog("WorktreeLifecycle", {
|
|
1252
|
+
action: "mergeAndExit",
|
|
1253
|
+
milestoneId,
|
|
1254
|
+
mode,
|
|
1255
|
+
basePath: worktreeBasePath,
|
|
1256
|
+
});
|
|
1257
|
+
emitJournalEvent(originalBasePath || worktreeBasePath, {
|
|
1258
|
+
ts: new Date().toISOString(),
|
|
1259
|
+
flowId: randomUUID(),
|
|
1260
|
+
seq: 0,
|
|
1261
|
+
eventType: "worktree-merge-start",
|
|
1262
|
+
data: { milestoneId, mode },
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
// #2625: If we are physically inside an auto-worktree, we MUST merge
|
|
1266
|
+
// regardless of the current isolation config. This prevents data loss
|
|
1267
|
+
// when the default isolation mode changes between versions.
|
|
1268
|
+
const inWorktree =
|
|
1269
|
+
lifecycleIsInAutoWorktree(deps, worktreeBasePath) && Boolean(originalBasePath);
|
|
1270
|
+
|
|
1271
|
+
if (mode === "none" && !inWorktree) {
|
|
1272
|
+
debugLog("WorktreeLifecycle", {
|
|
1273
|
+
action: "mergeAndExit",
|
|
1274
|
+
milestoneId,
|
|
1275
|
+
skipped: true,
|
|
1276
|
+
reason: "mode-none",
|
|
1277
|
+
});
|
|
1278
|
+
// Anchor cwd at project root before the early return so subsequent
|
|
1279
|
+
// process.cwd() calls after the skip don't ENOENT if we were inside a
|
|
1280
|
+
// worktree directory that gets torn down later. Best-effort.
|
|
1281
|
+
if (originalBasePath) {
|
|
1282
|
+
try {
|
|
1283
|
+
process.chdir(originalBasePath);
|
|
1284
|
+
} catch {
|
|
1285
|
+
/* best-effort */
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
merged: false,
|
|
1290
|
+
mode: "skipped",
|
|
1291
|
+
codeFilesChanged: false,
|
|
1292
|
+
pushed: false,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Set cwd to the correct anchor before dispatching to mode implementations.
|
|
1297
|
+
// Worktree mode / in-worktree override must run from the live worktree so
|
|
1298
|
+
// mergeMilestoneToMain can find worktree-local state; branch mode runs from
|
|
1299
|
+
// the original project root. Best-effort for synthetic test paths.
|
|
1300
|
+
const targetCwd = mode === "worktree" || inWorktree
|
|
1301
|
+
? worktreeBasePath
|
|
1302
|
+
: originalBasePath;
|
|
1303
|
+
if (targetCwd) {
|
|
1304
|
+
try {
|
|
1305
|
+
process.chdir(targetCwd);
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
debugLog("WorktreeLifecycle", {
|
|
1308
|
+
action: "mergeAndExit",
|
|
1309
|
+
phase: "pre-merge-chdir-failed",
|
|
1310
|
+
milestoneId,
|
|
1311
|
+
targetCwd,
|
|
1312
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (mode === "worktree" || inWorktree) {
|
|
1318
|
+
return _mergeWorktreeModeImpl(deps, mctx);
|
|
1319
|
+
}
|
|
1320
|
+
if (mode === "branch") {
|
|
1321
|
+
return _mergeBranchModeImpl(deps, mctx);
|
|
1322
|
+
}
|
|
1323
|
+
// Defensive fallback — should not reach here given the mode-none guard above.
|
|
1324
|
+
return {
|
|
1325
|
+
merged: false,
|
|
1326
|
+
mode: "skipped",
|
|
1327
|
+
codeFilesChanged: false,
|
|
1328
|
+
pushed: false,
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// ─── Module class ────────────────────────────────────────────────────────
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Worktree Lifecycle module instance.
|
|
1336
|
+
*
|
|
1337
|
+
* Constructed once per auto-mode session. Holds the session reference so
|
|
1338
|
+
* verbs can mutate `s.basePath` and related coordination state directly
|
|
1339
|
+
* without round-tripping through callers.
|
|
1340
|
+
*/
|
|
1341
|
+
export class WorktreeLifecycle {
|
|
1342
|
+
private readonly s: AutoSession;
|
|
1343
|
+
private readonly deps: WorktreeLifecycleDeps;
|
|
1344
|
+
|
|
1345
|
+
constructor(s: AutoSession, deps: WorktreeLifecycleDeps) {
|
|
1346
|
+
this.s = s;
|
|
1347
|
+
this.deps = deps;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* Enter or create the auto-worktree for `milestoneId`. Idempotent if
|
|
1352
|
+
* already in this milestone (lease refreshed; basePath unchanged).
|
|
1353
|
+
*
|
|
1354
|
+
* Returns a typed `EnterResult` describing the outcome. Callers may
|
|
1355
|
+
* ignore the result if they read `s.basePath` directly afterwards
|
|
1356
|
+
* (legacy behaviour); new callers should branch on the result.
|
|
1357
|
+
*/
|
|
1358
|
+
enterMilestone(milestoneId: string, ctx: NotifyCtx): EnterResult {
|
|
1359
|
+
return _enterMilestoneCore(this.s, this.deps, milestoneId, ctx);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* Exit the current worktree. With `opts.merge === true`, runs the full
|
|
1364
|
+
* merge-and-teardown path (worktree-mode or branch-mode auto-detected).
|
|
1365
|
+
* With `opts.merge === false`, runs auto-commit and teardown without
|
|
1366
|
+
* merging to main.
|
|
1367
|
+
*
|
|
1368
|
+
* Returns a typed `ExitResult`. `MergeConflictError` is surfaced as
|
|
1369
|
+
* `{ ok: false, reason: "merge-conflict", cause }` instead of thrown,
|
|
1370
|
+
* giving callers a typed branch for the expected failure path.
|
|
1371
|
+
* Unexpected failures (filesystem, git permissions, etc.) are wrapped
|
|
1372
|
+
* as `{ ok: false, reason: "teardown-failed", cause }` so callers always
|
|
1373
|
+
* receive a discriminated union — no exceptions for any expected outcome.
|
|
1374
|
+
*/
|
|
1375
|
+
exitMilestone(
|
|
1376
|
+
milestoneId: string,
|
|
1377
|
+
opts: { merge: boolean; preserveBranch?: boolean },
|
|
1378
|
+
ctx: NotifyCtx,
|
|
1379
|
+
): ExitResult {
|
|
1380
|
+
if (opts.merge) {
|
|
1381
|
+
try {
|
|
1382
|
+
const merged = this._mergeAndExit(milestoneId, ctx);
|
|
1383
|
+
return {
|
|
1384
|
+
ok: true,
|
|
1385
|
+
merged: merged.merged,
|
|
1386
|
+
codeFilesChanged: merged.codeFilesChanged,
|
|
1387
|
+
};
|
|
1388
|
+
} catch (err) {
|
|
1389
|
+
if (err instanceof MergeConflictError) {
|
|
1390
|
+
return { ok: false, reason: "merge-conflict", cause: err };
|
|
1391
|
+
}
|
|
1392
|
+
return { ok: false, reason: "teardown-failed", cause: err };
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
this._exitWithoutMerge(milestoneId, ctx, {
|
|
1397
|
+
preserveBranch: opts.preserveBranch,
|
|
1398
|
+
});
|
|
1399
|
+
return { ok: true, merged: false, codeFilesChanged: false };
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
return { ok: false, reason: "teardown-failed", cause: err };
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* Milestone transition: merge the current milestone, then enter the next
|
|
1407
|
+
* one. Pattern used when the loop detects that the active milestone has
|
|
1408
|
+
* changed (current completed, next is now active). Caller is responsible
|
|
1409
|
+
* for re-deriving state between the merge and the enter.
|
|
1410
|
+
*/
|
|
1411
|
+
mergeAndEnterNext(
|
|
1412
|
+
currentMilestoneId: string,
|
|
1413
|
+
nextMilestoneId: string,
|
|
1414
|
+
ctx: NotifyCtx,
|
|
1415
|
+
): void {
|
|
1416
|
+
debugLog("WorktreeLifecycle", {
|
|
1417
|
+
action: "mergeAndEnterNext",
|
|
1418
|
+
currentMilestoneId,
|
|
1419
|
+
nextMilestoneId,
|
|
1420
|
+
});
|
|
1421
|
+
let merged = false;
|
|
1422
|
+
let mergeThrew = false;
|
|
1423
|
+
try {
|
|
1424
|
+
merged = this._mergeAndExit(currentMilestoneId, ctx).merged;
|
|
1425
|
+
} catch (err) {
|
|
1426
|
+
if (err instanceof UserNotifiedError) throw err;
|
|
1427
|
+
mergeThrew = true;
|
|
1428
|
+
// _mergeAndExit emits a warning and restores state on failure during
|
|
1429
|
+
// merge/cleanup. If it throws before recovery runs (e.g. validation,
|
|
1430
|
+
// emitJournalEvent), basePath isn't restored — re-throw so we don't
|
|
621
1431
|
// enter the next milestone with the current one unmerged.
|
|
622
1432
|
const projectRoot = resolveWorktreeProjectRoot(
|
|
623
1433
|
this.s.basePath,
|
|
@@ -654,7 +1464,7 @@ export class WorktreeLifecycle {
|
|
|
654
1464
|
opts: { preserveBranch?: boolean },
|
|
655
1465
|
): void {
|
|
656
1466
|
validateMilestoneId(milestoneId);
|
|
657
|
-
if (!this.deps
|
|
1467
|
+
if (!lifecycleIsInAutoWorktree(this.deps, this.s.basePath)) {
|
|
658
1468
|
debugLog("WorktreeLifecycle", {
|
|
659
1469
|
action: "exitMilestone",
|
|
660
1470
|
milestoneId,
|
|
@@ -671,7 +1481,7 @@ export class WorktreeLifecycle {
|
|
|
671
1481
|
});
|
|
672
1482
|
|
|
673
1483
|
try {
|
|
674
|
-
this.deps
|
|
1484
|
+
autoCommitLifecycleBranch(this.deps, this.s.basePath, "stop", milestoneId);
|
|
675
1485
|
} catch (err) {
|
|
676
1486
|
debugLog("WorktreeLifecycle", {
|
|
677
1487
|
action: "exitMilestone",
|
|
@@ -680,7 +1490,7 @@ export class WorktreeLifecycle {
|
|
|
680
1490
|
error: err instanceof Error ? err.message : String(err),
|
|
681
1491
|
});
|
|
682
1492
|
ctx.notify(
|
|
683
|
-
`Auto-commit before exiting ${milestoneId} failed: ${err instanceof Error ? err.message : String(err)}. Branch ${this.deps
|
|
1493
|
+
`Auto-commit before exiting ${milestoneId} failed: ${err instanceof Error ? err.message : String(err)}. Branch ${lifecycleAutoWorktreeBranch(this.deps, milestoneId)} is preserved for recovery.`,
|
|
684
1494
|
"warning",
|
|
685
1495
|
);
|
|
686
1496
|
}
|
|
@@ -697,7 +1507,7 @@ export class WorktreeLifecycle {
|
|
|
697
1507
|
error: err instanceof Error ? err.message : String(err),
|
|
698
1508
|
});
|
|
699
1509
|
ctx.notify(
|
|
700
|
-
`Could not leave milestone worktree before cleanup: ${err instanceof Error ? err.message : String(err)}. Branch ${this.deps
|
|
1510
|
+
`Could not leave milestone worktree before cleanup: ${err instanceof Error ? err.message : String(err)}. Branch ${lifecycleAutoWorktreeBranch(this.deps, milestoneId)} is preserved for recovery.`,
|
|
701
1511
|
"warning",
|
|
702
1512
|
);
|
|
703
1513
|
}
|
|
@@ -705,7 +1515,7 @@ export class WorktreeLifecycle {
|
|
|
705
1515
|
|
|
706
1516
|
let teardownFailed = false;
|
|
707
1517
|
try {
|
|
708
|
-
this.deps
|
|
1518
|
+
lifecycleTeardownAutoWorktree(this.deps, this.s.originalBasePath, milestoneId, {
|
|
709
1519
|
preserveBranch: opts.preserveBranch ?? false,
|
|
710
1520
|
});
|
|
711
1521
|
} catch (err) {
|
|
@@ -717,7 +1527,7 @@ export class WorktreeLifecycle {
|
|
|
717
1527
|
error: err instanceof Error ? err.message : String(err),
|
|
718
1528
|
});
|
|
719
1529
|
ctx.notify(
|
|
720
|
-
`Worktree cleanup failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Branch ${this.deps
|
|
1530
|
+
`Worktree cleanup failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Branch ${lifecycleAutoWorktreeBranch(this.deps, milestoneId)} is preserved for recovery.`,
|
|
721
1531
|
"warning",
|
|
722
1532
|
);
|
|
723
1533
|
}
|
|
@@ -742,103 +1552,60 @@ export class WorktreeLifecycle {
|
|
|
742
1552
|
/**
|
|
743
1553
|
* Merge the completed milestone branch back to main and exit the worktree.
|
|
744
1554
|
*
|
|
745
|
-
* -
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
* teardown (preserving the branch) if no roadmap exists.
|
|
749
|
-
* - **branch mode**: validates HEAD is on the milestone branch (recovers
|
|
750
|
-
* via checkout if not), merges, rebuilds GitService.
|
|
751
|
-
* - **none**: no-op unless physically inside an auto-worktree (#2625).
|
|
1555
|
+
* Session-bound wrapper around `mergeMilestoneStandalone`. Builds a
|
|
1556
|
+
* `MergeContext` from `this.s`, layers session-side bookkeeping on top of
|
|
1557
|
+
* the result:
|
|
752
1558
|
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
1559
|
+
* - resquash-on-merge using `s.milestoneStartShas`
|
|
1560
|
+
* - merge-completion telemetry (duration)
|
|
1561
|
+
* - mode-specific session restore: worktree-mode → `restoreToProjectRoot`,
|
|
1562
|
+
* branch-mode → `gitService` rebuild
|
|
1563
|
+
*
|
|
1564
|
+
* Returns the session-less merge result. Errors propagate after
|
|
1565
|
+
* `restoreToProjectRoot()` runs so callers always receive a consistent
|
|
1566
|
+
* session.
|
|
755
1567
|
*/
|
|
756
|
-
private _mergeAndExit(
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
// paths (mergeMilestoneToMain, slice-cadence) chdir explicitly; others
|
|
761
|
-
// (branch-mode, isolation-degraded skip) do not. If the worktree dir
|
|
762
|
-
// is later torn down while cwd still points into it, every subsequent
|
|
763
|
-
// process.cwd() throws ENOENT — which after de73fb43d surfaces as a
|
|
764
|
-
// session-failed cancel and (in headless mode) terminates the whole
|
|
765
|
-
// gsd process. Best-effort: silent on failure so synthetic test paths
|
|
766
|
-
// still pass.
|
|
767
|
-
if (this.s.originalBasePath) {
|
|
768
|
-
try {
|
|
769
|
-
process.chdir(this.s.originalBasePath);
|
|
770
|
-
} catch (err) {
|
|
771
|
-
debugLog("WorktreeLifecycle", {
|
|
772
|
-
action: "mergeAndExit",
|
|
773
|
-
phase: "pre-merge-chdir-failed",
|
|
774
|
-
milestoneId,
|
|
775
|
-
originalBasePath: this.s.originalBasePath,
|
|
776
|
-
error: err instanceof Error ? err.message : String(err),
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
1568
|
+
private _mergeAndExit(
|
|
1569
|
+
milestoneId: string,
|
|
1570
|
+
ctx: NotifyCtx,
|
|
1571
|
+
): MergeStandaloneResult {
|
|
781
1572
|
// #4764 — telemetry: record start timestamp so we can emit merge duration.
|
|
782
1573
|
const mergeStartedAt = new Date().toISOString();
|
|
783
1574
|
const mergeStartMs = Date.now();
|
|
784
1575
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
reason: "isolation-degraded",
|
|
791
|
-
});
|
|
792
|
-
ctx.notify(
|
|
793
|
-
`Skipping worktree merge for ${milestoneId} — isolation was degraded (worktree creation failed earlier). Work is on the current branch.`,
|
|
794
|
-
"info",
|
|
795
|
-
);
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
const mode = this.deps.getIsolationMode(
|
|
800
|
-
this.s.originalBasePath || this.s.basePath,
|
|
801
|
-
);
|
|
802
|
-
debugLog("WorktreeLifecycle", {
|
|
803
|
-
action: "mergeAndExit",
|
|
804
|
-
milestoneId,
|
|
805
|
-
mode,
|
|
806
|
-
basePath: this.s.basePath,
|
|
807
|
-
});
|
|
808
|
-
emitJournalEvent(this.s.originalBasePath || this.s.basePath, {
|
|
809
|
-
ts: new Date().toISOString(),
|
|
810
|
-
flowId: randomUUID(),
|
|
811
|
-
seq: 0,
|
|
812
|
-
eventType: "worktree-merge-start",
|
|
813
|
-
data: { milestoneId, mode },
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
// #2625: If we are physically inside an auto-worktree, we MUST merge
|
|
817
|
-
// regardless of the current isolation config. This prevents data loss
|
|
818
|
-
// when the default isolation mode changes between versions.
|
|
819
|
-
const inWorktree =
|
|
820
|
-
this.deps.isInAutoWorktree(this.s.basePath) && this.s.originalBasePath;
|
|
821
|
-
|
|
822
|
-
if (mode === "none" && !inWorktree) {
|
|
823
|
-
debugLog("WorktreeLifecycle", {
|
|
824
|
-
action: "mergeAndExit",
|
|
1576
|
+
let result: MergeStandaloneResult;
|
|
1577
|
+
try {
|
|
1578
|
+
result = mergeMilestoneStandalone(this.deps, {
|
|
1579
|
+
originalBasePath: this.s.originalBasePath,
|
|
1580
|
+
worktreeBasePath: this.s.basePath,
|
|
825
1581
|
milestoneId,
|
|
826
|
-
|
|
827
|
-
|
|
1582
|
+
isolationDegraded: this.s.isolationDegraded,
|
|
1583
|
+
notify: ctx.notify,
|
|
828
1584
|
});
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
} else if (mode === "branch") {
|
|
836
|
-
actuallyMerged = this._mergeBranchMode(milestoneId, ctx);
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
// Standalone has already done its session-less cleanup
|
|
1587
|
+
// (chdir, SQUASH_MSG cleanup, journal event). Layer session-side
|
|
1588
|
+
// restore on top so callers get a consistent session.
|
|
1589
|
+
this.restoreToProjectRoot();
|
|
1590
|
+
throw err;
|
|
837
1591
|
}
|
|
838
1592
|
|
|
839
|
-
if (!
|
|
1593
|
+
if (!result.merged) {
|
|
1594
|
+
// Skip / no-roadmap / mode-none paths. milestoneStartShas housekeeping
|
|
1595
|
+
// is unconditional; mode-specific session restore happens for
|
|
1596
|
+
// worktree-mode (preserve-branch path tore down the worktree, so
|
|
1597
|
+
// basePath must restore) and not for branch-mode (no basePath change).
|
|
840
1598
|
this.s.milestoneStartShas.delete(milestoneId);
|
|
841
|
-
|
|
1599
|
+
if (result.mode === "worktree") {
|
|
1600
|
+
this.restoreToProjectRoot();
|
|
1601
|
+
debugLog("WorktreeLifecycle", {
|
|
1602
|
+
action: "mergeAndExit",
|
|
1603
|
+
milestoneId,
|
|
1604
|
+
result: "done",
|
|
1605
|
+
basePath: this.s.basePath,
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
return result;
|
|
842
1609
|
}
|
|
843
1610
|
|
|
844
1611
|
// #4765 — when collapse_cadence=slice AND milestone_resquash=true, the
|
|
@@ -848,19 +1615,20 @@ export class WorktreeLifecycle {
|
|
|
848
1615
|
try {
|
|
849
1616
|
const startSha = this.s.milestoneStartShas.get(milestoneId);
|
|
850
1617
|
if (startSha) {
|
|
851
|
-
const prefs =
|
|
1618
|
+
const prefs = lifecycleLoadPreferences(
|
|
1619
|
+
this.deps,
|
|
852
1620
|
this.s.originalBasePath || this.s.basePath,
|
|
853
1621
|
)?.preferences;
|
|
854
1622
|
if (
|
|
855
1623
|
getCollapseCadence(prefs) === "slice" &&
|
|
856
1624
|
getMilestoneResquash(prefs)
|
|
857
1625
|
) {
|
|
858
|
-
const
|
|
1626
|
+
const resquashResult = resquashMilestoneOnMain(
|
|
859
1627
|
this.s.originalBasePath || this.s.basePath,
|
|
860
1628
|
milestoneId,
|
|
861
1629
|
startSha,
|
|
862
1630
|
);
|
|
863
|
-
if (
|
|
1631
|
+
if (resquashResult.resquashed) {
|
|
864
1632
|
ctx.notify(
|
|
865
1633
|
`slice-cadence: re-squashed slice commits for ${milestoneId} into a single milestone commit.`,
|
|
866
1634
|
"info",
|
|
@@ -900,291 +1668,29 @@ export class WorktreeLifecycle {
|
|
|
900
1668
|
: String(telemetryErr),
|
|
901
1669
|
});
|
|
902
1670
|
}
|
|
903
|
-
return true;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
/** Worktree-mode merge body. Returns true when an actual squash-merge ran. */
|
|
907
|
-
private _mergeWorktreeMode(milestoneId: string, ctx: NotifyCtx): boolean {
|
|
908
|
-
const originalBase = this.s.originalBasePath;
|
|
909
|
-
if (!originalBase) {
|
|
910
|
-
debugLog("WorktreeLifecycle", {
|
|
911
|
-
action: "mergeAndExit",
|
|
912
|
-
milestoneId,
|
|
913
|
-
mode: "worktree",
|
|
914
|
-
skipped: true,
|
|
915
|
-
reason: "missing-original-base",
|
|
916
|
-
});
|
|
917
|
-
return false;
|
|
918
|
-
}
|
|
919
1671
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
// syncWorktreeStateBack(originalBase, basePath, milestoneId) call.
|
|
924
|
-
const finalScope = scopeMilestone(
|
|
925
|
-
createWorkspace(this.s.basePath),
|
|
926
|
-
milestoneId,
|
|
927
|
-
);
|
|
928
|
-
const { synced } =
|
|
929
|
-
this.deps.worktreeProjection.finalizeProjectionForMerge(finalScope);
|
|
930
|
-
if (synced.length > 0) {
|
|
931
|
-
debugLog("WorktreeLifecycle", {
|
|
932
|
-
action: "mergeAndExit",
|
|
933
|
-
milestoneId,
|
|
934
|
-
phase: "reverse-sync",
|
|
935
|
-
synced: synced.length,
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Resolve roadmap — try project root first, then worktree path as
|
|
940
|
-
// fallback. The worktree may hold the only copy when state-back
|
|
941
|
-
// projection silently dropped it or .gsd/ is not symlinked. Without
|
|
942
|
-
// the fallback, a missing roadmap triggers bare teardown which
|
|
943
|
-
// deletes the branch and orphans all milestone commits (#1573).
|
|
944
|
-
let roadmapPath = this.deps.resolveMilestoneFile(
|
|
945
|
-
originalBase,
|
|
946
|
-
milestoneId,
|
|
947
|
-
"ROADMAP",
|
|
948
|
-
);
|
|
949
|
-
if (
|
|
950
|
-
!roadmapPath &&
|
|
951
|
-
!isSamePathPhysical(this.s.basePath, originalBase)
|
|
952
|
-
) {
|
|
953
|
-
roadmapPath = this.deps.resolveMilestoneFile(
|
|
954
|
-
this.s.basePath,
|
|
955
|
-
milestoneId,
|
|
956
|
-
"ROADMAP",
|
|
957
|
-
);
|
|
958
|
-
if (roadmapPath) {
|
|
959
|
-
debugLog("WorktreeLifecycle", {
|
|
960
|
-
action: "mergeAndExit",
|
|
961
|
-
milestoneId,
|
|
962
|
-
phase: "roadmap-fallback",
|
|
963
|
-
note: "resolved from worktree path",
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (roadmapPath) {
|
|
969
|
-
const roadmapContent = this.deps.readFileSync(roadmapPath, "utf-8");
|
|
970
|
-
const mergeResult = this.deps.mergeMilestoneToMain(
|
|
971
|
-
originalBase,
|
|
972
|
-
milestoneId,
|
|
973
|
-
roadmapContent,
|
|
974
|
-
);
|
|
975
|
-
merged = true;
|
|
976
|
-
|
|
977
|
-
// #2945 Bug 3: mergeMilestoneToMain performs best-effort worktree
|
|
978
|
-
// cleanup internally (step 12), but it can silently fail on Windows
|
|
979
|
-
// or when the worktree directory is locked. Perform a secondary
|
|
980
|
-
// teardown here to ensure the worktree is properly cleaned up.
|
|
981
|
-
// Idempotent — if already removed, teardownAutoWorktree no-ops.
|
|
982
|
-
try {
|
|
983
|
-
this.deps.teardownAutoWorktree(originalBase, milestoneId);
|
|
984
|
-
} catch {
|
|
985
|
-
// Best-effort — primary cleanup in mergeMilestoneToMain may have
|
|
986
|
-
// already removed the worktree.
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
if (mergeResult.codeFilesChanged) {
|
|
990
|
-
ctx.notify(
|
|
991
|
-
`Milestone ${milestoneId} merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
992
|
-
"info",
|
|
993
|
-
);
|
|
994
|
-
} else {
|
|
995
|
-
// #1906 — milestone produced only .gsd/ metadata. Surface
|
|
996
|
-
// clearly so the user knows the milestone is not truly complete.
|
|
997
|
-
ctx.notify(
|
|
998
|
-
`WARNING: Milestone ${milestoneId} merged to main but contained NO code changes — only .gsd/ metadata files. ` +
|
|
999
|
-
`The milestone summary may describe planned work that was never implemented. ` +
|
|
1000
|
-
`Review the milestone output and re-run if code is missing.`,
|
|
1001
|
-
"warning",
|
|
1002
|
-
);
|
|
1003
|
-
}
|
|
1004
|
-
} else {
|
|
1005
|
-
// No roadmap at either location — teardown but PRESERVE the branch
|
|
1006
|
-
// so commits are not orphaned (#1573).
|
|
1007
|
-
this.deps.teardownAutoWorktree(originalBase, milestoneId, {
|
|
1008
|
-
preserveBranch: true,
|
|
1009
|
-
});
|
|
1010
|
-
ctx.notify(
|
|
1011
|
-
`Exited worktree for ${milestoneId} (no roadmap found — branch preserved for manual merge).`,
|
|
1012
|
-
"warning",
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
} catch (err) {
|
|
1016
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1672
|
+
// Mode-specific session restore.
|
|
1673
|
+
if (result.mode === "worktree") {
|
|
1674
|
+
this.restoreToProjectRoot();
|
|
1017
1675
|
debugLog("WorktreeLifecycle", {
|
|
1018
1676
|
action: "mergeAndExit",
|
|
1019
1677
|
milestoneId,
|
|
1020
|
-
result: "
|
|
1021
|
-
|
|
1022
|
-
fallback: "chdir-to-project-root",
|
|
1023
|
-
});
|
|
1024
|
-
emitJournalEvent(this.s.originalBasePath || this.s.basePath, {
|
|
1025
|
-
ts: new Date().toISOString(),
|
|
1026
|
-
flowId: randomUUID(),
|
|
1027
|
-
seq: 0,
|
|
1028
|
-
eventType: "worktree-merge-failed",
|
|
1029
|
-
data: { milestoneId, error: msg },
|
|
1678
|
+
result: "done",
|
|
1679
|
+
basePath: this.s.basePath,
|
|
1030
1680
|
});
|
|
1031
|
-
|
|
1032
|
-
// are intentionally preserved — nothing has been deleted. User can
|
|
1033
|
-
// retry /gsd dispatch complete-milestone or merge manually once the
|
|
1034
|
-
// underlying issue is fixed (#1668, #1891).
|
|
1035
|
-
ctx.notify(
|
|
1036
|
-
`Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry with \`/gsd dispatch complete-milestone\` or merge manually.`,
|
|
1037
|
-
"warning",
|
|
1038
|
-
);
|
|
1039
|
-
|
|
1040
|
-
// Clean up stale merge state left by failed squash-merge (#1389)
|
|
1041
|
-
try {
|
|
1042
|
-
const gitDir = join(originalBase || this.s.basePath, ".git");
|
|
1043
|
-
for (const f of ["SQUASH_MSG", "MERGE_HEAD", "MERGE_MSG"]) {
|
|
1044
|
-
const p = join(gitDir, f);
|
|
1045
|
-
if (existsSync(p)) unlinkSync(p);
|
|
1046
|
-
}
|
|
1047
|
-
} catch {
|
|
1048
|
-
/* best-effort */
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
// Error recovery: always restore to project root
|
|
1052
|
-
if (originalBase) {
|
|
1053
|
-
try {
|
|
1054
|
-
process.chdir(originalBase);
|
|
1055
|
-
} catch {
|
|
1056
|
-
/* best-effort */
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// Restore state before re-throwing so callers always get a
|
|
1061
|
-
// consistent session (#4380).
|
|
1062
|
-
this.restoreToProjectRoot();
|
|
1063
|
-
// Re-throw: MergeConflictError stops the auto loop (#2330);
|
|
1064
|
-
// non-conflict errors must also propagate so broken states are
|
|
1065
|
-
// diagnosable (#4380).
|
|
1066
|
-
throw err;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// Always restore basePath and rebuild — whether merge succeeded or failed
|
|
1070
|
-
this.restoreToProjectRoot();
|
|
1071
|
-
debugLog("WorktreeLifecycle", {
|
|
1072
|
-
action: "mergeAndExit",
|
|
1073
|
-
milestoneId,
|
|
1074
|
-
result: "done",
|
|
1075
|
-
basePath: this.s.basePath,
|
|
1076
|
-
});
|
|
1077
|
-
return merged;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
/** Branch-mode merge body. Returns true when a merge actually ran. */
|
|
1081
|
-
private _mergeBranchMode(milestoneId: string, ctx: NotifyCtx): boolean {
|
|
1082
|
-
try {
|
|
1083
|
-
const currentBranch = this.deps.getCurrentBranch(this.s.basePath);
|
|
1084
|
-
const milestoneBranch = this.deps.autoWorktreeBranch(milestoneId);
|
|
1085
|
-
|
|
1086
|
-
if (currentBranch !== milestoneBranch) {
|
|
1087
|
-
// #5538-followup: previous behaviour was to silently `return false`
|
|
1088
|
-
// when HEAD wasn't on the milestone branch — that let the loop
|
|
1089
|
-
// advance with the milestone's commits stranded on the branch.
|
|
1090
|
-
// Attempt recovery by force-checking-out the milestone branch; if
|
|
1091
|
-
// that fails, throw so the caller pauses auto-mode and the user
|
|
1092
|
-
// sees the failure instead of a silent merge skip.
|
|
1093
|
-
debugLog("WorktreeLifecycle", {
|
|
1094
|
-
action: "mergeAndExit",
|
|
1095
|
-
milestoneId,
|
|
1096
|
-
mode: "branch",
|
|
1097
|
-
recovery: "checkout-milestone-branch",
|
|
1098
|
-
currentBranch,
|
|
1099
|
-
milestoneBranch,
|
|
1100
|
-
});
|
|
1101
|
-
try {
|
|
1102
|
-
this.deps.checkoutBranch(this.s.basePath, milestoneBranch);
|
|
1103
|
-
} catch (checkoutErr) {
|
|
1104
|
-
const checkoutMsg =
|
|
1105
|
-
checkoutErr instanceof Error
|
|
1106
|
-
? checkoutErr.message
|
|
1107
|
-
: String(checkoutErr);
|
|
1108
|
-
ctx.notify(
|
|
1109
|
-
`Cannot merge milestone ${milestoneId}: working tree is on ${currentBranch} and checkout to ${milestoneBranch} failed (${checkoutMsg}). Resolve manually and run /gsd auto to resume.`,
|
|
1110
|
-
"error",
|
|
1111
|
-
);
|
|
1112
|
-
throw new UserNotifiedError(checkoutMsg, checkoutErr);
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
const reverify = this.deps.getCurrentBranch(this.s.basePath);
|
|
1116
|
-
if (reverify !== milestoneBranch) {
|
|
1117
|
-
const reverifyMsg = `branch checkout to ${milestoneBranch} reported success but current branch is ${reverify}`;
|
|
1118
|
-
ctx.notify(
|
|
1119
|
-
`Cannot merge milestone ${milestoneId}: ${reverifyMsg}. Resolve manually and run /gsd auto to resume.`,
|
|
1120
|
-
"error",
|
|
1121
|
-
);
|
|
1122
|
-
throw new UserNotifiedError(reverifyMsg);
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
const roadmapPath = this.deps.resolveMilestoneFile(
|
|
1127
|
-
this.s.basePath,
|
|
1128
|
-
milestoneId,
|
|
1129
|
-
"ROADMAP",
|
|
1130
|
-
);
|
|
1131
|
-
if (!roadmapPath) {
|
|
1132
|
-
debugLog("WorktreeLifecycle", {
|
|
1133
|
-
action: "mergeAndExit",
|
|
1134
|
-
milestoneId,
|
|
1135
|
-
mode: "branch",
|
|
1136
|
-
skipped: true,
|
|
1137
|
-
reason: "no-roadmap",
|
|
1138
|
-
});
|
|
1139
|
-
return false;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
const roadmapContent = this.deps.readFileSync(roadmapPath, "utf-8");
|
|
1143
|
-
const mergeResult = this.deps.mergeMilestoneToMain(
|
|
1144
|
-
this.s.basePath,
|
|
1145
|
-
milestoneId,
|
|
1146
|
-
roadmapContent,
|
|
1147
|
-
);
|
|
1148
|
-
|
|
1681
|
+
} else if (result.mode === "branch") {
|
|
1149
1682
|
// Rebuild GitService after merge (branch HEAD changed)
|
|
1150
1683
|
rebuildGitService(this.s, this.deps);
|
|
1151
|
-
|
|
1152
|
-
if (mergeResult.codeFilesChanged) {
|
|
1153
|
-
ctx.notify(
|
|
1154
|
-
`Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1155
|
-
"info",
|
|
1156
|
-
);
|
|
1157
|
-
} else {
|
|
1158
|
-
ctx.notify(
|
|
1159
|
-
`WARNING: Milestone ${milestoneId} merged (branch mode) but contained NO code changes — only .gsd/ metadata. ` +
|
|
1160
|
-
`Review the milestone output and re-run if code is missing.`,
|
|
1161
|
-
"warning",
|
|
1162
|
-
);
|
|
1163
|
-
}
|
|
1164
|
-
debugLog("WorktreeLifecycle", {
|
|
1165
|
-
action: "mergeAndExit",
|
|
1166
|
-
milestoneId,
|
|
1167
|
-
mode: "branch",
|
|
1168
|
-
result: "success",
|
|
1169
|
-
});
|
|
1170
|
-
return true;
|
|
1171
|
-
} catch (err) {
|
|
1172
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1173
|
-
debugLog("WorktreeLifecycle", {
|
|
1174
|
-
action: "mergeAndExit",
|
|
1175
|
-
milestoneId,
|
|
1176
|
-
mode: "branch",
|
|
1177
|
-
result: "error",
|
|
1178
|
-
error: msg,
|
|
1179
|
-
});
|
|
1180
|
-
if (!(err instanceof UserNotifiedError)) {
|
|
1181
|
-
ctx.notify(`Milestone merge failed (branch mode): ${msg}`, "warning");
|
|
1182
|
-
}
|
|
1183
|
-
// Re-throw all errors so callers can apply their own recovery (#4380).
|
|
1184
|
-
throw err;
|
|
1185
1684
|
}
|
|
1685
|
+
return result;
|
|
1186
1686
|
}
|
|
1187
1687
|
|
|
1688
|
+
// ── Removed: _mergeWorktreeMode / _mergeBranchMode bodies ────────────
|
|
1689
|
+
// The merge bodies moved to file-scope `_mergeWorktreeModeImpl` and
|
|
1690
|
+
// `_mergeBranchModeImpl`, callable from the session-less
|
|
1691
|
+
// `mergeMilestoneStandalone` entry. The previous private methods are
|
|
1692
|
+
// gone; `_mergeAndExit` above is the only session-bound caller.
|
|
1693
|
+
|
|
1188
1694
|
/**
|
|
1189
1695
|
* Fall back to branch-mode for `milestoneId` after a failed worktree
|
|
1190
1696
|
* creation, marking the session's isolation as degraded.
|
|
@@ -1210,9 +1716,9 @@ export class WorktreeLifecycle {
|
|
|
1210
1716
|
this.s.originalBasePath,
|
|
1211
1717
|
);
|
|
1212
1718
|
try {
|
|
1213
|
-
this.deps
|
|
1719
|
+
lifecycleEnterBranchMode(this.deps, basePath, milestoneId);
|
|
1214
1720
|
rebuildGitService(this.s, this.deps);
|
|
1215
|
-
|
|
1721
|
+
invalidateAllCaches();
|
|
1216
1722
|
this.s.isolationDegraded = true;
|
|
1217
1723
|
ctx.notify(
|
|
1218
1724
|
`Switched to branch milestone/${milestoneId} (isolation degraded).`,
|
|
@@ -1229,18 +1735,190 @@ export class WorktreeLifecycle {
|
|
|
1229
1735
|
}
|
|
1230
1736
|
|
|
1231
1737
|
/**
|
|
1232
|
-
* Restore `s.basePath` to `s.originalBasePath
|
|
1233
|
-
* No-op when `originalBasePath` is empty (fresh
|
|
1738
|
+
* Restore `s.basePath` to `s.originalBasePath`, chdir process cwd, and
|
|
1739
|
+
* rebuild `s.gitService`. No-op when `originalBasePath` is empty (fresh
|
|
1740
|
+
* sessions).
|
|
1234
1741
|
*
|
|
1235
1742
|
* Used by error/cleanup paths that need the session to behave as if the
|
|
1236
1743
|
* worktree was never entered. Does NOT teardown the worktree directory —
|
|
1237
1744
|
* callers that need teardown go through `exitMilestone({ merge: false })`.
|
|
1745
|
+
*
|
|
1746
|
+
* ADR-016 phase 3 (#5693): chdir lives inside the verb so callers do not
|
|
1747
|
+
* pair `restoreToProjectRoot()` with a redundant `process.chdir`. The
|
|
1748
|
+
* chdir runs BEFORE the throwable work (`rebuildGitService`, cache
|
|
1749
|
+
* invalidation) so that cleanup-path cwd is restored even if the
|
|
1750
|
+
* downstream rebuild throws. The chdir itself is best-effort; failure is
|
|
1751
|
+
* logged via debugLog and swallowed.
|
|
1238
1752
|
*/
|
|
1239
1753
|
restoreToProjectRoot(): void {
|
|
1240
1754
|
if (!this.s.originalBasePath) return;
|
|
1241
1755
|
this.s.basePath = this.s.originalBasePath;
|
|
1756
|
+
try {
|
|
1757
|
+
process.chdir(this.s.basePath);
|
|
1758
|
+
} catch (err) {
|
|
1759
|
+
debugLog("WorktreeLifecycle", {
|
|
1760
|
+
action: "restoreToProjectRoot",
|
|
1761
|
+
result: "chdir-failed",
|
|
1762
|
+
basePath: this.s.basePath,
|
|
1763
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1242
1766
|
rebuildGitService(this.s, this.deps);
|
|
1243
|
-
|
|
1767
|
+
invalidateAllCaches();
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
/**
|
|
1771
|
+
* Adopt a session root (ADR-016 phase 2 / B2, issue #5620).
|
|
1772
|
+
*
|
|
1773
|
+
* Sole owner of `s.basePath` mutation for bootstrap-class transitions:
|
|
1774
|
+
* initial session start, paused-resume entry (before persisted-state
|
|
1775
|
+
* consultation), and hook-trigger session activation. Defensive about
|
|
1776
|
+
* `s.originalBasePath`:
|
|
1777
|
+
*
|
|
1778
|
+
* - When `originalBase` is explicit: overwrite.
|
|
1779
|
+
* - Otherwise, set `s.originalBasePath` only if it is currently empty —
|
|
1780
|
+
* resume paths that already restored `s.originalBasePath` from paused
|
|
1781
|
+
* metadata keep their value.
|
|
1782
|
+
*
|
|
1783
|
+
* Does NOT chdir; callers that need cwd alignment with the new basePath
|
|
1784
|
+
* are responsible for it. Does NOT rebuild `s.gitService` — callers that
|
|
1785
|
+
* mutate `s.basePath` to a non-project-root path (e.g. a worktree on a
|
|
1786
|
+
* subsequent milestone enter) go through `enterMilestone`, which handles
|
|
1787
|
+
* the rebuild.
|
|
1788
|
+
*/
|
|
1789
|
+
adoptSessionRoot(base: string, originalBase?: string): void {
|
|
1790
|
+
this.s.basePath = base;
|
|
1791
|
+
if (originalBase !== undefined) {
|
|
1792
|
+
this.s.originalBasePath = originalBase;
|
|
1793
|
+
} else if (!this.s.originalBasePath) {
|
|
1794
|
+
this.s.originalBasePath = base;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
/**
|
|
1799
|
+
* Resume from a paused session (ADR-016 phase 2 / B3, issue #5621).
|
|
1800
|
+
*
|
|
1801
|
+
* Adopts `persistedWorktreePath` as `s.basePath` when the path is
|
|
1802
|
+
* non-null and exists on disk; otherwise falls back to `base`. Mirrors
|
|
1803
|
+
* the resume guard at `auto.ts:2164` — a stale or removed worktree
|
|
1804
|
+
* directory must not strand the resumed session in an invalid root.
|
|
1805
|
+
*
|
|
1806
|
+
* Folds in the body of the legacy `_resolvePausedResumeBasePathForTest`
|
|
1807
|
+
* helper (see `resolvePausedResumeBasePath` below). After this verb
|
|
1808
|
+
* lands the helper is deleted from `auto.ts` per the slice-7 closure
|
|
1809
|
+
* decision to retire `_*ForTest` suffixes from production paths.
|
|
1810
|
+
*
|
|
1811
|
+
* Like `adoptSessionRoot`, this is a pure session-state mutation — no
|
|
1812
|
+
* chdir, no git service rebuild, no cache invalidation.
|
|
1813
|
+
*/
|
|
1814
|
+
resumeFromPausedSession(
|
|
1815
|
+
base: string,
|
|
1816
|
+
persistedWorktreePath: string | null,
|
|
1817
|
+
): void {
|
|
1818
|
+
this.s.basePath = resolvePausedResumeBasePath(base, persistedWorktreePath);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Adopt an orphan worktree for a bootstrap-time merge (ADR-016 phase 2 / B4,
|
|
1823
|
+
* issue #5622).
|
|
1824
|
+
*
|
|
1825
|
+
* Owns the swap-run-revert protocol that bootstrap previously open-coded:
|
|
1826
|
+
*
|
|
1827
|
+
* 1. Snapshot prior `s.basePath` and `s.originalBasePath`.
|
|
1828
|
+
* 2. Resolve `getAutoWorktreePath(base, milestoneId) ?? base` before
|
|
1829
|
+
* mutating session state, then set `s.originalBasePath = base` and
|
|
1830
|
+
* `s.basePath` to the resolved path.
|
|
1831
|
+
* 3. Invoke the caller-supplied `run` callback under the swap.
|
|
1832
|
+
* 4. On `!result.merged`: revert to `base` and `chdir(base)` so the
|
|
1833
|
+
* caller can return early without leaving the session in a half-
|
|
1834
|
+
* swapped state.
|
|
1835
|
+
* 5. On `result.merged && !s.active`: revert to the snapshotted prior
|
|
1836
|
+
* paths (the orphan merge succeeded but bootstrap chose not to keep
|
|
1837
|
+
* the session active).
|
|
1838
|
+
* 6. On `result.merged && s.active`: leave the swap in place — the
|
|
1839
|
+
* loop will continue from the worktree path.
|
|
1840
|
+
*
|
|
1841
|
+
* The callback shape forces every caller through the same revert
|
|
1842
|
+
* protocol; an open-coded swap that forgets to revert on failure was the
|
|
1843
|
+
* original bug pattern this verb is designed to prevent.
|
|
1844
|
+
*/
|
|
1845
|
+
adoptOrphanWorktree<T extends { merged: boolean }>(
|
|
1846
|
+
milestoneId: string,
|
|
1847
|
+
base: string,
|
|
1848
|
+
run: () => T,
|
|
1849
|
+
): T {
|
|
1850
|
+
validateMilestoneId(milestoneId);
|
|
1851
|
+
|
|
1852
|
+
const priorBasePath = this.s.basePath;
|
|
1853
|
+
const priorOriginalBasePath = this.s.originalBasePath;
|
|
1854
|
+
const restorePriorPaths = (phase: string): void => {
|
|
1855
|
+
this.s.basePath = priorBasePath || base;
|
|
1856
|
+
this.s.originalBasePath = priorOriginalBasePath || base;
|
|
1857
|
+
try {
|
|
1858
|
+
process.chdir(this.s.originalBasePath || base);
|
|
1859
|
+
} catch (err) {
|
|
1860
|
+
debugLog("WorktreeLifecycle", {
|
|
1861
|
+
action: "adoptOrphanWorktree",
|
|
1862
|
+
phase,
|
|
1863
|
+
base: this.s.originalBasePath || base,
|
|
1864
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1869
|
+
let adoptedBasePath: string;
|
|
1870
|
+
try {
|
|
1871
|
+
const wtPathFn =
|
|
1872
|
+
primitiveOverrides(this.deps).getAutoWorktreePath ?? getAutoWorktreePath;
|
|
1873
|
+
adoptedBasePath = wtPathFn(base, milestoneId) ?? base;
|
|
1874
|
+
} catch (err) {
|
|
1875
|
+
restorePriorPaths("rollback-resolve-worktree-failed");
|
|
1876
|
+
throw err;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// Swap into the orphan worktree.
|
|
1880
|
+
this.s.originalBasePath = base;
|
|
1881
|
+
this.s.basePath = adoptedBasePath;
|
|
1882
|
+
|
|
1883
|
+
let result: T;
|
|
1884
|
+
try {
|
|
1885
|
+
result = run();
|
|
1886
|
+
} catch (err) {
|
|
1887
|
+
restorePriorPaths("rollback-run-failed");
|
|
1888
|
+
throw err;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
if (!result.merged) {
|
|
1892
|
+
// Failed orphan merge — revert to project root so the caller can
|
|
1893
|
+
// safely return early without leaving the session in an invalid
|
|
1894
|
+
// basePath. Mirror the chdir that bootstrap performed inline.
|
|
1895
|
+
this.s.basePath = base;
|
|
1896
|
+
this.s.originalBasePath = base;
|
|
1897
|
+
try {
|
|
1898
|
+
process.chdir(base);
|
|
1899
|
+
} catch (err) {
|
|
1900
|
+
debugLog("WorktreeLifecycle", {
|
|
1901
|
+
action: "adoptOrphanWorktree",
|
|
1902
|
+
phase: "revert-chdir-failed",
|
|
1903
|
+
base,
|
|
1904
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
return result;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
if (!this.s.active) {
|
|
1911
|
+
// Merge succeeded but the session was not (re)activated — restore
|
|
1912
|
+
// the snapshotted paths so the calling context resumes where it
|
|
1913
|
+
// was, with the orphan branch now merged on main.
|
|
1914
|
+
this.s.basePath = priorBasePath || base;
|
|
1915
|
+
this.s.originalBasePath = priorOriginalBasePath || base;
|
|
1916
|
+
}
|
|
1917
|
+
// else: merged && active — leave the swap; the loop continues from
|
|
1918
|
+
// the worktree path. Subsequent milestone enters mutate `s.basePath`
|
|
1919
|
+
// through their own Lifecycle verbs.
|
|
1920
|
+
|
|
1921
|
+
return result;
|
|
1244
1922
|
}
|
|
1245
1923
|
|
|
1246
1924
|
/** True if `milestoneId` is the session's currently-active milestone. */
|