@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.4fb2d460
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 +10 -10
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +1 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
- package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +12 -3
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
- package/dist/resources/extensions/gsd/auto-start.js +4 -3
- package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
- package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
- package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
- package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
- package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
- package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
- package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
- package/dist/resources/extensions/gsd/auto.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
- package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
- package/dist/resources/extensions/gsd/db/queries.js +9 -9
- package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
- package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
- package/dist/resources/extensions/gsd/git-service.js +45 -0
- package/dist/resources/extensions/gsd/gsd-db.js +23 -11
- package/dist/resources/extensions/gsd/guided-flow.js +22 -17
- package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
- package/dist/resources/extensions/gsd/md-importer.js +89 -23
- package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
- package/dist/resources/extensions/gsd/paths.js +17 -3
- package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
- package/dist/resources/extensions/gsd/preferences.js +20 -0
- package/dist/resources/extensions/gsd/prompts/system.md +6 -6
- package/dist/resources/extensions/gsd/quick.js +12 -3
- package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
- package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
- package/dist/resources/extensions/gsd/unit-registry.js +12 -2
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
- package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
- package/dist/resources/extensions/shared/interview-ui.js +62 -9
- 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 +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +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/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
- package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
- package/package.json +6 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +14 -9
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
- package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
- package/packages/pi-ai/dist/tool-result-content.js +62 -0
- package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
- package/packages/pi-ai/dist/types.d.ts +8 -20
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +4 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/core/export-html/template.js +53 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +1 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
- package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
- package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +12 -3
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
- package/src/resources/extensions/gsd/auto-start.ts +5 -3
- package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
- package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
- package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
- package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
- package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
- package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
- package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
- package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
- package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
- package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
- package/src/resources/extensions/gsd/auto.ts +11 -17
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
- package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
- package/src/resources/extensions/gsd/db/queries.ts +9 -9
- package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
- package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +53 -0
- package/src/resources/extensions/gsd/gsd-db.ts +33 -11
- package/src/resources/extensions/gsd/guided-flow.ts +26 -18
- package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
- package/src/resources/extensions/gsd/md-importer.ts +93 -22
- package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/paths.ts +21 -3
- package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
- package/src/resources/extensions/gsd/preferences-types.ts +30 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
- package/src/resources/extensions/gsd/preferences.ts +20 -0
- package/src/resources/extensions/gsd/prompts/system.md +6 -6
- package/src/resources/extensions/gsd/quick.ts +11 -3
- package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
- package/src/resources/extensions/gsd/tool-contract.ts +29 -0
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
- package/src/resources/extensions/gsd/unit-registry.ts +16 -2
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
- package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
- package/src/resources/extensions/shared/interview-ui.ts +58 -11
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
- /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// gsd-pi — Pre-merge stash handling for auto-worktree merge closeout.
|
|
2
|
+
//
|
|
3
|
+
// Owns the local-work protection around milestone merge: stash before merge,
|
|
4
|
+
// DB handle cycling for Windows, restoring on merge failures, and post-commit
|
|
5
|
+
// stash-pop conflict recovery.
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { closeWorkflowDatabase, getWorkflowDatabasePath, openWorkflowDatabasePath, } from "./db-workspace.js";
|
|
9
|
+
import { nativeAddPaths, nativeConflictFiles, nativeLsFiles, nativeRmForce, } from "./native-git-bridge.js";
|
|
10
|
+
import { gsdJsonlFilesWithConflictMarkers, hasConflictMarkers, popStashByRef, stashAlreadyExistsFilesFromError, stashRefFromError, } from "./worktree-git-recovery.js";
|
|
11
|
+
import { logWarning } from "./workflow-logger.js";
|
|
12
|
+
export function createPreMergeStash(basePath, milestoneId, cycleDbHandles) {
|
|
13
|
+
let stashed = false;
|
|
14
|
+
let marker = null;
|
|
15
|
+
const dbPathToReopen = cycleDbHandles ? getWorkflowDatabasePath() : null;
|
|
16
|
+
return {
|
|
17
|
+
stash() {
|
|
18
|
+
closeDbBeforeStashIfNeeded(cycleDbHandles);
|
|
19
|
+
try {
|
|
20
|
+
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
21
|
+
cwd: basePath,
|
|
22
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
23
|
+
encoding: "utf-8",
|
|
24
|
+
}).trim();
|
|
25
|
+
if (!status)
|
|
26
|
+
return;
|
|
27
|
+
marker = createStashMarker(milestoneId);
|
|
28
|
+
execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd: pre-merge stash for ${milestoneId} [${marker}]`], { cwd: basePath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
|
|
29
|
+
stashed = true;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
// Stash failure is non-fatal — proceed without stash and let the merge
|
|
33
|
+
// report the dirty tree if it fails.
|
|
34
|
+
logWarning("worktree", `git stash failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
reopenDbAfterMerge() {
|
|
38
|
+
if (!cycleDbHandles || !dbPathToReopen)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
openWorkflowDatabasePath(dbPathToReopen);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
logWarning("worktree", `post-merge db reopen failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
restoreForMergeFailure() {
|
|
48
|
+
if (!stashed)
|
|
49
|
+
return;
|
|
50
|
+
try {
|
|
51
|
+
popStashByRef(basePath, marker);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logWarning("worktree", `git stash pop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
restoreAfterCommit() {
|
|
58
|
+
if (!stashed)
|
|
59
|
+
return;
|
|
60
|
+
restoreStashAfterCommit(basePath, marker);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function closeDbBeforeStashIfNeeded(cycleDbHandles) {
|
|
65
|
+
if (!cycleDbHandles)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
closeWorkflowDatabase();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
logWarning("worktree", `pre-stash db close failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function createStashMarker(milestoneId) {
|
|
75
|
+
return `gsd-pre-merge:${milestoneId}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
|
|
76
|
+
}
|
|
77
|
+
function restoreStashAfterCommit(basePath, marker) {
|
|
78
|
+
let stashRefForDrop = null;
|
|
79
|
+
try {
|
|
80
|
+
stashRefForDrop = popStashByRef(basePath, marker);
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
stashRefForDrop = stashRefFromError(e);
|
|
84
|
+
logWarning("worktree", `git stash pop failed, attempting conflict resolution: ${e.message}`);
|
|
85
|
+
resolveStashPopConflict(basePath, e, stashRefForDrop);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function resolveStashPopConflict(basePath, error, stashRefForDrop) {
|
|
89
|
+
const uu = nativeConflictFiles(basePath);
|
|
90
|
+
const gsdUU = uu.filter((f) => f.startsWith(".gsd/"));
|
|
91
|
+
const nonGsdUU = uu.filter((f) => !f.startsWith(".gsd/"));
|
|
92
|
+
const stashPopMessage = error instanceof Error ? error.message : String(error);
|
|
93
|
+
const isUntrackedRestoreFailure = stashPopMessage.includes("could not restore untracked files from stash");
|
|
94
|
+
const gsdContentConflicts = [];
|
|
95
|
+
const alreadyExists = stashAlreadyExistsFilesFromError(error);
|
|
96
|
+
if (isUntrackedRestoreFailure) {
|
|
97
|
+
gsdContentConflicts.push(...gsdJsonlFilesWithConflictMarkers(basePath));
|
|
98
|
+
}
|
|
99
|
+
const gsdConflictFiles = [...new Set([...gsdUU, ...gsdContentConflicts])];
|
|
100
|
+
resolveGsdConflictFiles(basePath, gsdConflictFiles);
|
|
101
|
+
if (gsdConflictFiles.length > 0 && nonGsdUU.length === 0) {
|
|
102
|
+
dropStashIfNoNonGsdConflictsRemain(basePath, stashRefForDrop);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (gsdUU.length === 0 && nonGsdUU.length === 0 && alreadyExists.length > 0) {
|
|
106
|
+
dropRecordedStash(basePath, stashRefForDrop);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (nonGsdUU.length > 0) {
|
|
110
|
+
logWarning("reconcile", "Stash pop conflict on non-.gsd files after merge", {
|
|
111
|
+
files: nonGsdUU.join(", "),
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
logWarning("worktree", "git stash pop failed without resolvable conflict files; leaving stash for manual recovery");
|
|
116
|
+
}
|
|
117
|
+
function resolveGsdConflictFiles(basePath, conflictFiles) {
|
|
118
|
+
for (const file of conflictFiles) {
|
|
119
|
+
try {
|
|
120
|
+
// Accept the committed (HEAD) version of the state file.
|
|
121
|
+
execFileSync("git", ["checkout", "HEAD", "--", file], {
|
|
122
|
+
cwd: basePath,
|
|
123
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
124
|
+
encoding: "utf-8",
|
|
125
|
+
});
|
|
126
|
+
nativeAddPaths(basePath, [file]);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
// Last resort: remove the conflicted state file.
|
|
130
|
+
logWarning("worktree", `checkout HEAD failed for ${file}, removing: ${err.message}`);
|
|
131
|
+
nativeRmForce(basePath, [file]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function dropStashIfNoNonGsdConflictsRemain(basePath, stashRefForDrop) {
|
|
136
|
+
const remainingUnmerged = nativeConflictFiles(basePath);
|
|
137
|
+
const nonGsdUnmerged = remainingUnmerged.filter((f) => !f.startsWith(".gsd/"));
|
|
138
|
+
const markerCandidates = Array.from(new Set([
|
|
139
|
+
...nonGsdUnmerged,
|
|
140
|
+
...nativeLsFiles(basePath, "."),
|
|
141
|
+
])).filter((f) => !f.startsWith(".gsd/"));
|
|
142
|
+
const nonGsdMarkerConflicts = markerCandidates.filter((f) => hasConflictMarkers(join(basePath, f)));
|
|
143
|
+
const hasRemainingNonGsdConflicts = nonGsdUnmerged.length > 0 || nonGsdMarkerConflicts.length > 0;
|
|
144
|
+
if (hasRemainingNonGsdConflicts) {
|
|
145
|
+
const files = Array.from(new Set([...nonGsdUnmerged, ...nonGsdMarkerConflicts]));
|
|
146
|
+
logWarning("reconcile", "Leaving stash because non-.gsd conflicts remain after auto-resolution", {
|
|
147
|
+
files: files.join(", "),
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
dropRecordedStash(basePath, stashRefForDrop);
|
|
152
|
+
}
|
|
153
|
+
function dropRecordedStash(basePath, stashRefForDrop) {
|
|
154
|
+
if (!stashRefForDrop) {
|
|
155
|
+
logWarning("worktree", "recorded stash entry could not be resolved; skipping automatic drop");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
execFileSync("git", ["stash", "drop", stashRefForDrop], {
|
|
160
|
+
cwd: basePath,
|
|
161
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
162
|
+
encoding: "utf-8",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
logWarning("worktree", `git stash drop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// gsd-pi — Auto-worktree merge module.
|
|
2
|
+
//
|
|
3
|
+
// Owns milestone merge/closeout for auto-worktrees: dirty-state capture, DB
|
|
4
|
+
// reconciliation, integration-branch merge, stash/shelter recovery, publication,
|
|
5
|
+
// and safe teardown.
|
|
6
|
+
import { debugLog } from "./debug-logger.js";
|
|
7
|
+
import { GSDError, GSD_GIT_ERROR } from "./errors.js";
|
|
8
|
+
import { autoResolveSafeConflictPaths } from "./git-conflict-resolve.js";
|
|
9
|
+
import { MergeConflictError, RUNTIME_EXCLUSION_PATHS, } from "./git-service.js";
|
|
10
|
+
import { getMilestone, isDbAvailable, } from "./gsd-db.js";
|
|
11
|
+
import { nativeAddAllWithExclusions, nativeCommit, nativeConflictFiles, nativeGetCurrentBranch, nativeMergeRegular, nativeMergeSquash, nativeWorkingTreeStatus, } from "./native-git-bridge.js";
|
|
12
|
+
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
13
|
+
import { publishMilestone } from "./publication.js";
|
|
14
|
+
import { autoWorktreeBranch, } from "./auto-worktree-branch-lifecycle.js";
|
|
15
|
+
import { createMilestoneDirectoryShelter } from "./auto-worktree-milestone-shelter.js";
|
|
16
|
+
import { getActiveWorkspace } from "./auto-worktree-session-registry.js";
|
|
17
|
+
import { assertNoUnanchoredCodeChangesAfterEmptyMerge, detectMergedCodeFilesChanged, } from "./auto-worktree-merge-code-changes.js";
|
|
18
|
+
import { reconcileMilestoneBranchHead } from "./auto-worktree-merge-branch-head.js";
|
|
19
|
+
import { finalizeAlreadyMergedMilestoneIfReachable } from "./auto-worktree-merge-already-merged.js";
|
|
20
|
+
import { cleanupMergedMilestoneWorktree } from "./auto-worktree-merge-cleanup.js";
|
|
21
|
+
import { assertMilestoneDbReadyForMerge } from "./auto-worktree-merge-db-ready.js";
|
|
22
|
+
import { prepareIntegrationBranchForMilestoneMerge } from "./auto-worktree-merge-integration-branch.js";
|
|
23
|
+
import { buildMilestoneMergeMessage } from "./auto-worktree-merge-message.js";
|
|
24
|
+
import { assertMilestoneWorktreeCleanBeforeTeardown } from "./auto-worktree-merge-pre-teardown.js";
|
|
25
|
+
import { createPreMergeStash } from "./auto-worktree-merge-stash.js";
|
|
26
|
+
import { cleanupConflictState, removeMergeStateFiles, } from "./worktree-git-recovery.js";
|
|
27
|
+
import { logWarning } from "./workflow-logger.js";
|
|
28
|
+
export { _setRestoreEntryFnForTests } from "./auto-worktree-milestone-shelter.js";
|
|
29
|
+
// ─── Merge Milestone -> Main ───────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Auto-commit any dirty (uncommitted) state in the given directory.
|
|
32
|
+
* Returns true if a commit was made, false if working tree was clean.
|
|
33
|
+
*/
|
|
34
|
+
function autoCommitDirtyState(cwd) {
|
|
35
|
+
try {
|
|
36
|
+
const status = nativeWorkingTreeStatus(cwd);
|
|
37
|
+
if (!status)
|
|
38
|
+
return false;
|
|
39
|
+
nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
|
|
40
|
+
const result = nativeCommit(cwd, "chore: auto-commit before milestone merge");
|
|
41
|
+
return result !== null;
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
debugLog("autoCommitDirtyState", { error: String(e) });
|
|
45
|
+
throw new GSDError(GSD_GIT_ERROR, `Failed to auto-commit dirty worktree state before milestone merge: ${e instanceof Error ? e.message : String(e)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Squash-merge the milestone branch into main with a rich commit message
|
|
50
|
+
* listing all completed slices, then tear down the worktree.
|
|
51
|
+
*
|
|
52
|
+
* Sequence:
|
|
53
|
+
* 1. Auto-commit dirty worktree state
|
|
54
|
+
* 2. chdir to originalBasePath
|
|
55
|
+
* 3. git checkout main
|
|
56
|
+
* 4. git merge --squash milestone/<MID>
|
|
57
|
+
* 5. git commit with rich message
|
|
58
|
+
* 6. Auto-push if enabled
|
|
59
|
+
* 7. Delete milestone branch
|
|
60
|
+
* 8. Remove worktree directory
|
|
61
|
+
* 9. Clear originalBase
|
|
62
|
+
*
|
|
63
|
+
* On merge conflict: throws MergeConflictError.
|
|
64
|
+
* On "nothing to commit" after squash: safe only if milestone work is already
|
|
65
|
+
* on the integration branch. Throws if unanchored code changes would be lost.
|
|
66
|
+
*
|
|
67
|
+
* @internal **Do not call directly.** This is the inner squash-merge primitive
|
|
68
|
+
* for the Worktree Lifecycle Module (ADR-016 phase 2 / A3, issue #5619).
|
|
69
|
+
* Production callers must go through `WorktreeLifecycle.mergeMilestoneStandalone`
|
|
70
|
+
* or `WorktreeLifecycle.exitMilestone({ merge: true })`. The export keyword
|
|
71
|
+
* is preserved for legacy tests and the default transaction adapter; production
|
|
72
|
+
* wiring depends on `createDefaultMilestoneMergeTransaction()` instead.
|
|
73
|
+
*/
|
|
74
|
+
export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapContent) {
|
|
75
|
+
const worktreeCwd = process.cwd();
|
|
76
|
+
const milestoneBranch = autoWorktreeBranch(milestoneId);
|
|
77
|
+
// 1. Auto-commit dirty state before leaving.
|
|
78
|
+
// Guard: when we entered through an auto-worktree (originalBase is set),
|
|
79
|
+
// only auto-commit when cwd is on the milestone branch. In parallel mode,
|
|
80
|
+
// cwd may be on the integration branch after a prior merge's
|
|
81
|
+
// MergeConflictError left cwd unrestored. Auto-committing on the
|
|
82
|
+
// integration branch captures dirty files from OTHER milestones under a
|
|
83
|
+
// misleading commit message, contaminating the main branch (#2929).
|
|
84
|
+
//
|
|
85
|
+
// When activeWorkspace is null (branch mode, no worktree), autoCommitDirtyState
|
|
86
|
+
// runs unconditionally — the caller is responsible for cwd placement.
|
|
87
|
+
{
|
|
88
|
+
let shouldAutoCommit = true;
|
|
89
|
+
if (getActiveWorkspace() !== null) {
|
|
90
|
+
try {
|
|
91
|
+
const currentBranch = nativeGetCurrentBranch(worktreeCwd);
|
|
92
|
+
shouldAutoCommit = currentBranch === milestoneBranch;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// If we can't determine the branch, skip the auto-commit to be safe
|
|
96
|
+
shouldAutoCommit = false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (shouldAutoCommit) {
|
|
100
|
+
autoCommitDirtyState(worktreeCwd);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Reconcile DB state and prove closeout before leaving worktree context.
|
|
104
|
+
assertMilestoneDbReadyForMerge({
|
|
105
|
+
milestoneId,
|
|
106
|
+
projectRoot: originalBasePath_,
|
|
107
|
+
worktreeCwd,
|
|
108
|
+
});
|
|
109
|
+
// 2. Build completed-slice summaries and rich commit message.
|
|
110
|
+
const { commitMessage, milestoneTitle, sliceSummaries } = buildMilestoneMergeMessage({
|
|
111
|
+
milestoneId,
|
|
112
|
+
milestoneBranch,
|
|
113
|
+
roadmapContent,
|
|
114
|
+
});
|
|
115
|
+
// 3. chdir to original base
|
|
116
|
+
// Note: previousCwd captures the cwd at this point — i.e. the worktree cwd
|
|
117
|
+
// entering the function. Subsequent throws restore to previousCwd, leaving
|
|
118
|
+
// the caller in worktree-cwd; callers (worktree-resolver) are responsible
|
|
119
|
+
// for any further cwd movement on the error path.
|
|
120
|
+
const previousCwd = process.cwd();
|
|
121
|
+
process.chdir(originalBasePath_);
|
|
122
|
+
// 4/5. Resolve and prepare the integration branch. The integration-branch
|
|
123
|
+
// module owns stale metadata fallback, self-merge fail-closed behavior,
|
|
124
|
+
// detached HEAD protection, transient state cleanup, and checkout.
|
|
125
|
+
const prefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
|
|
126
|
+
const { mainBranch } = prepareIntegrationBranchForMilestoneMerge({
|
|
127
|
+
milestoneBranch,
|
|
128
|
+
milestoneId,
|
|
129
|
+
prefs,
|
|
130
|
+
previousCwd,
|
|
131
|
+
projectRoot: originalBasePath_,
|
|
132
|
+
});
|
|
133
|
+
// 6b. Reconcile worktree HEAD with milestone branch ref (#1846).
|
|
134
|
+
// The branch-head module owns the data-loss guard: fast-forward stale
|
|
135
|
+
// branch refs to detached worktree commits, and fail loudly on divergence.
|
|
136
|
+
reconcileMilestoneBranchHead({
|
|
137
|
+
projectRoot: originalBasePath_,
|
|
138
|
+
worktreeCwd,
|
|
139
|
+
milestoneBranch,
|
|
140
|
+
previousCwd,
|
|
141
|
+
});
|
|
142
|
+
// Already regular-merged milestones can skip the squash path and proceed to cleanup (#5831).
|
|
143
|
+
const alreadyMerged = finalizeAlreadyMergedMilestoneIfReachable({
|
|
144
|
+
projectRoot: originalBasePath_,
|
|
145
|
+
milestoneId,
|
|
146
|
+
milestoneBranch,
|
|
147
|
+
mainBranch,
|
|
148
|
+
previousCwd,
|
|
149
|
+
commitMessage,
|
|
150
|
+
});
|
|
151
|
+
if (alreadyMerged)
|
|
152
|
+
return alreadyMerged;
|
|
153
|
+
// 7. Shelter queued milestone directories before the squash merge (#2505).
|
|
154
|
+
// MUST run before the pre-merge stash so queued CONTEXT files are not swept
|
|
155
|
+
// into a stash that may later fail to pop and strand user work.
|
|
156
|
+
const milestoneDirectoryShelter = createMilestoneDirectoryShelter(originalBasePath_, milestoneId, getMilestone(milestoneId)?.title);
|
|
157
|
+
// 7a. Stash pre-existing dirty files so the squash merge is not blocked by
|
|
158
|
+
// unrelated local changes (#2151). The stash module also owns Windows DB
|
|
159
|
+
// handle cycling and later stash-pop conflict recovery.
|
|
160
|
+
const preMergeStash = createPreMergeStash(originalBasePath_, milestoneId, process.platform === "win32" && isDbAvailable());
|
|
161
|
+
preMergeStash.stash();
|
|
162
|
+
// 7b. Clean up stale merge state before attempting the merge (#2912).
|
|
163
|
+
// A leftover MERGE_HEAD (from a previous failed merge, libgit2 native path,
|
|
164
|
+
// or interrupted operation) causes git merge to refuse with
|
|
165
|
+
// "fatal: You have not concluded your merge (MERGE_HEAD exists)".
|
|
166
|
+
// Defensively remove merge artifacts before starting.
|
|
167
|
+
removeMergeStateFiles(originalBasePath_, "pre-merge");
|
|
168
|
+
// 8. Merge — respect merge_strategy preference (#549).
|
|
169
|
+
// "squash" (default): stages changes without a merge commit; caller commits.
|
|
170
|
+
// "merge": --no-ff --no-commit so caller can supply the commit message while
|
|
171
|
+
// git records a real merge commit (MERGE_HEAD present when nativeCommit runs).
|
|
172
|
+
const effectiveStrategy = prefs.merge_strategy === "merge" ? "merge" : "squash";
|
|
173
|
+
const mergeResult = effectiveStrategy === "merge"
|
|
174
|
+
? nativeMergeRegular(originalBasePath_, milestoneBranch)
|
|
175
|
+
: nativeMergeSquash(originalBasePath_, milestoneBranch);
|
|
176
|
+
preMergeStash.reopenDbAfterMerge();
|
|
177
|
+
if (!mergeResult.success) {
|
|
178
|
+
// Dirty working tree — the merge was rejected before it started (e.g.
|
|
179
|
+
// untracked .gsd/ files left by syncStateToProjectRoot). Preserve the
|
|
180
|
+
// milestone branch so commits are not lost.
|
|
181
|
+
if (mergeResult.conflicts.includes("__dirty_working_tree__")) {
|
|
182
|
+
// Defensively clean merge state — the native path may leave MERGE_HEAD
|
|
183
|
+
// even when the merge is rejected (#2912).
|
|
184
|
+
removeMergeStateFiles(originalBasePath_, "dirty-tree rejection");
|
|
185
|
+
// Pop stash before throwing so local work is not lost.
|
|
186
|
+
preMergeStash.restoreForMergeFailure();
|
|
187
|
+
milestoneDirectoryShelter.restore();
|
|
188
|
+
// Restore cwd so the caller is not stranded on the integration branch
|
|
189
|
+
process.chdir(previousCwd);
|
|
190
|
+
// Surface the actual dirty filenames from git stderr instead of
|
|
191
|
+
// generically blaming .gsd/ (#2151).
|
|
192
|
+
const fileList = mergeResult.dirtyFiles?.length
|
|
193
|
+
? `Dirty files:\n${mergeResult.dirtyFiles.map((f) => ` ${f}`).join("\n")}`
|
|
194
|
+
: `Check \`git status\` in the project root for details.`;
|
|
195
|
+
throw new GSDError(GSD_GIT_ERROR, `${effectiveStrategy === "merge" ? "Merge" : "Squash merge"} of ${milestoneBranch} rejected: working tree has dirty or untracked files ` +
|
|
196
|
+
`that conflict with the merge. ${fileList}`);
|
|
197
|
+
}
|
|
198
|
+
// Check for conflicts — use merge result first, fall back to nativeConflictFiles
|
|
199
|
+
const conflictedFiles = mergeResult.conflicts.length > 0
|
|
200
|
+
? mergeResult.conflicts
|
|
201
|
+
: nativeConflictFiles(originalBasePath_);
|
|
202
|
+
if (conflictedFiles.length > 0) {
|
|
203
|
+
// Separate auto-resolvable conflicts (GSD state files + build artifacts)
|
|
204
|
+
// from real code conflicts. GSD state files diverge between branches
|
|
205
|
+
// during normal operation. Build artifacts are machine-generated and
|
|
206
|
+
// regenerable. Both are safe to accept from the milestone branch.
|
|
207
|
+
const { resolved: autoResolved, remaining: codeConflicts } = autoResolveSafeConflictPaths(originalBasePath_, conflictedFiles);
|
|
208
|
+
if (autoResolved.length > 0) {
|
|
209
|
+
logWarning("worktree", `auto-resolved safe merge conflicts: ${autoResolved.join(", ")}`);
|
|
210
|
+
}
|
|
211
|
+
// If there are still real code conflicts, escalate
|
|
212
|
+
if (codeConflicts.length > 0) {
|
|
213
|
+
cleanupConflictState(originalBasePath_);
|
|
214
|
+
// Pop stash before throwing so local work is not lost (#2151).
|
|
215
|
+
preMergeStash.restoreForMergeFailure();
|
|
216
|
+
milestoneDirectoryShelter.restore();
|
|
217
|
+
// Restore cwd so the caller is not stranded on the integration branch.
|
|
218
|
+
// Without this, the next mergeMilestoneToMain call in a parallel merge
|
|
219
|
+
// sequence uses process.cwd() (now the project root) as worktreeCwd,
|
|
220
|
+
// causing autoCommitDirtyState to commit unrelated milestone files to
|
|
221
|
+
// the integration branch (#2929).
|
|
222
|
+
process.chdir(previousCwd);
|
|
223
|
+
throw new MergeConflictError(codeConflicts, effectiveStrategy, milestoneBranch, mainBranch);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// No conflicts detected — possibly "already up to date", fall through to commit
|
|
227
|
+
}
|
|
228
|
+
// 9. Commit (handle nothing-to-commit gracefully)
|
|
229
|
+
const commitResult = nativeCommit(originalBasePath_, commitMessage);
|
|
230
|
+
const nothingToCommit = commitResult === null;
|
|
231
|
+
// 9a. Clean up merge state files left by git merge --squash (#1853, #2912).
|
|
232
|
+
// git only removes SQUASH_MSG when the commit reads it directly (plain
|
|
233
|
+
// `git commit`). nativeCommit uses `-F -` (stdin) or libgit2, neither
|
|
234
|
+
// of which trigger git's SQUASH_MSG cleanup. MERGE_HEAD is created by
|
|
235
|
+
// libgit2's merge even in squash mode and is not removed by nativeCommit.
|
|
236
|
+
// If left on disk, doctor reports `corrupt_merge_state` on every subsequent run.
|
|
237
|
+
removeMergeStateFiles(originalBasePath_, "post-commit");
|
|
238
|
+
// 9a-ii. Restore stashed files now that the merge+commit is complete (#2151).
|
|
239
|
+
preMergeStash.restoreAfterCommit();
|
|
240
|
+
// 9a-iii. Restore sheltered queued milestone directories (#2505).
|
|
241
|
+
milestoneDirectoryShelter.restore();
|
|
242
|
+
// 9b. Safety check (#1792): if nothing was committed, verify the milestone
|
|
243
|
+
// work is already on the integration branch before allowing teardown.
|
|
244
|
+
try {
|
|
245
|
+
assertNoUnanchoredCodeChangesAfterEmptyMerge(originalBasePath_, mainBranch, milestoneBranch, effectiveStrategy, nothingToCommit);
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
process.chdir(previousCwd);
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
// 9c. Detect whether any non-.gsd/ code files were actually merged (#1906).
|
|
252
|
+
const codeFilesChanged = detectMergedCodeFilesChanged(originalBasePath_, nothingToCommit);
|
|
253
|
+
const finalizeMilestoneCleanup = () => {
|
|
254
|
+
cleanupMergedMilestoneWorktree({
|
|
255
|
+
projectRoot: originalBasePath_,
|
|
256
|
+
milestoneId,
|
|
257
|
+
milestoneBranch,
|
|
258
|
+
previousCwd,
|
|
259
|
+
chdirWarningContext: "after merge",
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
let shouldCleanup = false;
|
|
263
|
+
try {
|
|
264
|
+
// 10/9b. Publication (auto-push / draft PR) — Publication module seam (ADR-034).
|
|
265
|
+
const publication = publishMilestone({
|
|
266
|
+
basePath: originalBasePath_,
|
|
267
|
+
milestoneId,
|
|
268
|
+
milestoneTitle,
|
|
269
|
+
integrationBranch: mainBranch,
|
|
270
|
+
milestoneBranch,
|
|
271
|
+
sliceSummaries,
|
|
272
|
+
nothingToCommit,
|
|
273
|
+
prefs: {
|
|
274
|
+
autoPush: prefs.auto_push === true,
|
|
275
|
+
autoPr: prefs.auto_pr === true,
|
|
276
|
+
remote: prefs.remote,
|
|
277
|
+
prTargetBranch: prefs.pr_target_branch,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
const { pushed, prCreated } = publication;
|
|
281
|
+
// 11. Guard removed — step 9b (#1792) now handles this with a smarter check:
|
|
282
|
+
// throws only when the milestone has unanchored code changes, passes
|
|
283
|
+
// through when the code is genuinely already on the integration branch.
|
|
284
|
+
// 11a. Pre-teardown safety net (#1853): preserve the milestone branch if
|
|
285
|
+
// the source worktree still has uncommitted changes after the merge commit.
|
|
286
|
+
assertMilestoneWorktreeCleanBeforeTeardown({
|
|
287
|
+
milestoneBranch,
|
|
288
|
+
previousCwd,
|
|
289
|
+
worktreeCwd,
|
|
290
|
+
});
|
|
291
|
+
shouldCleanup = true;
|
|
292
|
+
return { commitMessage, pushed, prCreated, codeFilesChanged };
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
if (shouldCleanup) {
|
|
296
|
+
finalizeMilestoneCleanup();
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
logWarning("worktree", `Skipping worktree cleanup for ${milestoneBranch}; merge did not reach safe-cleanup point and milestone work is preserved for manual recovery.`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// gsd-pi — Milestone directory shelter for auto-worktree merge closeout.
|
|
2
|
+
//
|
|
3
|
+
// Owns the #2505 queued-milestone directory protection step that runs before
|
|
4
|
+
// merge stash handling. This keeps queued CONTEXT files out of the stash and
|
|
5
|
+
// restores them after merge closeout without hiding restore failures.
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, } from "node:fs";
|
|
7
|
+
import { basename, join } from "node:path";
|
|
8
|
+
import { canonicalPhaseDirName, gsdRoot, milestonesDir, resolveMilestonePath, } from "./paths.js";
|
|
9
|
+
import { logError, logWarning } from "./workflow-logger.js";
|
|
10
|
+
/**
|
|
11
|
+
* Optional override for the shelter restore copy step (milestone merge #2505).
|
|
12
|
+
* Production leaves this null so restore uses the real cpSync; tests inject a
|
|
13
|
+
* throwing function to deterministically exercise the best-effort failure path
|
|
14
|
+
* and the shelter-retention guarantee.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
let restoreEntryFn = null;
|
|
18
|
+
/** @internal */
|
|
19
|
+
export function _setRestoreEntryFnForTests(fn) {
|
|
20
|
+
restoreEntryFn = fn;
|
|
21
|
+
return () => { restoreEntryFn = null; };
|
|
22
|
+
}
|
|
23
|
+
export function createMilestoneDirectoryShelter(originalBasePath, milestoneId, milestoneTitle) {
|
|
24
|
+
const planningDir = milestonesDir(originalBasePath);
|
|
25
|
+
const shelterDir = join(gsdRoot(originalBasePath), ".milestone-shelter");
|
|
26
|
+
const shelteredDirs = [];
|
|
27
|
+
const mergeDirNames = milestoneDirectoryNames(originalBasePath, milestoneId, milestoneTitle);
|
|
28
|
+
let restored = false;
|
|
29
|
+
try {
|
|
30
|
+
if (existsSync(planningDir)) {
|
|
31
|
+
const entries = readdirSync(planningDir, { withFileTypes: true });
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (!entry.isDirectory())
|
|
34
|
+
continue;
|
|
35
|
+
if (mergeDirNames.has(entry.name))
|
|
36
|
+
continue;
|
|
37
|
+
shelterMilestoneDirectory(planningDir, shelterDir, entry.name, shelteredDirs);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// Non-fatal — proceed with merge; untracked files may block it.
|
|
43
|
+
logWarning("worktree", `milestone shelter operation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
restore() {
|
|
47
|
+
if (restored)
|
|
48
|
+
return;
|
|
49
|
+
restored = true;
|
|
50
|
+
restoreShelteredDirectories(planningDir, shelterDir, shelteredDirs);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function milestoneDirectoryNames(originalBasePath, milestoneId, milestoneTitle) {
|
|
55
|
+
const mergeDirNames = new Set([milestoneId]);
|
|
56
|
+
const resolvedMergeDir = resolveMilestonePath(originalBasePath, milestoneId);
|
|
57
|
+
if (resolvedMergeDir) {
|
|
58
|
+
mergeDirNames.add(basename(resolvedMergeDir));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
mergeDirNames.add(canonicalPhaseDirName(milestoneId, milestoneTitle));
|
|
62
|
+
}
|
|
63
|
+
return mergeDirNames;
|
|
64
|
+
}
|
|
65
|
+
function shelterMilestoneDirectory(planningDir, shelterDir, dirName, shelteredDirs) {
|
|
66
|
+
const srcDir = join(planningDir, dirName);
|
|
67
|
+
const dstDir = join(shelterDir, dirName);
|
|
68
|
+
try {
|
|
69
|
+
mkdirSync(shelterDir, { recursive: true });
|
|
70
|
+
cpSync(srcDir, dstDir, { recursive: true, force: true });
|
|
71
|
+
rmSync(srcDir, { recursive: true, force: true });
|
|
72
|
+
shelteredDirs.push(dirName);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
// Non-fatal — if shelter fails, the merge may still succeed.
|
|
76
|
+
logWarning("worktree", `milestone shelter failed (${dirName}): ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function restoreShelteredDirectories(planningDir, shelterDir, shelteredDirs) {
|
|
80
|
+
if (shelteredDirs.length === 0)
|
|
81
|
+
return;
|
|
82
|
+
let restoreFailed = false;
|
|
83
|
+
for (const dirName of shelteredDirs) {
|
|
84
|
+
const src = join(shelterDir, dirName);
|
|
85
|
+
if (!existsSync(src)) {
|
|
86
|
+
logWarning("worktree", `shelter source missing for ${dirName}; skipping restore (shelter already cleaned or entry never staged)`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
mkdirSync(planningDir, { recursive: true });
|
|
91
|
+
const dest = join(planningDir, dirName);
|
|
92
|
+
if (restoreEntryFn) {
|
|
93
|
+
restoreEntryFn(src, dest);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
restoreFailed = true;
|
|
101
|
+
logError("worktree", `shelter restore failed (${dirName}): ${err instanceof Error ? err.message : String(err)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Preserve the shelter if any per-entry restore failed — it is the only
|
|
105
|
+
// surviving copy of the queued milestone dirs (sources were deleted during
|
|
106
|
+
// shelter). Deleting it here would permanently lose those files (#2505).
|
|
107
|
+
if (restoreFailed) {
|
|
108
|
+
logWarning("worktree", `shelter retained at ${shelterDir} — manual recovery required for unrestored entries`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (existsSync(shelterDir)) {
|
|
112
|
+
try {
|
|
113
|
+
rmSync(shelterDir, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
logWarning("worktree", `shelter cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// gsd-pi — Auto-worktree path resolution seam.
|
|
2
|
+
//
|
|
3
|
+
// Keeps callers that only resolve existing auto-worktree paths on the focused
|
|
4
|
+
// entry/path module instead of the legacy auto-worktree compatibility barrel.
|
|
5
|
+
export { getAutoWorktreePath } from "./auto-worktree-entry.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// gsd-pi — Narrow auto-worktree path utility seam.
|
|
2
|
+
//
|
|
3
|
+
// Keeps path comparison, pathspec, and expected-warning callers off the legacy
|
|
4
|
+
// auto-worktree compatibility barrel while extraction continues.
|
|
5
|
+
export { _gitPathspecForWorktreePath, _isExpectedWorktreeUnlinkError, _isSamePath, } from "./auto-worktree-cleanup.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// gsd-pi — Auto-worktree resource version module.
|
|
2
|
+
//
|
|
3
|
+
// Owns managed-resource freshness checks for long-running auto sessions.
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { gsdHome } from "./gsd-home.js";
|
|
7
|
+
import { logWarning } from "./workflow-logger.js";
|
|
8
|
+
/**
|
|
9
|
+
* Read the resource version (semver) from the managed-resources manifest.
|
|
10
|
+
* Uses gsdVersion instead of syncedAt so that launching a second session
|
|
11
|
+
* doesn't falsely trigger staleness (#804).
|
|
12
|
+
*/
|
|
13
|
+
export function readResourceVersion() {
|
|
14
|
+
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome(), "agent");
|
|
15
|
+
const manifestPath = join(agentDir, "managed-resources.json");
|
|
16
|
+
try {
|
|
17
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
18
|
+
return typeof manifest?.gsdVersion === "string"
|
|
19
|
+
? manifest.gsdVersion
|
|
20
|
+
: null;
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
logWarning("worktree", `readResourceVersion failed: ${e.message}`);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if managed resources have been updated since session start.
|
|
29
|
+
* Returns a warning message if stale, null otherwise.
|
|
30
|
+
*/
|
|
31
|
+
export function checkResourcesStale(versionOnStart) {
|
|
32
|
+
if (versionOnStart === null)
|
|
33
|
+
return null;
|
|
34
|
+
const current = readResourceVersion();
|
|
35
|
+
if (current === null)
|
|
36
|
+
return null;
|
|
37
|
+
if (current !== versionOnStart) {
|
|
38
|
+
return "GSD resources were updated since this session started. Restart gsd to load the new code.";
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|