@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.4fb2d460
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/ask-user-questions.js +1 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
- package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
- package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +12 -3
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
- package/dist/resources/extensions/gsd/auto-start.js +4 -3
- package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
- package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
- package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
- package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
- package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
- package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
- package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
- package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
- package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
- package/dist/resources/extensions/gsd/auto.js +9 -3
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
- package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
- package/dist/resources/extensions/gsd/db/queries.js +9 -9
- package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
- package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
- package/dist/resources/extensions/gsd/git-service.js +45 -0
- package/dist/resources/extensions/gsd/gsd-db.js +23 -11
- package/dist/resources/extensions/gsd/guided-flow.js +22 -17
- package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
- package/dist/resources/extensions/gsd/md-importer.js +89 -23
- package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
- package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
- package/dist/resources/extensions/gsd/paths.js +17 -3
- package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
- package/dist/resources/extensions/gsd/preferences.js +20 -0
- package/dist/resources/extensions/gsd/prompts/system.md +6 -6
- package/dist/resources/extensions/gsd/quick.js +12 -3
- package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
- package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
- package/dist/resources/extensions/gsd/unit-registry.js +12 -2
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
- package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
- package/dist/resources/extensions/shared/interview-ui.js +62 -9
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
- package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
- package/package.json +6 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +14 -9
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
- package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
- package/packages/pi-ai/dist/tool-result-content.js +62 -0
- package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
- package/packages/pi-ai/dist/types.d.ts +8 -20
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
- package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +4 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/core/export-html/template.js +53 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +1 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
- package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
- package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +12 -3
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
- package/src/resources/extensions/gsd/auto-start.ts +5 -3
- package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
- package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
- package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
- package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
- package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
- package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
- package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
- package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
- package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
- package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
- package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
- package/src/resources/extensions/gsd/auto.ts +11 -17
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
- package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
- package/src/resources/extensions/gsd/db/queries.ts +9 -9
- package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
- package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +53 -0
- package/src/resources/extensions/gsd/gsd-db.ts +33 -11
- package/src/resources/extensions/gsd/guided-flow.ts +26 -18
- package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
- package/src/resources/extensions/gsd/md-importer.ts +93 -22
- package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
- package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/paths.ts +21 -3
- package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
- package/src/resources/extensions/gsd/preferences-types.ts +30 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
- package/src/resources/extensions/gsd/preferences.ts +20 -0
- package/src/resources/extensions/gsd/prompts/system.md +6 -6
- package/src/resources/extensions/gsd/quick.ts +11 -3
- package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
- package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
- package/src/resources/extensions/gsd/tool-contract.ts +29 -0
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
- package/src/resources/extensions/gsd/unit-registry.ts +16 -2
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
- package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
- package/src/resources/extensions/shared/interview-ui.ts +58 -11
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
- /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, utimesSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
@@ -360,6 +360,174 @@ test("canonical worktree + project-root legacy dir produces legacy filename (#bu
|
|
|
360
360
|
}
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
+
// ── cursor[bot] 3523932401: bare milestone id must not match a team-suffix ────
|
|
364
|
+
//
|
|
365
|
+
// A bare milestone id (M001, no team-mode suffix) resolves its phase dir by
|
|
366
|
+
// scanning phases/NN-*. Before the guard (commit 8f45bf3),
|
|
367
|
+
// phaseDirMatchesMilestoneId returned true for ANY same-number dir, so a
|
|
368
|
+
// leftover team-mode projection (phases/01-<6char>-.../, from a prior
|
|
369
|
+
// team-suffixed run) matched the bare id too. When that stale dir carried a
|
|
370
|
+
// newer mtime, pickPreferredPhaseDir selected it over the real title dir and
|
|
371
|
+
// resolved the ROADMAP into a directory that has no ROADMAP — the file-not-found
|
|
372
|
+
// retry loop. The guard (slugLooksLikeTeamSuffixProjection) rejects
|
|
373
|
+
// team-suffix-looking slugs for bare ids in the primary pass; the suffixed
|
|
374
|
+
// projection stays available only as a fallback when the primary pass finds
|
|
375
|
+
// nothing (#1195, guarded by the second test below).
|
|
376
|
+
|
|
377
|
+
test("bare milestone id prefers the real title dir over a newer team-suffix projection (cursor 3523932401)", () => {
|
|
378
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-bare-teamsuffix-")));
|
|
379
|
+
try {
|
|
380
|
+
const phasesDir = join(root, ".gsd", "phases");
|
|
381
|
+
// Intended flat-phase dir for the bare id: 01-<title-slug>/ with the ROADMAP.
|
|
382
|
+
const intendedDir = join(phasesDir, "01-clean-continuation");
|
|
383
|
+
mkdirSync(intendedDir, { recursive: true });
|
|
384
|
+
writeFileSync(join(intendedDir, "01-ROADMAP.md"), "# roadmap\n");
|
|
385
|
+
|
|
386
|
+
// Leftover team-mode projection: 01-<6char>-.../ with NO ROADMAP, made
|
|
387
|
+
// NEWER so an mtime tiebreak would wrongly prefer it without the guard.
|
|
388
|
+
const staleDir = join(phasesDir, "01-re4q3k-old-team-projection");
|
|
389
|
+
mkdirSync(staleDir, { recursive: true });
|
|
390
|
+
const newer = new Date(Date.now() + 60_000);
|
|
391
|
+
const older = new Date(Date.now() - 60_000);
|
|
392
|
+
utimesSync(staleDir, newer, newer);
|
|
393
|
+
utimesSync(intendedDir, older, older);
|
|
394
|
+
|
|
395
|
+
_clearGsdRootCache();
|
|
396
|
+
clearPathCache();
|
|
397
|
+
|
|
398
|
+
const resolved = resolveExpectedArtifactPath("plan-milestone", "M001", root);
|
|
399
|
+
assert.equal(
|
|
400
|
+
resolved,
|
|
401
|
+
join(intendedDir, "01-ROADMAP.md"),
|
|
402
|
+
"bare id must resolve to the real title dir, not the newer team-suffix projection",
|
|
403
|
+
);
|
|
404
|
+
assert.notEqual(
|
|
405
|
+
resolved,
|
|
406
|
+
join(staleDir, "01-ROADMAP.md"),
|
|
407
|
+
"must NOT resolve into the team-suffix projection dir that has no ROADMAP",
|
|
408
|
+
);
|
|
409
|
+
} finally {
|
|
410
|
+
_clearGsdRootCache();
|
|
411
|
+
clearPathCache();
|
|
412
|
+
rmSync(root, { recursive: true, force: true });
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("bare milestone id still falls back to a lone team-suffix projection (#1195)", () => {
|
|
417
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-bare-teamsuffix-only-")));
|
|
418
|
+
try {
|
|
419
|
+
const phasesDir = join(root, ".gsd", "phases");
|
|
420
|
+
// The only on-disk dir for the bare id is a team-mode projection. With no
|
|
421
|
+
// non-suffixed candidate, the fallback pass must still resolve to it.
|
|
422
|
+
const onlyDir = join(phasesDir, "01-re4q3k-clean-continuation");
|
|
423
|
+
mkdirSync(onlyDir, { recursive: true });
|
|
424
|
+
writeFileSync(join(onlyDir, "01-ROADMAP.md"), "# roadmap\n");
|
|
425
|
+
|
|
426
|
+
_clearGsdRootCache();
|
|
427
|
+
clearPathCache();
|
|
428
|
+
|
|
429
|
+
const resolved = resolveExpectedArtifactPath("plan-milestone", "M001", root);
|
|
430
|
+
assert.equal(
|
|
431
|
+
resolved,
|
|
432
|
+
join(onlyDir, "01-ROADMAP.md"),
|
|
433
|
+
"with no non-suffixed dir, the team-suffix projection is the correct fallback",
|
|
434
|
+
);
|
|
435
|
+
} finally {
|
|
436
|
+
_clearGsdRootCache();
|
|
437
|
+
clearPathCache();
|
|
438
|
+
rmSync(root, { recursive: true, force: true });
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// ── #1208: flat-phase execute-task summaries resolve at the phase root ────────
|
|
443
|
+
//
|
|
444
|
+
// In flat-phase layout task summaries are written beside the plan files
|
|
445
|
+
// (phases/<phase>/T##-SUMMARY.md), NOT under a tasks/ subdir. A tasks/ dir may
|
|
446
|
+
// still exist to hold auxiliary task-scoped gate artifacts (e.g. T01-VERIFY.json).
|
|
447
|
+
// Before the fix, the mere existence of that tasks/ dir redirected summary
|
|
448
|
+
// verification into tasks/T##-SUMMARY.md — which never exists — trapping auto-mode
|
|
449
|
+
// in a false verification retry that eventually paused after a successful task.
|
|
450
|
+
|
|
451
|
+
test("flat-phase execute-task summary resolves at phase root when no tasks/ dir exists (#1208)", () => {
|
|
452
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-flat-task-summary-")));
|
|
453
|
+
try {
|
|
454
|
+
const gsd = join(root, ".gsd");
|
|
455
|
+
const phaseDir = join(gsd, "phases", "49-end-to-end-mandates");
|
|
456
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
457
|
+
writeFileSync(join(phaseDir, "49-03-PLAN.md"), "# plan\n- [x] T02\n");
|
|
458
|
+
|
|
459
|
+
_clearGsdRootCache();
|
|
460
|
+
clearPathCache();
|
|
461
|
+
|
|
462
|
+
assert.equal(
|
|
463
|
+
resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root),
|
|
464
|
+
join(phaseDir, "T02-SUMMARY.md"),
|
|
465
|
+
);
|
|
466
|
+
} finally {
|
|
467
|
+
_clearGsdRootCache();
|
|
468
|
+
clearPathCache();
|
|
469
|
+
rmSync(root, { recursive: true, force: true });
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("flat-phase execute-task summary resolves at phase root even when tasks/ dir holds gate artifacts (#1208)", () => {
|
|
474
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-flat-task-tasksdir-")));
|
|
475
|
+
try {
|
|
476
|
+
const gsd = join(root, ".gsd");
|
|
477
|
+
const phaseDir = join(gsd, "phases", "49-end-to-end-mandates");
|
|
478
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
479
|
+
writeFileSync(join(phaseDir, "49-03-PLAN.md"), "# plan\n- [x] T02\n");
|
|
480
|
+
// Auxiliary task-scoped gate artifact — creates the tasks/ dir but must not
|
|
481
|
+
// redirect the T02 summary into tasks/.
|
|
482
|
+
const tasksDir = join(phaseDir, "tasks");
|
|
483
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
484
|
+
writeFileSync(join(tasksDir, "T01-VERIFY.json"), '{"ok":true}');
|
|
485
|
+
|
|
486
|
+
_clearGsdRootCache();
|
|
487
|
+
clearPathCache();
|
|
488
|
+
|
|
489
|
+
const resolved = resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root);
|
|
490
|
+
assert.equal(
|
|
491
|
+
resolved,
|
|
492
|
+
join(phaseDir, "T02-SUMMARY.md"),
|
|
493
|
+
"flat-phase task summary must resolve at the phase root, not tasks/",
|
|
494
|
+
);
|
|
495
|
+
assert.notEqual(
|
|
496
|
+
resolved,
|
|
497
|
+
join(tasksDir, "T02-SUMMARY.md"),
|
|
498
|
+
"a tasks/ dir with gate artifacts must NOT redirect summary resolution into tasks/",
|
|
499
|
+
);
|
|
500
|
+
} finally {
|
|
501
|
+
_clearGsdRootCache();
|
|
502
|
+
clearPathCache();
|
|
503
|
+
rmSync(root, { recursive: true, force: true });
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("legacy execute-task summary still resolves under slices/<SID>/tasks/ (#1208 regression guard)", () => {
|
|
508
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-legacy-task-summary-")));
|
|
509
|
+
try {
|
|
510
|
+
const milestoneDir = join(root, ".gsd", "milestones", "M001");
|
|
511
|
+
const sliceDir = join(milestoneDir, "slices", "S01");
|
|
512
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
513
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
514
|
+
writeFileSync(join(milestoneDir, "M001-CONTEXT.md"), "# context\n");
|
|
515
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), "# plan\n");
|
|
516
|
+
|
|
517
|
+
_clearGsdRootCache();
|
|
518
|
+
clearPathCache();
|
|
519
|
+
|
|
520
|
+
assert.equal(
|
|
521
|
+
resolveExpectedArtifactPath("execute-task", "M001/S01/T02", root),
|
|
522
|
+
join(tasksDir, "T02-SUMMARY.md"),
|
|
523
|
+
);
|
|
524
|
+
} finally {
|
|
525
|
+
_clearGsdRootCache();
|
|
526
|
+
clearPathCache();
|
|
527
|
+
rmSync(root, { recursive: true, force: true });
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
363
531
|
// ── resolveSliceResearchLocation / resolveExistingSliceResearchPath ───────────
|
|
364
532
|
//
|
|
365
533
|
// Added in the code-quality consolidation PR as the shared dual-path resolver
|
|
@@ -14,7 +14,8 @@ import { join } from "node:path";
|
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
import { execFileSync } from "node:child_process";
|
|
16
16
|
|
|
17
|
-
import { createAutoWorktree
|
|
17
|
+
import { createAutoWorktree } from "../auto-worktree-creation.ts";
|
|
18
|
+
import { teardownAutoWorktree } from "../auto-worktree-teardown.ts";
|
|
18
19
|
|
|
19
20
|
function git(args: string[], cwd: string): void {
|
|
20
21
|
execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -299,6 +299,63 @@ test("refreshRecoveryDbForArtifact treats missing execute-task DB rows as fatal
|
|
|
299
299
|
});
|
|
300
300
|
});
|
|
301
301
|
|
|
302
|
+
test("refreshRecoveryDbForArtifact promotes an open execute-task DB row when artifacts are verified on disk (#1204)", () => {
|
|
303
|
+
const dir = makeTmpProject();
|
|
304
|
+
insertTask({
|
|
305
|
+
milestoneId: "M001",
|
|
306
|
+
sliceId: "S01",
|
|
307
|
+
id: "T01",
|
|
308
|
+
title: "Stuck Task",
|
|
309
|
+
status: "pending",
|
|
310
|
+
});
|
|
311
|
+
const sliceDir = join(dir, ".gsd", "milestones", "M001", "slices", "S01");
|
|
312
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
313
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
314
|
+
"# S01 Plan",
|
|
315
|
+
"",
|
|
316
|
+
"- [ ] **T01: Implement feature** `est:1h`",
|
|
317
|
+
"",
|
|
318
|
+
].join("\n"));
|
|
319
|
+
|
|
320
|
+
const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01", dir);
|
|
321
|
+
|
|
322
|
+
assert.deepEqual(result, { ok: true });
|
|
323
|
+
assert.equal(getTask("M001", "S01", "T01")?.status, "complete");
|
|
324
|
+
const planContent = readFileSync(join(sliceDir, "S01-PLAN.md"), "utf-8");
|
|
325
|
+
assert.ok(planContent.includes("[x] **T01:"), "plan checkbox should be re-rendered to [x]");
|
|
326
|
+
const events = readEvents(join(dir, ".gsd", "event-log.jsonl"));
|
|
327
|
+
assert.ok(
|
|
328
|
+
events.some((e) => e.cmd === "complete-task" && e.trigger_reason === "stuck-artifact-recovery"),
|
|
329
|
+
"a complete-task recovery event should be appended",
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("refreshRecoveryDbForArtifact still appends the recovery event when the plan checkbox sync throws after DB promotion (#1221)", () => {
|
|
334
|
+
const dir = makeTmpProject();
|
|
335
|
+
insertTask({
|
|
336
|
+
milestoneId: "M001",
|
|
337
|
+
sliceId: "S01",
|
|
338
|
+
id: "T01",
|
|
339
|
+
title: "Stuck Task",
|
|
340
|
+
status: "pending",
|
|
341
|
+
});
|
|
342
|
+
const sliceDir = join(dir, ".gsd", "milestones", "M001", "slices", "S01");
|
|
343
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
344
|
+
// Make the expected plan path a directory so the best-effort checkbox
|
|
345
|
+
// rewrite (readFileSync) throws EISDIR after the DB row is already promoted.
|
|
346
|
+
mkdirSync(join(sliceDir, "S01-PLAN.md"), { recursive: true });
|
|
347
|
+
|
|
348
|
+
const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01", dir);
|
|
349
|
+
|
|
350
|
+
assert.deepEqual(result, { ok: true });
|
|
351
|
+
assert.equal(getTask("M001", "S01", "T01")?.status, "complete");
|
|
352
|
+
const events = readEvents(join(dir, ".gsd", "event-log.jsonl"));
|
|
353
|
+
assert.ok(
|
|
354
|
+
events.some((e) => e.cmd === "complete-task" && e.trigger_reason === "stuck-artifact-recovery"),
|
|
355
|
+
"recovery event must still be appended even when the plan checkbox sync fails",
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
302
359
|
test("refreshRecoveryDbForArtifact closes complete-milestone DB row when artifacts exist but DB is stale (#5568)", async () => {
|
|
303
360
|
const base = mkdtempSync(join(tmpdir(), "auto-recovery-complete-ms-"));
|
|
304
361
|
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
@@ -6,7 +6,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
|
-
import { cleanStaleRuntimeUnits } from "../auto-worktree.ts";
|
|
9
|
+
import { cleanStaleRuntimeUnits } from "../auto-worktree-runtime-cleanup.ts";
|
|
10
10
|
|
|
11
11
|
function makeBase(): string {
|
|
12
12
|
return join(tmpdir(), `gsd-clean-runtime-${randomUUID()}`);
|
|
@@ -28,7 +28,8 @@ import { join } from "node:path";
|
|
|
28
28
|
import { tmpdir } from "node:os";
|
|
29
29
|
|
|
30
30
|
import { buildFlatRateContext } from "../auto-model-selection.ts";
|
|
31
|
-
import {
|
|
31
|
+
import { _resetAutoWorktreeOriginalBaseForTests } from "../auto-worktree-session-registry.ts";
|
|
32
|
+
import { _isSamePath } from "../auto-worktree-path-utils.ts";
|
|
32
33
|
import {
|
|
33
34
|
checkAutoStartAfterDiscuss,
|
|
34
35
|
clearPendingAutoStart,
|
|
@@ -11,7 +11,7 @@ import assert from "node:assert/strict";
|
|
|
11
11
|
import {
|
|
12
12
|
isSafeToAutoResolve,
|
|
13
13
|
SAFE_AUTO_RESOLVE_PATTERNS,
|
|
14
|
-
} from "../auto-worktree.ts";
|
|
14
|
+
} from "../auto-worktree-conflict-auto-resolve.ts";
|
|
15
15
|
|
|
16
16
|
describe("isSafeToAutoResolve", () => {
|
|
17
17
|
// ─── .gsd/ state files ───────────────────────────────────────────────────
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// gsd-pi — Regression tests for already-merged milestone cleanup safety.
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterEach, beforeEach, describe, test } from "node:test";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
finalizeAlreadyMergedMilestoneIfReachable,
|
|
12
|
+
type AlreadyMergedMilestoneRequest,
|
|
13
|
+
} from "../auto-worktree-merge-already-merged.js";
|
|
14
|
+
import {
|
|
15
|
+
_resetMilestoneCleanupDepsForTests,
|
|
16
|
+
_setMilestoneCleanupDepsForTests,
|
|
17
|
+
} from "../auto-worktree-merge-cleanup.js";
|
|
18
|
+
import { GSDError } from "../errors.js";
|
|
19
|
+
|
|
20
|
+
const cleanupPaths: string[] = [];
|
|
21
|
+
const initialCwd = process.cwd();
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
_resetMilestoneCleanupDepsForTests();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
_resetMilestoneCleanupDepsForTests();
|
|
29
|
+
process.chdir(initialCwd);
|
|
30
|
+
for (const path of cleanupPaths.splice(0)) {
|
|
31
|
+
rmSync(path, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function git(cwd: string, args: string[]): string {
|
|
36
|
+
return execFileSync("git", args, {
|
|
37
|
+
cwd,
|
|
38
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
39
|
+
encoding: "utf-8",
|
|
40
|
+
}).trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function commitFile(cwd: string, file: string, content: string, message: string): string {
|
|
44
|
+
writeFileSync(join(cwd, file), content);
|
|
45
|
+
git(cwd, ["add", file]);
|
|
46
|
+
git(cwd, ["commit", "-m", message]);
|
|
47
|
+
return git(cwd, ["rev-parse", "HEAD"]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createRepo(): string {
|
|
51
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-already-merged-"));
|
|
52
|
+
cleanupPaths.push(repo);
|
|
53
|
+
git(repo, ["init", "-b", "main"]);
|
|
54
|
+
git(repo, ["config", "user.email", "test@example.invalid"]);
|
|
55
|
+
git(repo, ["config", "user.name", "Test"]);
|
|
56
|
+
commitFile(repo, "README.md", "base\n", "initial");
|
|
57
|
+
return repo;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createRegularMergedMilestone(repo: string, milestoneId = "M001"): { milestoneBranch: string } {
|
|
61
|
+
const milestoneBranch = `milestone/${milestoneId}`;
|
|
62
|
+
git(repo, ["checkout", "-b", milestoneBranch]);
|
|
63
|
+
commitFile(repo, "feature.txt", "milestone work\n", `feat: implement ${milestoneId}`);
|
|
64
|
+
git(repo, ["checkout", "main"]);
|
|
65
|
+
git(repo, ["merge", "--no-ff", milestoneBranch, "-m", `merge ${milestoneId}`]);
|
|
66
|
+
return { milestoneBranch };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function request(
|
|
70
|
+
repo: string,
|
|
71
|
+
milestoneBranch: string,
|
|
72
|
+
previousCwd: string,
|
|
73
|
+
): AlreadyMergedMilestoneRequest {
|
|
74
|
+
return {
|
|
75
|
+
commitMessage: "merge milestone",
|
|
76
|
+
mainBranch: "main",
|
|
77
|
+
milestoneBranch,
|
|
78
|
+
milestoneId: "M001",
|
|
79
|
+
previousCwd,
|
|
80
|
+
projectRoot: repo,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe("finalizeAlreadyMergedMilestoneIfReachable", () => {
|
|
85
|
+
test("returns null when the milestone branch is not reachable from integration branch", () => {
|
|
86
|
+
const repo = createRepo();
|
|
87
|
+
git(repo, ["checkout", "-b", "milestone/M001"]);
|
|
88
|
+
commitFile(repo, "feature.txt", "milestone work\n", "feat: implement M001");
|
|
89
|
+
git(repo, ["checkout", "main"]);
|
|
90
|
+
|
|
91
|
+
let cleanupCalled = false;
|
|
92
|
+
_setMilestoneCleanupDepsForTests({
|
|
93
|
+
removeWorktree: () => {
|
|
94
|
+
cleanupCalled = true;
|
|
95
|
+
return true;
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = finalizeAlreadyMergedMilestoneIfReachable(
|
|
100
|
+
request(repo, "milestone/M001", repo),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
assert.equal(result, null);
|
|
104
|
+
assert.equal(cleanupCalled, false, "unmerged branch must not be cleaned up");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("cleans up a regular-merged milestone after main advances", () => {
|
|
108
|
+
const repo = createRepo();
|
|
109
|
+
const { milestoneBranch } = createRegularMergedMilestone(repo);
|
|
110
|
+
commitFile(repo, "hotfix.txt", "later main work\n", "fix: advance main");
|
|
111
|
+
const previousCwd = join(repo, ".gsd-worktrees", "M001");
|
|
112
|
+
const calls: string[] = [];
|
|
113
|
+
|
|
114
|
+
_setMilestoneCleanupDepsForTests({
|
|
115
|
+
removeWorktree: (_projectRoot, milestoneId, options) => {
|
|
116
|
+
assert.ok(options, "cleanup must pass branch options");
|
|
117
|
+
calls.push(
|
|
118
|
+
`remove:${milestoneId}:${options.branch}:${String(options.deleteBranch)}`,
|
|
119
|
+
);
|
|
120
|
+
return true;
|
|
121
|
+
},
|
|
122
|
+
nativeBranchDelete: (_projectRoot, branch) => {
|
|
123
|
+
calls.push(`delete:${branch}`);
|
|
124
|
+
},
|
|
125
|
+
setActiveWorkspace: (workspace) => {
|
|
126
|
+
calls.push(`workspace:${String(workspace)}`);
|
|
127
|
+
},
|
|
128
|
+
nudgeGitBranchCache: (path) => {
|
|
129
|
+
calls.push(`nudge:${path}`);
|
|
130
|
+
},
|
|
131
|
+
chdir: (path) => {
|
|
132
|
+
calls.push(`chdir:${path}`);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const result = finalizeAlreadyMergedMilestoneIfReachable(
|
|
137
|
+
request(repo, milestoneBranch, previousCwd),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
assert.deepEqual(result, {
|
|
141
|
+
commitMessage: "merge milestone",
|
|
142
|
+
pushed: false,
|
|
143
|
+
prCreated: false,
|
|
144
|
+
codeFilesChanged: true,
|
|
145
|
+
});
|
|
146
|
+
assert.deepEqual(calls, [
|
|
147
|
+
"remove:M001:milestone/M001:false",
|
|
148
|
+
"delete:milestone/M001",
|
|
149
|
+
"workspace:null",
|
|
150
|
+
`nudge:${previousCwd}`,
|
|
151
|
+
`chdir:${repo}`,
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("throws and restores cwd when regular-merged code was later removed from main", () => {
|
|
156
|
+
const repo = createRepo();
|
|
157
|
+
const { milestoneBranch } = createRegularMergedMilestone(repo);
|
|
158
|
+
commitFile(repo, "feature.txt", "", "chore: remove milestone feature");
|
|
159
|
+
const previousCwd = join(repo, "caller-cwd");
|
|
160
|
+
mkdirSync(previousCwd);
|
|
161
|
+
process.chdir(repo);
|
|
162
|
+
|
|
163
|
+
assert.throws(
|
|
164
|
+
() => finalizeAlreadyMergedMilestoneIfReachable(
|
|
165
|
+
request(repo, milestoneBranch, previousCwd),
|
|
166
|
+
),
|
|
167
|
+
(err: unknown) => err instanceof GSDError
|
|
168
|
+
&& /milestone-touched code file/.test(err.message)
|
|
169
|
+
&& /prevent data loss/.test(err.message),
|
|
170
|
+
);
|
|
171
|
+
assert.equal(
|
|
172
|
+
realpathSync(process.cwd()),
|
|
173
|
+
realpathSync(previousCwd),
|
|
174
|
+
"throws before cleanup but restores to the caller's previous cwd",
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// gsd-pi — Regression tests for milestone branch/head reconciliation before merge.
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterEach, describe, test } from "node:test";
|
|
9
|
+
|
|
10
|
+
import { GSDError } from "../errors.js";
|
|
11
|
+
import { reconcileMilestoneBranchHead } from "../auto-worktree-merge-branch-head.js";
|
|
12
|
+
|
|
13
|
+
const cleanupPaths: string[] = [];
|
|
14
|
+
const initialCwd = process.cwd();
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
process.chdir(initialCwd);
|
|
18
|
+
for (const path of cleanupPaths.splice(0)) {
|
|
19
|
+
rmSync(path, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function git(cwd: string, args: string[]): string {
|
|
24
|
+
return execFileSync("git", args, {
|
|
25
|
+
cwd,
|
|
26
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
27
|
+
encoding: "utf-8",
|
|
28
|
+
}).trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function commitFile(cwd: string, file: string, content: string, message: string): string {
|
|
32
|
+
writeFileSync(join(cwd, file), content);
|
|
33
|
+
git(cwd, ["add", file]);
|
|
34
|
+
git(cwd, ["commit", "-m", message]);
|
|
35
|
+
return git(cwd, ["rev-parse", "HEAD"]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createRepoWithMilestoneWorktree(): { repo: string; worktree: string; branch: string } {
|
|
39
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-branch-head-"));
|
|
40
|
+
cleanupPaths.push(repo);
|
|
41
|
+
git(repo, ["init", "-b", "main"]);
|
|
42
|
+
git(repo, ["config", "user.email", "test@example.invalid"]);
|
|
43
|
+
git(repo, ["config", "user.name", "Test"]);
|
|
44
|
+
commitFile(repo, "README.md", "base\n", "initial");
|
|
45
|
+
|
|
46
|
+
const branch = "milestone/M001";
|
|
47
|
+
git(repo, ["branch", branch]);
|
|
48
|
+
const worktree = join(repo, ".gsd-worktrees", "M001");
|
|
49
|
+
git(repo, ["worktree", "add", worktree, branch]);
|
|
50
|
+
git(worktree, ["config", "user.email", "test@example.invalid"]);
|
|
51
|
+
git(worktree, ["config", "user.name", "Test"]);
|
|
52
|
+
return { repo, worktree, branch };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("reconcileMilestoneBranchHead", () => {
|
|
56
|
+
test("fast-forwards stale milestone branch ref to detached worktree HEAD", () => {
|
|
57
|
+
const { repo, worktree, branch } = createRepoWithMilestoneWorktree();
|
|
58
|
+
git(worktree, ["checkout", "--detach"]);
|
|
59
|
+
const detachedHead = commitFile(worktree, "feature.txt", "worktree-only\n", "worktree detached commit");
|
|
60
|
+
|
|
61
|
+
const result = reconcileMilestoneBranchHead({
|
|
62
|
+
projectRoot: repo,
|
|
63
|
+
worktreeCwd: worktree,
|
|
64
|
+
milestoneBranch: branch,
|
|
65
|
+
previousCwd: worktree,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
assert.deepEqual(result, {
|
|
69
|
+
checked: true,
|
|
70
|
+
updated: true,
|
|
71
|
+
reason: "fast-forwarded",
|
|
72
|
+
});
|
|
73
|
+
assert.equal(git(repo, ["rev-parse", branch]), detachedHead);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("throws and restores cwd when worktree HEAD diverges from milestone branch", () => {
|
|
77
|
+
const { repo, worktree, branch } = createRepoWithMilestoneWorktree();
|
|
78
|
+
const baseHead = git(repo, ["rev-parse", branch]);
|
|
79
|
+
git(worktree, ["checkout", "--detach", baseHead]);
|
|
80
|
+
const detachedHead = commitFile(worktree, "worktree.txt", "detached\n", "detached side");
|
|
81
|
+
|
|
82
|
+
git(repo, ["checkout", branch]);
|
|
83
|
+
const branchHead = commitFile(repo, "branch.txt", "branch\n", "branch side");
|
|
84
|
+
git(repo, ["checkout", "main"]);
|
|
85
|
+
process.chdir(repo);
|
|
86
|
+
|
|
87
|
+
assert.throws(
|
|
88
|
+
() => reconcileMilestoneBranchHead({
|
|
89
|
+
projectRoot: repo,
|
|
90
|
+
worktreeCwd: worktree,
|
|
91
|
+
milestoneBranch: branch,
|
|
92
|
+
previousCwd: worktree,
|
|
93
|
+
}),
|
|
94
|
+
(err: unknown) => err instanceof GSDError && /diverged/.test(err.message),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
assert.equal(git(repo, ["rev-parse", branch]), branchHead, "diverged branch ref must not be moved");
|
|
98
|
+
assert.equal(git(worktree, ["rev-parse", "HEAD"]), detachedHead, "detached worktree commit remains for manual recovery");
|
|
99
|
+
assert.equal(realpathSync(process.cwd()), realpathSync(worktree), "divergence restores cwd to previous worktree");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("skips reconciliation when cwd is already the project root", () => {
|
|
103
|
+
const { repo, branch } = createRepoWithMilestoneWorktree();
|
|
104
|
+
|
|
105
|
+
const result = reconcileMilestoneBranchHead({
|
|
106
|
+
projectRoot: repo,
|
|
107
|
+
worktreeCwd: repo,
|
|
108
|
+
milestoneBranch: branch,
|
|
109
|
+
previousCwd: repo,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
assert.deepEqual(result, {
|
|
113
|
+
checked: false,
|
|
114
|
+
updated: false,
|
|
115
|
+
reason: "not-worktree",
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|