@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.553dc518
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/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-direct-dispatch.js +4 -5
- package/dist/resources/extensions/gsd/auto-start.js +3 -2
- 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 +19 -1
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +118 -21
- 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/doctor-engine-checks.js +93 -11
- package/dist/resources/extensions/gsd/flat-phase-migration.js +14 -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 +11 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -10
- package/dist/resources/extensions/gsd/markdown-renderer.js +15 -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/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +82 -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/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/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/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/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/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-direct-dispatch.ts +4 -5
- package/src/resources/extensions/gsd/auto-start.ts +4 -2
- 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 +19 -1
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +152 -21
- 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/doctor-engine-checks.ts +109 -10
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -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 +13 -0
- package/src/resources/extensions/gsd/guided-flow.ts +7 -11
- package/src/resources/extensions/gsd/markdown-renderer.ts +15 -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/preferences-types.ts +30 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +69 -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/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-loop-no-copy-artifacts.test.ts +2 -1
- 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/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 +44 -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/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/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/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/prefs-wizard-coverage.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -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 +103 -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/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/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → ZpFDazP-38PXBPiI_LdZf}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → ZpFDazP-38PXBPiI_LdZf}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -28,16 +28,16 @@ See [CHANGELOG.md](./CHANGELOG.md) for release-by-release fixes and [Legacy Rele
|
|
|
28
28
|
## Latest Release Highlights
|
|
29
29
|
|
|
30
30
|
<!-- release-highlights:start -->
|
|
31
|
-
Latest release: **v1.
|
|
32
|
-
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
31
|
+
Latest release: **v1.5.0**
|
|
32
|
+
|
|
33
|
+
- **hermes:** Add /gsd new-milestone command for chat-driven milestone creation (#1162).
|
|
34
|
+
- **gsd:** Make CODEBASE.md workspace-aware in parent mode.
|
|
35
|
+
- **gsd:** Doctor probe + validation for parent-workspace repos.
|
|
36
|
+
- **gsd:** Surface per-repo git health in /gsd status + document limits.
|
|
37
|
+
- **gsd:** Wire repository assignment into slice/task planning.
|
|
38
|
+
- **gsd:** Add parent-workspace discovery UX + document nested-only layout.
|
|
39
|
+
- **provider:** Add Cursor Agent provider.
|
|
40
|
+
- **gsd:** Make workspace.mode parent behavioral (mode contract).
|
|
41
41
|
|
|
42
42
|
<!-- release-highlights:end -->
|
|
43
43
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
be3c5dc39486dc24
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Auto-loop closeout, milestone report, and merge helpers.
|
|
3
3
|
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { join, basename } from "node:path";
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
4
8
|
import { MergeConflictError } from "../git-service.js";
|
|
5
9
|
import { findUnmergedCompletedMilestones } from "../unmerged-milestone-guard.js";
|
|
6
10
|
import { getIsolationMode } from "../preferences.js";
|
|
7
11
|
import { isDbAvailable, getMilestone } from "../gsd-db.js";
|
|
8
12
|
import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
|
|
9
13
|
import { isClosedStatus } from "../status-guards.js";
|
|
14
|
+
import { PLANNING_ARTIFACT_SUFFIXES } from "../paths.js";
|
|
10
15
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
11
16
|
import { debugLog } from "../debug-logger.js";
|
|
12
17
|
import { _resolveReportBasePath } from "./phase-helpers.js";
|
|
@@ -51,7 +56,15 @@ export async function restorePreflightStashOrStop(ic, preflight, milestoneId) {
|
|
|
51
56
|
*/
|
|
52
57
|
export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, options = {}) {
|
|
53
58
|
const { ctx, pi, s, deps } = ic;
|
|
54
|
-
|
|
59
|
+
// Resolve the project root once, before the merge can flip session path
|
|
60
|
+
// pointers (worktree teardown mutates s.basePath / s.scope). The guarded
|
|
61
|
+
// merge's preflight/postflight stash restore, the pre-merge ignored-markdown
|
|
62
|
+
// snapshot, the dirty-projection check, and the rebuild must all target this
|
|
63
|
+
// same tree; otherwise `.gsd` markdown restored on one tree can be silently
|
|
64
|
+
// overwritten by a rebuild on another. canonicalProjectRoot is the
|
|
65
|
+
// authoritative `.gsd`/DB writer path (see AutoSession.canonicalProjectRoot).
|
|
66
|
+
const projectRoot = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
67
|
+
const ignoredProjectionSnapshot = snapshotIgnoredGsdMarkdownProjections(projectRoot);
|
|
55
68
|
const mergeResult = deps.lifecycle.exitMilestone(milestoneId, {
|
|
56
69
|
merge: true,
|
|
57
70
|
guardedMerge: {
|
|
@@ -61,11 +74,11 @@ export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, option
|
|
|
61
74
|
},
|
|
62
75
|
}, ctx.ui);
|
|
63
76
|
if (mergeResult.ok) {
|
|
64
|
-
await markMilestoneMergedAndRebuild(s);
|
|
77
|
+
await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
|
|
65
78
|
return null;
|
|
66
79
|
}
|
|
67
80
|
if (mergeResult.reason === "postflight-stash-restore-failed") {
|
|
68
|
-
await markMilestoneMergedAndRebuild(s);
|
|
81
|
+
await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
|
|
69
82
|
}
|
|
70
83
|
if (mergeResult.reason === "preflight-dirty-overlap" || mergeResult.reason === "preflight-unmerged-conflicts") {
|
|
71
84
|
const reason = mergeResult.reason === "preflight-unmerged-conflicts"
|
|
@@ -122,10 +135,13 @@ export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, option
|
|
|
122
135
|
}
|
|
123
136
|
return null;
|
|
124
137
|
}
|
|
125
|
-
async function markMilestoneMergedAndRebuild(s) {
|
|
138
|
+
async function markMilestoneMergedAndRebuild(s, rebuildBasePath, ignoredProjectionSnapshot) {
|
|
126
139
|
s.milestoneMergedInPhases = true;
|
|
140
|
+
if (hasDirtyGsdMarkdownProjections(rebuildBasePath, ignoredProjectionSnapshot)) {
|
|
141
|
+
logWarning("engine", "skipping markdown projection rebuild after milestone merge because restored .gsd edits are dirty");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
127
144
|
try {
|
|
128
|
-
const rebuildBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
129
145
|
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
130
146
|
await rebuildMarkdownProjectionsFromDb(rebuildBasePath);
|
|
131
147
|
}
|
|
@@ -133,6 +149,82 @@ async function markMilestoneMergedAndRebuild(s) {
|
|
|
133
149
|
logWarning("engine", `markdown projection rebuild after milestone merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
134
150
|
}
|
|
135
151
|
}
|
|
152
|
+
function hasDirtyGsdMarkdownProjections(basePath, ignoredProjectionSnapshot) {
|
|
153
|
+
try {
|
|
154
|
+
const output = execFileSync("git", ["status", "--porcelain", "-z", "--untracked-files=all", "--", ".gsd"], { cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
155
|
+
const entries = output.split("\0").filter(Boolean);
|
|
156
|
+
for (let i = 0; i < entries.length; i++) {
|
|
157
|
+
const entry = entries[i];
|
|
158
|
+
if (entry.length <= 3)
|
|
159
|
+
continue;
|
|
160
|
+
const status = entry.slice(0, 2);
|
|
161
|
+
const path = entry.slice(3);
|
|
162
|
+
if (isGeneratedGsdMarkdownProjection(path))
|
|
163
|
+
return true;
|
|
164
|
+
if ((status === "R " || status === "C ") && i + 1 < entries.length) {
|
|
165
|
+
const dest = entries[++i];
|
|
166
|
+
if (isGeneratedGsdMarkdownProjection(dest))
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
void err;
|
|
173
|
+
// Fall through to ignored-file snapshot comparison below.
|
|
174
|
+
}
|
|
175
|
+
return ignoredGsdMarkdownProjectionSnapshotChanged(basePath, ignoredProjectionSnapshot);
|
|
176
|
+
}
|
|
177
|
+
function isGeneratedGsdMarkdownProjection(path) {
|
|
178
|
+
const normalized = path.replace(/\\/g, "/");
|
|
179
|
+
if (normalized === ".gsd/DECISIONS.md")
|
|
180
|
+
return true;
|
|
181
|
+
if (!normalized.startsWith(".gsd/milestones/") && !normalized.startsWith(".gsd/phases/")) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return isGeneratedPlanningArtifactFileName(basename(normalized));
|
|
185
|
+
}
|
|
186
|
+
function isGeneratedPlanningArtifactFileName(fileName) {
|
|
187
|
+
if (!fileName.endsWith(".md"))
|
|
188
|
+
return false;
|
|
189
|
+
const stem = fileName.slice(0, -".md".length).toUpperCase();
|
|
190
|
+
for (const suffix of PLANNING_ARTIFACT_SUFFIXES) {
|
|
191
|
+
if (!stem.endsWith(`-${suffix}`))
|
|
192
|
+
continue;
|
|
193
|
+
const idPrefix = stem.slice(0, -suffix.length - 1);
|
|
194
|
+
return /^(?:[MST]\d+|\d{2,})(?:-[A-Z0-9]+)*$/.test(idPrefix);
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
function snapshotIgnoredGsdMarkdownProjections(basePath) {
|
|
199
|
+
try {
|
|
200
|
+
const output = execFileSync("git", ["ls-files", "--others", "--ignored", "--exclude-standard", "-z", "--", ".gsd"], { cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
201
|
+
const snapshot = new Map();
|
|
202
|
+
for (const path of output.split("\0")) {
|
|
203
|
+
if (!isGeneratedGsdMarkdownProjection(path))
|
|
204
|
+
continue;
|
|
205
|
+
snapshot.set(path, hashFile(join(basePath, path)));
|
|
206
|
+
}
|
|
207
|
+
return snapshot;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function hashFile(path) {
|
|
214
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
215
|
+
}
|
|
216
|
+
function ignoredGsdMarkdownProjectionSnapshotChanged(basePath, before) {
|
|
217
|
+
if (!before)
|
|
218
|
+
return true;
|
|
219
|
+
const after = snapshotIgnoredGsdMarkdownProjections(basePath);
|
|
220
|
+
if (!after || after.size !== before.size)
|
|
221
|
+
return true;
|
|
222
|
+
for (const [path, hash] of before) {
|
|
223
|
+
if (after.get(path) !== hash)
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
136
228
|
export async function _runMilestoneMergeOnceWithStashRestore(ic, milestoneId, options = {}) {
|
|
137
229
|
if (ic.s.milestoneMergedInPhases) {
|
|
138
230
|
debugLog("autoLoop", {
|
|
@@ -21,7 +21,8 @@ import { createWorktreeSafetyModule } from "../worktree-safety.js";
|
|
|
21
21
|
import { repairAutoWorktreeSafetyFailure } from "../auto-worktree-repair.js";
|
|
22
22
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
23
23
|
import { preDispatchHealthGate, recordHealthSnapshot, } from "../doctor-proactive.js";
|
|
24
|
-
import {
|
|
24
|
+
import { autoWorktreeBranch } from "../auto-worktree-branch-lifecycle.js";
|
|
25
|
+
import { checkResourcesStale } from "../auto-worktree-resource-version.js";
|
|
25
26
|
import { getSessionLockStatus } from "../session-lock.js";
|
|
26
27
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
27
28
|
import { emitJournalEvent as _emitJournalEvent } from "../journal.js";
|
|
@@ -31,7 +32,7 @@ import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
|
|
|
31
32
|
import { GitServiceImpl } from "../git-service.js";
|
|
32
33
|
import { WorktreeStateProjection } from "../worktree-state-projection.js";
|
|
33
34
|
import { WorktreeLifecycle } from "../worktree-lifecycle.js";
|
|
34
|
-
import {
|
|
35
|
+
import { createDefaultMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
|
|
35
36
|
import { createWorkspace, scopeMilestone } from "../workspace.js";
|
|
36
37
|
import { supportsStructuredQuestions } from "../workflow-mcp.js";
|
|
37
38
|
import { getRegisteredToolSnapshot, getToolBaselineSnapshot } from "../auto-model-selection.js";
|
|
@@ -560,7 +561,7 @@ export class AutoOrchestrator {
|
|
|
560
561
|
return new GitServiceImpl(basePath, gitConfig);
|
|
561
562
|
},
|
|
562
563
|
worktreeProjection: new WorktreeStateProjection(),
|
|
563
|
-
mergeMilestone:
|
|
564
|
+
mergeMilestone: createDefaultMilestoneMergeTransaction(),
|
|
564
565
|
});
|
|
565
566
|
}
|
|
566
567
|
rebuildScope(rawPath, milestoneId) {
|
|
@@ -345,7 +345,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
345
345
|
if (stop)
|
|
346
346
|
return stop;
|
|
347
347
|
}
|
|
348
|
-
// PR creation (auto_pr) is handled
|
|
348
|
+
// PR creation (auto_pr) is handled by the publication handoff in
|
|
349
|
+
// the milestone merge transaction (#2302).
|
|
349
350
|
deps.invalidateAllCaches();
|
|
350
351
|
state = await deps.deriveState(s.canonicalProjectRoot);
|
|
351
352
|
mid = state.activeMilestone?.id;
|
|
@@ -430,7 +431,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
430
431
|
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
|
|
431
432
|
if (stop)
|
|
432
433
|
return stop;
|
|
433
|
-
// PR creation (auto_pr) is handled
|
|
434
|
+
// PR creation (auto_pr) is handled by the publication handoff in
|
|
435
|
+
// the milestone merge transaction (#2302).
|
|
434
436
|
}
|
|
435
437
|
const unmappedActive = countUnmappedActiveRequirements();
|
|
436
438
|
const completionStopReason = formatCompletePhaseNextAction(unmappedActive);
|
|
@@ -506,7 +508,8 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
506
508
|
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
|
|
507
509
|
if (stop)
|
|
508
510
|
return stop;
|
|
509
|
-
// PR creation (auto_pr) is handled
|
|
511
|
+
// PR creation (auto_pr) is handled by the publication handoff in
|
|
512
|
+
// the milestone merge transaction (#2302).
|
|
510
513
|
}
|
|
511
514
|
deps.sendDesktopNotification("GSD", `Milestone ${mid} complete!`, "success", "milestone", basename(s.originalBasePath || s.basePath));
|
|
512
515
|
deps.logCmuxEvent(prefs, `Milestone ${mid} complete.`, "success");
|
|
@@ -17,7 +17,7 @@ import { isDbAvailable, getTask } from "../gsd-db.js";
|
|
|
17
17
|
import { parseUnitId } from "../unit-id.js";
|
|
18
18
|
import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
|
|
19
19
|
import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
|
20
|
-
import {
|
|
20
|
+
import { getUnitWorkflowDispatchReadinessErrorForModel } from "../tool-contract.js";
|
|
21
21
|
import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
|
|
22
22
|
import { applyThinkingLevelForModel, floorThinkingLevelForUnit, } from "../auto-model-selection.js";
|
|
23
23
|
import { isSuspiciousGhostCompletion } from "../auto-unit-closeout.js";
|
|
@@ -246,17 +246,13 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
246
246
|
s.currentDispatchedModelId = s.currentUnitModel
|
|
247
247
|
? `${s.currentUnitModel.provider ?? ""}/${s.currentUnitModel.id ?? ""}`
|
|
248
248
|
: null;
|
|
249
|
-
const compatibilityError =
|
|
250
|
-
|
|
249
|
+
const compatibilityError = getUnitWorkflowDispatchReadinessErrorForModel({
|
|
250
|
+
model: s.currentUnitModel,
|
|
251
|
+
fallbackModel: ctx.model,
|
|
252
|
+
getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
|
|
251
253
|
projectRoot: s.basePath,
|
|
252
254
|
surface: "auto-mode",
|
|
253
255
|
unitType,
|
|
254
|
-
authMode: s.currentUnitModel?.provider
|
|
255
|
-
? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
|
|
256
|
-
: ctx.model?.provider
|
|
257
|
-
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
258
|
-
: undefined,
|
|
259
|
-
baseUrl: s.currentUnitModel?.baseUrl ?? ctx.model?.baseUrl,
|
|
260
256
|
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
261
257
|
});
|
|
262
258
|
const workflowMcpPrepModel = s.currentUnitModel;
|
|
@@ -10,7 +10,7 @@ import { buildResearchSlicePrompt, buildResearchMilestonePrompt, buildPlanSliceP
|
|
|
10
10
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
11
11
|
import { pauseAuto } from "./auto.js";
|
|
12
12
|
import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
|
|
13
|
-
import {
|
|
13
|
+
import { getUnitWorkflowDispatchReadinessErrorForModel } from "./tool-contract.js";
|
|
14
14
|
export function parseDirectDispatchPhase(raw) {
|
|
15
15
|
const tokens = raw.trim().split(/\s+/).filter(Boolean);
|
|
16
16
|
if (tokens.length === 0)
|
|
@@ -213,13 +213,12 @@ export async function dispatchDirectPhase(ctx, pi, phase, base, opts = {}) {
|
|
|
213
213
|
ctx.ui.notify(`Unknown phase "${phase}". Valid phases: research, plan, execute, complete, validate, reassess, uat, replan.`, "warning");
|
|
214
214
|
return;
|
|
215
215
|
}
|
|
216
|
-
const compatibilityError =
|
|
217
|
-
|
|
216
|
+
const compatibilityError = getUnitWorkflowDispatchReadinessErrorForModel({
|
|
217
|
+
model: ctx.model,
|
|
218
|
+
getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
|
|
218
219
|
projectRoot,
|
|
219
220
|
surface: "direct phase dispatch",
|
|
220
221
|
unitType,
|
|
221
|
-
authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
|
|
222
|
-
baseUrl: ctx.model?.baseUrl,
|
|
223
222
|
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
224
223
|
});
|
|
225
224
|
if (compatibilityError) {
|
|
@@ -27,9 +27,10 @@ import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
|
27
27
|
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchList, nativeBranchExists, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, nativeCommitCountBetween, nativeHasChanges, } from "./native-git-bridge.js";
|
|
28
28
|
import { GitServiceImpl } from "./git-service.js";
|
|
29
29
|
import { captureIntegrationBranch, detectWorktreeName, setActiveMilestoneId, } from "./worktree.js";
|
|
30
|
-
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
30
|
+
import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
|
|
31
31
|
import { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
|
|
32
|
-
import {
|
|
32
|
+
import { cleanStaleRuntimeUnits } from "./auto-worktree-runtime-cleanup.js";
|
|
33
|
+
import { readResourceVersion } from "./auto-worktree-resource-version.js";
|
|
33
34
|
import { worktreePath as getWorktreeDir, isInsideWorktreesDir } from "./worktree-manager.js";
|
|
34
35
|
import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
|
|
35
36
|
import { queryJournal } from "./journal.js";
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// gsd-pi — Auto-worktree branch lifecycle module.
|
|
2
|
+
//
|
|
3
|
+
// Owns milestone branch naming, branch-mode entry, and safe fast-forward of
|
|
4
|
+
// reused milestone branches. Worktree creation/merge code imports this module
|
|
5
|
+
// instead of keeping branch policy inside the legacy auto-worktree barrel.
|
|
6
|
+
import { GSDError, GSD_GIT_ERROR } from "./errors.js";
|
|
7
|
+
import { readIntegrationBranch } from "./git-service.js";
|
|
8
|
+
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
9
|
+
import { debugLog } from "./debug-logger.js";
|
|
10
|
+
import { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
|
|
11
|
+
import { nativeBranchExists, nativeBranchForceReset, nativeDetectMainBranch, nativeIsAncestor, nativeUpdateRef, nativeWorktreeList, } from "./native-git-bridge.js";
|
|
12
|
+
/** Returns the git branch name for a milestone worktree (`milestone/<MID>`). */
|
|
13
|
+
export function autoWorktreeBranch(milestoneId) {
|
|
14
|
+
return `milestone/${milestoneId}`;
|
|
15
|
+
}
|
|
16
|
+
export function _resolveAutoWorktreeStartPoint(integrationBranch, gitMainBranch, branchExists) {
|
|
17
|
+
if (integrationBranch)
|
|
18
|
+
return integrationBranch;
|
|
19
|
+
return gitMainBranch &&
|
|
20
|
+
typeof gitMainBranch === "string" &&
|
|
21
|
+
gitMainBranch.length > 0 &&
|
|
22
|
+
branchExists(gitMainBranch)
|
|
23
|
+
? gitMainBranch
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Enter branch isolation mode for a milestone.
|
|
28
|
+
*
|
|
29
|
+
* Creates `milestone/<MID>` from the integration branch (if it doesn't
|
|
30
|
+
* exist yet) and checks out to it. No worktree directory is created — the
|
|
31
|
+
* project root is the working copy; only HEAD changes.
|
|
32
|
+
*
|
|
33
|
+
* Uses the same 3-tier integration-branch fallback as createAutoWorktree:
|
|
34
|
+
* 1. META.json recorded integration branch
|
|
35
|
+
* 2. git.main_branch preference
|
|
36
|
+
* 3. nativeDetectMainBranch (origin/HEAD auto-detection)
|
|
37
|
+
*/
|
|
38
|
+
export function enterBranchModeForMilestone(basePath, milestoneId) {
|
|
39
|
+
const branch = autoWorktreeBranch(milestoneId);
|
|
40
|
+
const branchExists = nativeBranchExists(basePath, branch);
|
|
41
|
+
if (!branchExists) {
|
|
42
|
+
// Create the milestone branch from the integration branch start-point.
|
|
43
|
+
const integrationBranch = readIntegrationBranch(basePath, milestoneId) ?? undefined;
|
|
44
|
+
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
45
|
+
const startPoint = _resolveAutoWorktreeStartPoint(integrationBranch, gitPrefs?.main_branch, (branchName) => nativeBranchExists(basePath, branchName)) ??
|
|
46
|
+
nativeDetectMainBranch(basePath);
|
|
47
|
+
// TOCTOU ancestry guard (Issue #4980 HIGH-3).
|
|
48
|
+
//
|
|
49
|
+
// The outer `branchExists` check is racy: a concurrent process
|
|
50
|
+
// (parallel-orchestrator worker, side-by-side `gsd` instance, or manual
|
|
51
|
+
// `git branch` invocation) may have created the branch with real commits
|
|
52
|
+
// between that check and this point. `nativeBranchForceReset` does
|
|
53
|
+
// `git branch -f`, which silently overwrites the branch ref — orphaning
|
|
54
|
+
// any commits not reachable from `startPoint`. Re-check immediately
|
|
55
|
+
// before the destructive call and refuse if the branch suddenly exists
|
|
56
|
+
// with non-ancestor commits.
|
|
57
|
+
const concurrentlyCreated = nativeBranchExists(basePath, branch);
|
|
58
|
+
if (concurrentlyCreated &&
|
|
59
|
+
!nativeIsAncestor(basePath, branch, startPoint)) {
|
|
60
|
+
throw new GSDError(GSD_GIT_ERROR, `Branch "${branch}" was created concurrently with commits not reachable from "${startPoint}". ` +
|
|
61
|
+
`Refusing to force-reset — would orphan prior work. ` +
|
|
62
|
+
`Resume the existing milestone or run \`git branch -D ${branch}\` to discard.`);
|
|
63
|
+
}
|
|
64
|
+
// nativeBranchForceReset creates (or resets) branch at startPoint,
|
|
65
|
+
// then checkout switches HEAD to it.
|
|
66
|
+
nativeBranchForceReset(basePath, branch, startPoint);
|
|
67
|
+
debugLog("auto-worktree", {
|
|
68
|
+
action: "enterBranchMode",
|
|
69
|
+
milestoneId,
|
|
70
|
+
branch,
|
|
71
|
+
startPoint,
|
|
72
|
+
created: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
debugLog("auto-worktree", {
|
|
77
|
+
action: "enterBranchMode",
|
|
78
|
+
milestoneId,
|
|
79
|
+
branch,
|
|
80
|
+
reused: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
checkoutBranchWithStashGuard(basePath, branch, `enter-branch-mode:${milestoneId}`);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* True when `branch` is checked out in any worktree listed by
|
|
87
|
+
* `git worktree list --porcelain`. Used to gate ref updates that would
|
|
88
|
+
* otherwise leave a concurrent worktree's HEAD inconsistent with its
|
|
89
|
+
* index/working tree (Codex peer-review of #5538-followup).
|
|
90
|
+
*
|
|
91
|
+
* Best-effort: a `nativeWorktreeList` failure returns true so we err on
|
|
92
|
+
* the side of NOT moving the ref. Better to skip a fast-forward than to
|
|
93
|
+
* silently corrupt another worktree.
|
|
94
|
+
*/
|
|
95
|
+
export function _isBranchCheckedOutElsewhere(basePath, branch) {
|
|
96
|
+
try {
|
|
97
|
+
const entries = nativeWorktreeList(basePath);
|
|
98
|
+
return entries.some((entry) => entry.branch === branch);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the integration branch using the same 3-tier fallback as the
|
|
106
|
+
* fresh-create path: META.json → git.main_branch preference → detected
|
|
107
|
+
* main branch. Returns null when no usable target exists.
|
|
108
|
+
*/
|
|
109
|
+
function resolveIntegrationBranchForReuse(basePath, milestoneId) {
|
|
110
|
+
const fromMeta = readIntegrationBranch(basePath, milestoneId);
|
|
111
|
+
if (fromMeta)
|
|
112
|
+
return fromMeta;
|
|
113
|
+
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
114
|
+
const fromPref = gitPrefs?.main_branch &&
|
|
115
|
+
typeof gitPrefs.main_branch === "string" &&
|
|
116
|
+
gitPrefs.main_branch.length > 0 &&
|
|
117
|
+
nativeBranchExists(basePath, gitPrefs.main_branch)
|
|
118
|
+
? gitPrefs.main_branch
|
|
119
|
+
: null;
|
|
120
|
+
if (fromPref)
|
|
121
|
+
return fromPref;
|
|
122
|
+
try {
|
|
123
|
+
return nativeDetectMainBranch(basePath);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* When reusing an existing milestone branch, fast-forward it onto the
|
|
131
|
+
* integration branch when that's safe (branch is a strict ancestor of
|
|
132
|
+
* integration — no commits would be lost). Skips when the branch has its
|
|
133
|
+
* own commits ahead of integration, when the integration branch can't be
|
|
134
|
+
* resolved, or when any git operation fails — the merge gate at milestone
|
|
135
|
+
* completion will surface real divergence as a conflict.
|
|
136
|
+
*
|
|
137
|
+
* The previous behavior re-attached the worktree to whatever stale tip
|
|
138
|
+
* the branch held, which caused new milestone work to fork from a base
|
|
139
|
+
* missing prior milestones' merges (#5538-followup).
|
|
140
|
+
*/
|
|
141
|
+
export function fastForwardReusedMilestoneBranchIfSafe(basePath, milestoneId, branch) {
|
|
142
|
+
try {
|
|
143
|
+
const integrationBranch = resolveIntegrationBranchForReuse(basePath, milestoneId);
|
|
144
|
+
if (!integrationBranch || integrationBranch === branch)
|
|
145
|
+
return;
|
|
146
|
+
if (!nativeBranchExists(basePath, integrationBranch))
|
|
147
|
+
return;
|
|
148
|
+
// Pure fast-forward only: branch must be a strict ancestor of integration.
|
|
149
|
+
// If the branch has its own commits ahead, leave it alone.
|
|
150
|
+
if (!nativeIsAncestor(basePath, branch, integrationBranch)) {
|
|
151
|
+
debugLog("createAutoWorktree", {
|
|
152
|
+
phase: "skip-ff-branch-not-ancestor",
|
|
153
|
+
milestoneId,
|
|
154
|
+
branch,
|
|
155
|
+
integration: integrationBranch,
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Codex peer-review: `nativeUpdateRef` succeeds even when the branch is
|
|
160
|
+
// currently checked out in another worktree, leaving that worktree's HEAD
|
|
161
|
+
// inconsistent with its index/work tree. Skip the fast-forward if any
|
|
162
|
+
// listed worktree has this branch checked out — the merge gate at
|
|
163
|
+
// milestone-completion will surface stale-base divergence as a conflict
|
|
164
|
+
// instead of silently corrupting the other worktree's state.
|
|
165
|
+
if (_isBranchCheckedOutElsewhere(basePath, branch)) {
|
|
166
|
+
debugLog("createAutoWorktree", {
|
|
167
|
+
phase: "skip-ff-branch-checked-out-elsewhere",
|
|
168
|
+
milestoneId,
|
|
169
|
+
branch,
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
nativeUpdateRef(basePath, `refs/heads/${branch}`, integrationBranch);
|
|
174
|
+
debugLog("createAutoWorktree", {
|
|
175
|
+
phase: "fast-forward-reused-branch",
|
|
176
|
+
milestoneId,
|
|
177
|
+
branch,
|
|
178
|
+
integration: integrationBranch,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
debugLog("createAutoWorktree", {
|
|
183
|
+
phase: "fast-forward-reused-branch-failed",
|
|
184
|
+
milestoneId,
|
|
185
|
+
branch,
|
|
186
|
+
error: err instanceof Error ? err.message : String(err),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// gsd-pi — Auto-worktree cleanup helpers.
|
|
2
|
+
//
|
|
3
|
+
// Owns shared cleanup primitives used by teardown and merge cleanup paths:
|
|
4
|
+
// same-file DB reconciliation checks, transient project-root state removal,
|
|
5
|
+
// git pathspec conversion for externally-rooted .gsd paths, and expected
|
|
6
|
+
// unlink error classification.
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { existsSync, realpathSync, unlinkSync } from "node:fs";
|
|
9
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
10
|
+
import { gsdRoot } from "./paths.js";
|
|
11
|
+
import { logWarning } from "./workflow-logger.js";
|
|
12
|
+
function isSamePath(a, b) {
|
|
13
|
+
try {
|
|
14
|
+
return realpathSync(a) === realpathSync(b);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
if (e.code === "ENOENT")
|
|
18
|
+
return false;
|
|
19
|
+
logWarning("worktree", `isSamePath failed: ${e.message}`);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function _isSamePath(a, b) {
|
|
24
|
+
return isSamePath(a, b);
|
|
25
|
+
}
|
|
26
|
+
export function _shouldReconcileWorktreeDb(worktreeDbPath, mainDbPath, pathExists = existsSync, samePath = isSamePath) {
|
|
27
|
+
return pathExists(worktreeDbPath) && !samePath(worktreeDbPath, mainDbPath);
|
|
28
|
+
}
|
|
29
|
+
export function _isExpectedWorktreeUnlinkError(code) {
|
|
30
|
+
return code === "ENOENT" || code === "EISDIR";
|
|
31
|
+
}
|
|
32
|
+
function gitPathspecForWorktreePath(basePath, targetPath) {
|
|
33
|
+
let base = basePath;
|
|
34
|
+
let target = targetPath;
|
|
35
|
+
try {
|
|
36
|
+
base = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
37
|
+
cwd: basePath,
|
|
38
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
39
|
+
encoding: "utf-8",
|
|
40
|
+
}).trim() || basePath;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* keep original */
|
|
44
|
+
void base;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
base = realpathSync.native(base);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* keep original */
|
|
51
|
+
void base;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
target = realpathSync.native(targetPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* keep original */
|
|
58
|
+
void target;
|
|
59
|
+
}
|
|
60
|
+
const rel = relative(base, target);
|
|
61
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
|
|
62
|
+
return null;
|
|
63
|
+
return rel.replaceAll("\\", "/");
|
|
64
|
+
}
|
|
65
|
+
export function _gitPathspecForWorktreePath(basePath, targetPath) {
|
|
66
|
+
return gitPathspecForWorktreePath(basePath, targetPath);
|
|
67
|
+
}
|
|
68
|
+
export function clearProjectRootStateFiles(basePath, milestoneId) {
|
|
69
|
+
const gsdDir = gsdRoot(basePath);
|
|
70
|
+
// Phase C pt 2: auto.lock removed from this list — the file is gone
|
|
71
|
+
// (migrated to the workers + unit_dispatches + runtime_kv tables). The
|
|
72
|
+
// remaining transient files (STATE.md, {MID}-META.json) are still
|
|
73
|
+
// worth removing on teardown.
|
|
74
|
+
const transientFiles = [
|
|
75
|
+
join(gsdDir, "STATE.md"),
|
|
76
|
+
join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
|
|
77
|
+
];
|
|
78
|
+
for (const file of transientFiles) {
|
|
79
|
+
try {
|
|
80
|
+
unlinkSync(file);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// ENOENT is expected — file may not exist (#3597)
|
|
84
|
+
if (err.code !== "ENOENT") {
|
|
85
|
+
logWarning("worktree", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Clean up legacy synced milestone directories and runtime/units.
|
|
90
|
+
// Older versions copied these into the project root during execution.
|
|
91
|
+
// If they remain as untracked files when we attempt
|
|
92
|
+
// `git merge --squash`, git rejects the merge with "local changes would
|
|
93
|
+
// be overwritten", causing silent data loss (#1738).
|
|
94
|
+
const syncedDirs = [
|
|
95
|
+
join(gsdDir, "milestones", milestoneId),
|
|
96
|
+
join(gsdDir, "runtime", "units"),
|
|
97
|
+
];
|
|
98
|
+
for (const dir of syncedDirs) {
|
|
99
|
+
try {
|
|
100
|
+
if (!existsSync(dir))
|
|
101
|
+
continue;
|
|
102
|
+
const pathspec = gitPathspecForWorktreePath(basePath, dir);
|
|
103
|
+
if (!pathspec)
|
|
104
|
+
continue;
|
|
105
|
+
// Only remove files that are untracked by git — tracked files are
|
|
106
|
+
// managed by the branch checkout and should not be deleted.
|
|
107
|
+
const untrackedOutput = execFileSync("git", ["ls-files", "--others", "--exclude-standard", pathspec], {
|
|
108
|
+
cwd: basePath,
|
|
109
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
110
|
+
encoding: "utf-8",
|
|
111
|
+
}).trim();
|
|
112
|
+
if (!untrackedOutput)
|
|
113
|
+
continue;
|
|
114
|
+
for (const f of untrackedOutput.split("\n").filter(Boolean)) {
|
|
115
|
+
try {
|
|
116
|
+
unlinkSync(join(basePath, f));
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
// ENOENT/EISDIR are expected for already-removed or
|
|
120
|
+
// directory entries (#3597).
|
|
121
|
+
const code = err.code;
|
|
122
|
+
if (!_isExpectedWorktreeUnlinkError(code)) {
|
|
123
|
+
logWarning("worktree", `untracked file unlink failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
/* non-fatal — git command may fail if not in repo */
|
|
130
|
+
logWarning("worktree", `untracked file cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|