@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
|
@@ -894,15 +894,64 @@
|
|
|
894
894
|
const isError = result?.isError || false;
|
|
895
895
|
const statusClass = result ? (isError ? 'error' : 'success') : 'pending';
|
|
896
896
|
|
|
897
|
+
// This template runs as a standalone browser script in exported HTML,
|
|
898
|
+
// so it cannot import normalizeToolResultContent from @gsd/pi-ai.
|
|
899
|
+
// Keep these fallback semantics aligned with that helper.
|
|
900
|
+
const stringifyResultContent = (content) => {
|
|
901
|
+
try {
|
|
902
|
+
const value = JSON.stringify(content);
|
|
903
|
+
if (typeof value === 'string') return value;
|
|
904
|
+
} catch {
|
|
905
|
+
// Fall through to String(content). JSON.stringify can throw for cycles.
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
try {
|
|
909
|
+
return String(content);
|
|
910
|
+
} catch {
|
|
911
|
+
return '[unstringifiable tool result content]';
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
const serializableCacheControl = (block) => {
|
|
916
|
+
if (!block || typeof block !== 'object' || !('cache_control' in block)) return {};
|
|
917
|
+
try {
|
|
918
|
+
JSON.stringify(block.cache_control);
|
|
919
|
+
return { cache_control: block.cache_control };
|
|
920
|
+
} catch {
|
|
921
|
+
return {};
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
const normalizeResultContentBlock = (block) => {
|
|
926
|
+
if (typeof block === 'string') return { type: 'text', text: block };
|
|
927
|
+
if (block && typeof block === 'object') {
|
|
928
|
+
const cacheControl = serializableCacheControl(block);
|
|
929
|
+
if (block.type === 'text' && typeof block.text === 'string') return { type: 'text', text: block.text, ...cacheControl };
|
|
930
|
+
if (block.type === 'image' && typeof block.data === 'string' && typeof block.mimeType === 'string') {
|
|
931
|
+
return { type: 'image', data: block.data, mimeType: block.mimeType, ...cacheControl };
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return { type: 'text', text: stringifyResultContent(block) };
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
const getResultContent = () => {
|
|
938
|
+
if (!result) return [];
|
|
939
|
+
if (Array.isArray(result.content)) {
|
|
940
|
+
const blocks = result.content.map(normalizeResultContentBlock);
|
|
941
|
+
return blocks.length > 0 ? blocks : [{ type: 'text', text: '' }];
|
|
942
|
+
}
|
|
943
|
+
if (typeof result.content === 'string') return [{ type: 'text', text: result.content }];
|
|
944
|
+
if (result.content == null) return [{ type: 'text', text: '' }];
|
|
945
|
+
return [{ type: 'text', text: stringifyResultContent(result.content) }];
|
|
946
|
+
};
|
|
947
|
+
|
|
897
948
|
const getResultText = () => {
|
|
898
|
-
|
|
899
|
-
const textBlocks = result.content.filter(c => c.type === 'text');
|
|
949
|
+
const textBlocks = getResultContent().filter(c => c.type === 'text');
|
|
900
950
|
return textBlocks.map(c => c.text).join('\n');
|
|
901
951
|
};
|
|
902
952
|
|
|
903
953
|
const getResultImages = () => {
|
|
904
|
-
|
|
905
|
-
return result.content.filter(c => c.type === 'image');
|
|
954
|
+
return getResultContent().filter(c => c.type === 'image');
|
|
906
955
|
};
|
|
907
956
|
|
|
908
957
|
const renderResultImages = () => {
|
package/pkg/package.json
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
import { importExtensionModule, type ExtensionAPI, type ExtensionContext } from "@gsd/pi-coding-agent";
|
|
5
5
|
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
6
8
|
import { join, basename } from "node:path";
|
|
7
|
-
import { existsSync, cpSync } from "node:fs";
|
|
9
|
+
import { existsSync, cpSync, readFileSync } from "node:fs";
|
|
8
10
|
import type { AutoSession } from "./session.js";
|
|
9
11
|
import type { LoopDeps } from "./loop-deps.js";
|
|
10
12
|
import type { GSDState } from "../types.js";
|
|
@@ -16,7 +18,7 @@ import { getIsolationMode } from "../preferences.js";
|
|
|
16
18
|
import { isDbAvailable, getMilestone } from "../gsd-db.js";
|
|
17
19
|
import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
|
|
18
20
|
import { isClosedStatus } from "../status-guards.js";
|
|
19
|
-
import { gsdRoot } from "../paths.js";
|
|
21
|
+
import { gsdRoot, PLANNING_ARTIFACT_SUFFIXES } from "../paths.js";
|
|
20
22
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
21
23
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
22
24
|
import { debugLog } from "../debug-logger.js";
|
|
@@ -98,7 +100,15 @@ export async function _runMilestoneMergeWithStashRestore(
|
|
|
98
100
|
): Promise<{ action: "break"; reason: string } | null> {
|
|
99
101
|
const { ctx, pi, s, deps } = ic;
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
// Resolve the project root once, before the merge can flip session path
|
|
104
|
+
// pointers (worktree teardown mutates s.basePath / s.scope). The guarded
|
|
105
|
+
// merge's preflight/postflight stash restore, the pre-merge ignored-markdown
|
|
106
|
+
// snapshot, the dirty-projection check, and the rebuild must all target this
|
|
107
|
+
// same tree; otherwise `.gsd` markdown restored on one tree can be silently
|
|
108
|
+
// overwritten by a rebuild on another. canonicalProjectRoot is the
|
|
109
|
+
// authoritative `.gsd`/DB writer path (see AutoSession.canonicalProjectRoot).
|
|
110
|
+
const projectRoot = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
111
|
+
const ignoredProjectionSnapshot = snapshotIgnoredGsdMarkdownProjections(projectRoot);
|
|
102
112
|
const mergeResult = deps.lifecycle.exitMilestone(
|
|
103
113
|
milestoneId,
|
|
104
114
|
{
|
|
@@ -113,12 +123,12 @@ export async function _runMilestoneMergeWithStashRestore(
|
|
|
113
123
|
);
|
|
114
124
|
|
|
115
125
|
if (mergeResult.ok) {
|
|
116
|
-
await markMilestoneMergedAndRebuild(s);
|
|
126
|
+
await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
|
|
117
127
|
return null;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
if (mergeResult.reason === "postflight-stash-restore-failed") {
|
|
121
|
-
await markMilestoneMergedAndRebuild(s);
|
|
131
|
+
await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
if (mergeResult.reason === "preflight-dirty-overlap" || mergeResult.reason === "preflight-unmerged-conflicts") {
|
|
@@ -180,10 +190,23 @@ export async function _runMilestoneMergeWithStashRestore(
|
|
|
180
190
|
return null;
|
|
181
191
|
}
|
|
182
192
|
|
|
183
|
-
|
|
193
|
+
type GsdMarkdownSnapshot = Map<string, string>;
|
|
194
|
+
|
|
195
|
+
async function markMilestoneMergedAndRebuild(
|
|
196
|
+
s: AutoSession,
|
|
197
|
+
rebuildBasePath: string,
|
|
198
|
+
ignoredProjectionSnapshot: GsdMarkdownSnapshot | null,
|
|
199
|
+
): Promise<void> {
|
|
184
200
|
s.milestoneMergedInPhases = true;
|
|
201
|
+
if (hasDirtyGsdMarkdownProjections(rebuildBasePath, ignoredProjectionSnapshot)) {
|
|
202
|
+
logWarning(
|
|
203
|
+
"engine",
|
|
204
|
+
"skipping markdown projection rebuild after milestone merge because restored .gsd edits are dirty",
|
|
205
|
+
);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
185
209
|
try {
|
|
186
|
-
const rebuildBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
187
210
|
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
188
211
|
await rebuildMarkdownProjectionsFromDb(rebuildBasePath);
|
|
189
212
|
} catch (err) {
|
|
@@ -194,6 +217,90 @@ async function markMilestoneMergedAndRebuild(s: AutoSession): Promise<void> {
|
|
|
194
217
|
}
|
|
195
218
|
}
|
|
196
219
|
|
|
220
|
+
function hasDirtyGsdMarkdownProjections(
|
|
221
|
+
basePath: string,
|
|
222
|
+
ignoredProjectionSnapshot: GsdMarkdownSnapshot | null,
|
|
223
|
+
): boolean {
|
|
224
|
+
try {
|
|
225
|
+
const output = execFileSync(
|
|
226
|
+
"git",
|
|
227
|
+
["status", "--porcelain", "-z", "--untracked-files=all", "--", ".gsd"],
|
|
228
|
+
{ cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
|
|
229
|
+
);
|
|
230
|
+
const entries = output.split("\0").filter(Boolean);
|
|
231
|
+
for (let i = 0; i < entries.length; i++) {
|
|
232
|
+
const entry = entries[i];
|
|
233
|
+
if (entry.length <= 3) continue;
|
|
234
|
+
const status = entry.slice(0, 2);
|
|
235
|
+
const path = entry.slice(3);
|
|
236
|
+
if (isGeneratedGsdMarkdownProjection(path)) return true;
|
|
237
|
+
if ((status === "R " || status === "C ") && i + 1 < entries.length) {
|
|
238
|
+
const dest = entries[++i];
|
|
239
|
+
if (isGeneratedGsdMarkdownProjection(dest)) return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
void err;
|
|
244
|
+
// Fall through to ignored-file snapshot comparison below.
|
|
245
|
+
}
|
|
246
|
+
return ignoredGsdMarkdownProjectionSnapshotChanged(basePath, ignoredProjectionSnapshot);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function isGeneratedGsdMarkdownProjection(path: string): boolean {
|
|
250
|
+
const normalized = path.replace(/\\/g, "/");
|
|
251
|
+
if (normalized === ".gsd/DECISIONS.md") return true;
|
|
252
|
+
if (!normalized.startsWith(".gsd/milestones/") && !normalized.startsWith(".gsd/phases/")) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return isGeneratedPlanningArtifactFileName(basename(normalized));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function isGeneratedPlanningArtifactFileName(fileName: string): boolean {
|
|
259
|
+
if (!fileName.endsWith(".md")) return false;
|
|
260
|
+
const stem = fileName.slice(0, -".md".length).toUpperCase();
|
|
261
|
+
for (const suffix of PLANNING_ARTIFACT_SUFFIXES) {
|
|
262
|
+
if (!stem.endsWith(`-${suffix}`)) continue;
|
|
263
|
+
const idPrefix = stem.slice(0, -suffix.length - 1);
|
|
264
|
+
return /^(?:[MST]\d+|\d{2,})(?:-[A-Z0-9]+)*$/.test(idPrefix);
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function snapshotIgnoredGsdMarkdownProjections(basePath: string): GsdMarkdownSnapshot | null {
|
|
270
|
+
try {
|
|
271
|
+
const output = execFileSync(
|
|
272
|
+
"git",
|
|
273
|
+
["ls-files", "--others", "--ignored", "--exclude-standard", "-z", "--", ".gsd"],
|
|
274
|
+
{ cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
|
|
275
|
+
);
|
|
276
|
+
const snapshot: GsdMarkdownSnapshot = new Map();
|
|
277
|
+
for (const path of output.split("\0")) {
|
|
278
|
+
if (!isGeneratedGsdMarkdownProjection(path)) continue;
|
|
279
|
+
snapshot.set(path, hashFile(join(basePath, path)));
|
|
280
|
+
}
|
|
281
|
+
return snapshot;
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hashFile(path: string): string {
|
|
288
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function ignoredGsdMarkdownProjectionSnapshotChanged(
|
|
292
|
+
basePath: string,
|
|
293
|
+
before: GsdMarkdownSnapshot | null,
|
|
294
|
+
): boolean {
|
|
295
|
+
if (!before) return true;
|
|
296
|
+
const after = snapshotIgnoredGsdMarkdownProjections(basePath);
|
|
297
|
+
if (!after || after.size !== before.size) return true;
|
|
298
|
+
for (const [path, hash] of before) {
|
|
299
|
+
if (after.get(path) !== hash) return true;
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
197
304
|
export async function _runMilestoneMergeOnceWithStashRestore(
|
|
198
305
|
ic: IterationContext,
|
|
199
306
|
milestoneId: string,
|
|
@@ -34,7 +34,8 @@ import {
|
|
|
34
34
|
preDispatchHealthGate,
|
|
35
35
|
recordHealthSnapshot,
|
|
36
36
|
} from "../doctor-proactive.js";
|
|
37
|
-
import {
|
|
37
|
+
import { autoWorktreeBranch } from "../auto-worktree-branch-lifecycle.js";
|
|
38
|
+
import { checkResourcesStale } from "../auto-worktree-resource-version.js";
|
|
38
39
|
import { getSessionLockStatus } from "../session-lock.js";
|
|
39
40
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
40
41
|
import { emitJournalEvent as _emitJournalEvent } from "../journal.js";
|
|
@@ -49,7 +50,7 @@ import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
|
|
|
49
50
|
import { GitServiceImpl } from "../git-service.js";
|
|
50
51
|
import { WorktreeStateProjection } from "../worktree-state-projection.js";
|
|
51
52
|
import { WorktreeLifecycle } from "../worktree-lifecycle.js";
|
|
52
|
-
import {
|
|
53
|
+
import { createDefaultMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
|
|
53
54
|
import { createWorkspace, scopeMilestone } from "../workspace.js";
|
|
54
55
|
import { supportsStructuredQuestions } from "../workflow-mcp.js";
|
|
55
56
|
import { getRegisteredToolSnapshot, getToolBaselineSnapshot } from "../auto-model-selection.js";
|
|
@@ -731,7 +732,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
731
732
|
return new GitServiceImpl(basePath, gitConfig);
|
|
732
733
|
},
|
|
733
734
|
worktreeProjection: new WorktreeStateProjection(),
|
|
734
|
-
mergeMilestone:
|
|
735
|
+
mergeMilestone: createDefaultMilestoneMergeTransaction(),
|
|
735
736
|
});
|
|
736
737
|
}
|
|
737
738
|
|
|
@@ -440,7 +440,8 @@ export async function runPreDispatch(
|
|
|
440
440
|
if (stop) return stop;
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
// PR creation (auto_pr) is handled
|
|
443
|
+
// PR creation (auto_pr) is handled by the publication handoff in
|
|
444
|
+
// the milestone merge transaction (#2302).
|
|
444
445
|
|
|
445
446
|
deps.invalidateAllCaches();
|
|
446
447
|
|
|
@@ -551,7 +552,8 @@ export async function runPreDispatch(
|
|
|
551
552
|
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
552
553
|
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
|
|
553
554
|
if (stop) return stop;
|
|
554
|
-
// PR creation (auto_pr) is handled
|
|
555
|
+
// PR creation (auto_pr) is handled by the publication handoff in
|
|
556
|
+
// the milestone merge transaction (#2302).
|
|
555
557
|
}
|
|
556
558
|
const unmappedActive = countUnmappedActiveRequirements();
|
|
557
559
|
const completionStopReason = formatCompletePhaseNextAction(unmappedActive);
|
|
@@ -654,7 +656,8 @@ export async function runPreDispatch(
|
|
|
654
656
|
// #2909 / #5538-followup: preflight stash + always-on postflight pop.
|
|
655
657
|
const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
|
|
656
658
|
if (stop) return stop;
|
|
657
|
-
// PR creation (auto_pr) is handled
|
|
659
|
+
// PR creation (auto_pr) is handled by the publication handoff in
|
|
660
|
+
// the milestone merge transaction (#2302).
|
|
658
661
|
}
|
|
659
662
|
deps.sendDesktopNotification(
|
|
660
663
|
"GSD",
|
|
@@ -29,7 +29,7 @@ import type { MinimalModelRegistry } from "../context-budget.js";
|
|
|
29
29
|
import { parseUnitId } from "../unit-id.js";
|
|
30
30
|
import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
|
|
31
31
|
import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
|
32
|
-
import {
|
|
32
|
+
import { getUnitWorkflowDispatchReadinessErrorForModel } from "../tool-contract.js";
|
|
33
33
|
import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
|
|
34
34
|
import {
|
|
35
35
|
applyThinkingLevelForModel,
|
|
@@ -343,17 +343,13 @@ export async function runUnitPhase(
|
|
|
343
343
|
? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
|
|
344
344
|
: null;
|
|
345
345
|
|
|
346
|
-
const compatibilityError =
|
|
347
|
-
|
|
346
|
+
const compatibilityError = getUnitWorkflowDispatchReadinessErrorForModel({
|
|
347
|
+
model: s.currentUnitModel as any,
|
|
348
|
+
fallbackModel: ctx.model,
|
|
349
|
+
getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
|
|
348
350
|
projectRoot: s.basePath,
|
|
349
351
|
surface: "auto-mode",
|
|
350
352
|
unitType,
|
|
351
|
-
authMode: s.currentUnitModel?.provider
|
|
352
|
-
? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
|
|
353
|
-
: ctx.model?.provider
|
|
354
|
-
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
355
|
-
: undefined,
|
|
356
|
-
baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
|
|
357
353
|
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
358
354
|
});
|
|
359
355
|
const workflowMcpPrepModel = s.currentUnitModel;
|
|
@@ -31,7 +31,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
|
31
31
|
import type { MinimalModelRegistry } from "./context-budget.js";
|
|
32
32
|
import { pauseAuto } from "./auto.js";
|
|
33
33
|
import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
|
|
34
|
-
import {
|
|
34
|
+
import { getUnitWorkflowDispatchReadinessErrorForModel } from "./tool-contract.js";
|
|
35
35
|
|
|
36
36
|
export function parseDirectDispatchPhase(raw: string): { phase: string; milestoneId?: string } {
|
|
37
37
|
const tokens = raw.trim().split(/\s+/).filter(Boolean);
|
|
@@ -266,13 +266,12 @@ export async function dispatchDirectPhase(
|
|
|
266
266
|
return;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
const compatibilityError =
|
|
270
|
-
|
|
269
|
+
const compatibilityError = getUnitWorkflowDispatchReadinessErrorForModel({
|
|
270
|
+
model: ctx.model,
|
|
271
|
+
getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
|
|
271
272
|
projectRoot,
|
|
272
273
|
surface: "direct phase dispatch",
|
|
273
274
|
unitType,
|
|
274
|
-
authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
|
|
275
|
-
baseUrl: ctx.model?.baseUrl,
|
|
276
275
|
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
277
276
|
});
|
|
278
277
|
if (compatibilityError) {
|
|
@@ -63,9 +63,11 @@ import {
|
|
|
63
63
|
detectWorktreeName,
|
|
64
64
|
setActiveMilestoneId,
|
|
65
65
|
} from "./worktree.js";
|
|
66
|
-
import {
|
|
66
|
+
import { isInAutoWorktree } from "./auto-worktree-entry.js";
|
|
67
|
+
import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
|
|
67
68
|
import { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
|
|
68
|
-
import {
|
|
69
|
+
import { cleanStaleRuntimeUnits } from "./auto-worktree-runtime-cleanup.js";
|
|
70
|
+
import { readResourceVersion } from "./auto-worktree-resource-version.js";
|
|
69
71
|
import { worktreePath as getWorktreeDir, isInsideWorktreesDir } from "./worktree-manager.js";
|
|
70
72
|
import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
|
|
71
73
|
import { queryJournal } from "./journal.js";
|
|
@@ -0,0 +1,237 @@
|
|
|
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
|
+
|
|
7
|
+
import { GSDError, GSD_GIT_ERROR } from "./errors.js";
|
|
8
|
+
import { readIntegrationBranch } from "./git-service.js";
|
|
9
|
+
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
10
|
+
import { debugLog } from "./debug-logger.js";
|
|
11
|
+
import { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
|
|
12
|
+
import {
|
|
13
|
+
nativeBranchExists,
|
|
14
|
+
nativeBranchForceReset,
|
|
15
|
+
nativeDetectMainBranch,
|
|
16
|
+
nativeIsAncestor,
|
|
17
|
+
nativeUpdateRef,
|
|
18
|
+
nativeWorktreeList,
|
|
19
|
+
} from "./native-git-bridge.js";
|
|
20
|
+
|
|
21
|
+
/** Returns the git branch name for a milestone worktree (`milestone/<MID>`). */
|
|
22
|
+
export function autoWorktreeBranch(milestoneId: string): string {
|
|
23
|
+
return `milestone/${milestoneId}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function _resolveAutoWorktreeStartPoint(
|
|
27
|
+
integrationBranch: string | null | undefined,
|
|
28
|
+
gitMainBranch: string | null | undefined,
|
|
29
|
+
branchExists: (branch: string) => boolean,
|
|
30
|
+
): string | undefined {
|
|
31
|
+
if (integrationBranch) return integrationBranch;
|
|
32
|
+
return gitMainBranch &&
|
|
33
|
+
typeof gitMainBranch === "string" &&
|
|
34
|
+
gitMainBranch.length > 0 &&
|
|
35
|
+
branchExists(gitMainBranch)
|
|
36
|
+
? gitMainBranch
|
|
37
|
+
: undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Enter branch isolation mode for a milestone.
|
|
42
|
+
*
|
|
43
|
+
* Creates `milestone/<MID>` from the integration branch (if it doesn't
|
|
44
|
+
* exist yet) and checks out to it. No worktree directory is created — the
|
|
45
|
+
* project root is the working copy; only HEAD changes.
|
|
46
|
+
*
|
|
47
|
+
* Uses the same 3-tier integration-branch fallback as createAutoWorktree:
|
|
48
|
+
* 1. META.json recorded integration branch
|
|
49
|
+
* 2. git.main_branch preference
|
|
50
|
+
* 3. nativeDetectMainBranch (origin/HEAD auto-detection)
|
|
51
|
+
*/
|
|
52
|
+
export function enterBranchModeForMilestone(
|
|
53
|
+
basePath: string,
|
|
54
|
+
milestoneId: string,
|
|
55
|
+
): void {
|
|
56
|
+
const branch = autoWorktreeBranch(milestoneId);
|
|
57
|
+
const branchExists = nativeBranchExists(basePath, branch);
|
|
58
|
+
|
|
59
|
+
if (!branchExists) {
|
|
60
|
+
// Create the milestone branch from the integration branch start-point.
|
|
61
|
+
const integrationBranch =
|
|
62
|
+
readIntegrationBranch(basePath, milestoneId) ?? undefined;
|
|
63
|
+
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
64
|
+
const startPoint =
|
|
65
|
+
_resolveAutoWorktreeStartPoint(
|
|
66
|
+
integrationBranch,
|
|
67
|
+
gitPrefs?.main_branch,
|
|
68
|
+
(branchName) => nativeBranchExists(basePath, branchName),
|
|
69
|
+
) ??
|
|
70
|
+
nativeDetectMainBranch(basePath);
|
|
71
|
+
|
|
72
|
+
// TOCTOU ancestry guard (Issue #4980 HIGH-3).
|
|
73
|
+
//
|
|
74
|
+
// The outer `branchExists` check is racy: a concurrent process
|
|
75
|
+
// (parallel-orchestrator worker, side-by-side `gsd` instance, or manual
|
|
76
|
+
// `git branch` invocation) may have created the branch with real commits
|
|
77
|
+
// between that check and this point. `nativeBranchForceReset` does
|
|
78
|
+
// `git branch -f`, which silently overwrites the branch ref — orphaning
|
|
79
|
+
// any commits not reachable from `startPoint`. Re-check immediately
|
|
80
|
+
// before the destructive call and refuse if the branch suddenly exists
|
|
81
|
+
// with non-ancestor commits.
|
|
82
|
+
const concurrentlyCreated = nativeBranchExists(basePath, branch);
|
|
83
|
+
if (
|
|
84
|
+
concurrentlyCreated &&
|
|
85
|
+
!nativeIsAncestor(basePath, branch, startPoint)
|
|
86
|
+
) {
|
|
87
|
+
throw new GSDError(
|
|
88
|
+
GSD_GIT_ERROR,
|
|
89
|
+
`Branch "${branch}" was created concurrently with commits not reachable from "${startPoint}". ` +
|
|
90
|
+
`Refusing to force-reset — would orphan prior work. ` +
|
|
91
|
+
`Resume the existing milestone or run \`git branch -D ${branch}\` to discard.`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
// nativeBranchForceReset creates (or resets) branch at startPoint,
|
|
95
|
+
// then checkout switches HEAD to it.
|
|
96
|
+
nativeBranchForceReset(basePath, branch, startPoint);
|
|
97
|
+
debugLog("auto-worktree", {
|
|
98
|
+
action: "enterBranchMode",
|
|
99
|
+
milestoneId,
|
|
100
|
+
branch,
|
|
101
|
+
startPoint,
|
|
102
|
+
created: true,
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
debugLog("auto-worktree", {
|
|
106
|
+
action: "enterBranchMode",
|
|
107
|
+
milestoneId,
|
|
108
|
+
branch,
|
|
109
|
+
reused: true,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
checkoutBranchWithStashGuard(
|
|
114
|
+
basePath,
|
|
115
|
+
branch,
|
|
116
|
+
`enter-branch-mode:${milestoneId}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* True when `branch` is checked out in any worktree listed by
|
|
122
|
+
* `git worktree list --porcelain`. Used to gate ref updates that would
|
|
123
|
+
* otherwise leave a concurrent worktree's HEAD inconsistent with its
|
|
124
|
+
* index/working tree (Codex peer-review of #5538-followup).
|
|
125
|
+
*
|
|
126
|
+
* Best-effort: a `nativeWorktreeList` failure returns true so we err on
|
|
127
|
+
* the side of NOT moving the ref. Better to skip a fast-forward than to
|
|
128
|
+
* silently corrupt another worktree.
|
|
129
|
+
*/
|
|
130
|
+
export function _isBranchCheckedOutElsewhere(
|
|
131
|
+
basePath: string,
|
|
132
|
+
branch: string,
|
|
133
|
+
): boolean {
|
|
134
|
+
try {
|
|
135
|
+
const entries = nativeWorktreeList(basePath);
|
|
136
|
+
return entries.some((entry) => entry.branch === branch);
|
|
137
|
+
} catch {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve the integration branch using the same 3-tier fallback as the
|
|
144
|
+
* fresh-create path: META.json → git.main_branch preference → detected
|
|
145
|
+
* main branch. Returns null when no usable target exists.
|
|
146
|
+
*/
|
|
147
|
+
function resolveIntegrationBranchForReuse(
|
|
148
|
+
basePath: string,
|
|
149
|
+
milestoneId: string,
|
|
150
|
+
): string | null {
|
|
151
|
+
const fromMeta = readIntegrationBranch(basePath, milestoneId);
|
|
152
|
+
if (fromMeta) return fromMeta;
|
|
153
|
+
|
|
154
|
+
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
155
|
+
const fromPref = gitPrefs?.main_branch &&
|
|
156
|
+
typeof gitPrefs.main_branch === "string" &&
|
|
157
|
+
gitPrefs.main_branch.length > 0 &&
|
|
158
|
+
nativeBranchExists(basePath, gitPrefs.main_branch)
|
|
159
|
+
? gitPrefs.main_branch
|
|
160
|
+
: null;
|
|
161
|
+
if (fromPref) return fromPref;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
return nativeDetectMainBranch(basePath);
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* When reusing an existing milestone branch, fast-forward it onto the
|
|
172
|
+
* integration branch when that's safe (branch is a strict ancestor of
|
|
173
|
+
* integration — no commits would be lost). Skips when the branch has its
|
|
174
|
+
* own commits ahead of integration, when the integration branch can't be
|
|
175
|
+
* resolved, or when any git operation fails — the merge gate at milestone
|
|
176
|
+
* completion will surface real divergence as a conflict.
|
|
177
|
+
*
|
|
178
|
+
* The previous behavior re-attached the worktree to whatever stale tip
|
|
179
|
+
* the branch held, which caused new milestone work to fork from a base
|
|
180
|
+
* missing prior milestones' merges (#5538-followup).
|
|
181
|
+
*/
|
|
182
|
+
export function fastForwardReusedMilestoneBranchIfSafe(
|
|
183
|
+
basePath: string,
|
|
184
|
+
milestoneId: string,
|
|
185
|
+
branch: string,
|
|
186
|
+
): void {
|
|
187
|
+
try {
|
|
188
|
+
const integrationBranch = resolveIntegrationBranchForReuse(
|
|
189
|
+
basePath,
|
|
190
|
+
milestoneId,
|
|
191
|
+
);
|
|
192
|
+
if (!integrationBranch || integrationBranch === branch) return;
|
|
193
|
+
if (!nativeBranchExists(basePath, integrationBranch)) return;
|
|
194
|
+
|
|
195
|
+
// Pure fast-forward only: branch must be a strict ancestor of integration.
|
|
196
|
+
// If the branch has its own commits ahead, leave it alone.
|
|
197
|
+
if (!nativeIsAncestor(basePath, branch, integrationBranch)) {
|
|
198
|
+
debugLog("createAutoWorktree", {
|
|
199
|
+
phase: "skip-ff-branch-not-ancestor",
|
|
200
|
+
milestoneId,
|
|
201
|
+
branch,
|
|
202
|
+
integration: integrationBranch,
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Codex peer-review: `nativeUpdateRef` succeeds even when the branch is
|
|
208
|
+
// currently checked out in another worktree, leaving that worktree's HEAD
|
|
209
|
+
// inconsistent with its index/work tree. Skip the fast-forward if any
|
|
210
|
+
// listed worktree has this branch checked out — the merge gate at
|
|
211
|
+
// milestone-completion will surface stale-base divergence as a conflict
|
|
212
|
+
// instead of silently corrupting the other worktree's state.
|
|
213
|
+
if (_isBranchCheckedOutElsewhere(basePath, branch)) {
|
|
214
|
+
debugLog("createAutoWorktree", {
|
|
215
|
+
phase: "skip-ff-branch-checked-out-elsewhere",
|
|
216
|
+
milestoneId,
|
|
217
|
+
branch,
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
nativeUpdateRef(basePath, `refs/heads/${branch}`, integrationBranch);
|
|
223
|
+
debugLog("createAutoWorktree", {
|
|
224
|
+
phase: "fast-forward-reused-branch",
|
|
225
|
+
milestoneId,
|
|
226
|
+
branch,
|
|
227
|
+
integration: integrationBranch,
|
|
228
|
+
});
|
|
229
|
+
} catch (err) {
|
|
230
|
+
debugLog("createAutoWorktree", {
|
|
231
|
+
phase: "fast-forward-reused-branch-failed",
|
|
232
|
+
milestoneId,
|
|
233
|
+
branch,
|
|
234
|
+
error: err instanceof Error ? err.message : String(err),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|