@opengsd/gsd-pi 1.4.0 → 1.5.0
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 +11 -11
- package/dist/onboarding.js +11 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +20 -25
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
- package/dist/resources/extensions/cursor-cli/index.js +16 -0
- package/dist/resources/extensions/cursor-cli/models.js +63 -0
- package/dist/resources/extensions/cursor-cli/package.json +11 -0
- package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
- package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
- package/dist/resources/extensions/gsd/activity-log.js +4 -1
- package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
- package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
- package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
- package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
- package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
- package/dist/resources/extensions/gsd/auto-start.js +4 -3
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
- package/dist/resources/extensions/gsd/auto.js +23 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
- package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
- package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
- package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
- package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
- package/dist/resources/extensions/gsd/db/queries.js +39 -0
- package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
- package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
- package/dist/resources/extensions/gsd/doctor.js +93 -3
- package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
- package/dist/resources/extensions/gsd/git-service.js +14 -18
- package/dist/resources/extensions/gsd/gsd-db.js +52 -0
- package/dist/resources/extensions/gsd/guidance.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +40 -2
- package/dist/resources/extensions/gsd/key-manager.js +1 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
- package/dist/resources/extensions/gsd/migrate-external.js +24 -7
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
- package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
- package/dist/resources/extensions/gsd/preferences-models.js +6 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
- package/dist/resources/extensions/gsd/preferences.js +29 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
- package/dist/resources/extensions/gsd/repo-identity.js +16 -0
- package/dist/resources/extensions/gsd/repository-registry.js +6 -1
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
- package/dist/resources/extensions/gsd/safe-fs.js +23 -2
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
- package/dist/resources/extensions/gsd/unit-registry.js +27 -2
- package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
- package/dist/resources/extensions/gsd/workspace.js +26 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
- 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 +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- 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/api/update/route.js +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 +7 -7
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- 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/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +8 -1
- package/integrations/hermes/CONTEXT.md +47 -0
- package/integrations/hermes/README.md +21 -0
- package/integrations/hermes/__init__.py +31 -0
- package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
- package/integrations/hermes/docs/setup.md +4 -0
- package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
- package/integrations/hermes/open_gsd_hermes/config.py +2 -0
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
- package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
- package/integrations/hermes/plugin.yaml +4 -0
- package/integrations/hermes/tests/test_config.py +6 -0
- package/integrations/hermes/tests/test_directory_loader.py +37 -0
- package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
- package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
- package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
- package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
- package/integrations/hermes/tests/test_milestone_client.py +447 -0
- package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
- package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
- package/package.json +8 -8
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
- package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-runtime.js +25 -1
- package/packages/daemon/dist/cloud-runtime.js.map +1 -1
- package/packages/daemon/dist/cloud-runtime.test.js +51 -0
- package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
- package/packages/daemon/dist/message-batcher.d.ts +7 -0
- package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
- package/packages/daemon/dist/message-batcher.js +13 -0
- package/packages/daemon/dist/message-batcher.js.map +1 -1
- package/packages/daemon/dist/message-batcher.test.js +48 -0
- package/packages/daemon/dist/message-batcher.test.js.map +1 -1
- package/packages/daemon/dist/session-manager.js +1 -1
- package/packages/daemon/dist/session-manager.js.map +1 -1
- package/packages/daemon/package.json +5 -5
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/server.d.ts +18 -5
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +46 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +1 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +3 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- 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/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
- package/packages/pi-coding-agent/dist/core/session-manager.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/dist/rpc-client.js +1 -1
- package/packages/rpc-client/dist/rpc-client.js.map +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/link-workspace-packages.cjs +14 -4
- package/src/resources/GSD-WORKFLOW.md +20 -25
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
- package/src/resources/extensions/cursor-cli/index.ts +18 -0
- package/src/resources/extensions/cursor-cli/models.ts +65 -0
- package/src/resources/extensions/cursor-cli/package.json +11 -0
- package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
- package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
- package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
- package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
- package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
- package/src/resources/extensions/gsd/activity-log.ts +4 -1
- package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
- package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
- package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
- package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
- package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
- package/src/resources/extensions/gsd/auto-start.ts +4 -3
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
- package/src/resources/extensions/gsd/auto.ts +22 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
- package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
- package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
- package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
- package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
- package/src/resources/extensions/gsd/db/queries.ts +40 -0
- package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
- package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
- package/src/resources/extensions/gsd/doctor-types.ts +5 -1
- package/src/resources/extensions/gsd/doctor.ts +95 -3
- package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
- package/src/resources/extensions/gsd/git-service.ts +14 -17
- package/src/resources/extensions/gsd/gsd-db.ts +72 -0
- package/src/resources/extensions/gsd/guidance.ts +2 -0
- package/src/resources/extensions/gsd/guided-flow.ts +45 -2
- package/src/resources/extensions/gsd/key-manager.ts +1 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
- package/src/resources/extensions/gsd/migrate-external.ts +23 -6
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
- package/src/resources/extensions/gsd/model-router.ts +4 -5
- package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
- package/src/resources/extensions/gsd/preferences-models.ts +6 -3
- package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
- package/src/resources/extensions/gsd/preferences.ts +38 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
- package/src/resources/extensions/gsd/repo-identity.ts +17 -0
- package/src/resources/extensions/gsd/repository-registry.ts +7 -1
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
- package/src/resources/extensions/gsd/safe-fs.ts +22 -2
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
- package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
- package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
- package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
- package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
- package/src/resources/extensions/gsd/unit-registry.ts +34 -2
- package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
- package/src/resources/extensions/gsd/workspace.ts +42 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
- package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
//
|
|
21
21
|
// Not yet declared here (remaining ADR-033 steps): the manifest data
|
|
22
22
|
// (`UNIT_MANIFESTS` stays in unit-context-manifest.ts, already type-enforced
|
|
23
|
-
// against the registry's `UnitType`)
|
|
24
|
-
//
|
|
23
|
+
// against the registry's `UnitType`). Prompt-template association is declared
|
|
24
|
+
// for direct one-template units; conditional/composite prompt selection stays
|
|
25
|
+
// with the prompt builders until it has a richer registry shape.
|
|
25
26
|
import { BROWSER_CONTRACT_TOOL_NAMES } from "../shared/browser-contract.js";
|
|
26
27
|
// ─── Shared tool-name constants (used by registry rows) ──────────────────
|
|
27
28
|
export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
|
|
@@ -50,6 +51,7 @@ export const UNIT_REGISTRY = {
|
|
|
50
51
|
kind: "primary",
|
|
51
52
|
scopeClass: "standard",
|
|
52
53
|
phaseChain: ["research"],
|
|
54
|
+
promptTemplate: "research-milestone",
|
|
53
55
|
toolContract: {
|
|
54
56
|
allowedGsdTools: [
|
|
55
57
|
"gsd_summary_save",
|
|
@@ -65,6 +67,7 @@ export const UNIT_REGISTRY = {
|
|
|
65
67
|
kind: "primary",
|
|
66
68
|
scopeClass: "standard",
|
|
67
69
|
phaseChain: ["planning"],
|
|
70
|
+
promptTemplate: "plan-milestone",
|
|
68
71
|
toolContract: {
|
|
69
72
|
allowedGsdTools: [
|
|
70
73
|
"gsd_milestone_status",
|
|
@@ -113,6 +116,7 @@ export const UNIT_REGISTRY = {
|
|
|
113
116
|
kind: "primary",
|
|
114
117
|
scopeClass: "section-close",
|
|
115
118
|
phaseChain: ["validation", "planning"],
|
|
119
|
+
promptTemplate: "validate-milestone",
|
|
116
120
|
toolContract: {
|
|
117
121
|
allowedGsdTools: [
|
|
118
122
|
"gsd_milestone_status",
|
|
@@ -130,6 +134,7 @@ export const UNIT_REGISTRY = {
|
|
|
130
134
|
kind: "primary",
|
|
131
135
|
scopeClass: "standard",
|
|
132
136
|
phaseChain: ["completion"],
|
|
137
|
+
promptTemplate: "complete-milestone",
|
|
133
138
|
toolContract: {
|
|
134
139
|
allowedGsdTools: [
|
|
135
140
|
"gsd_milestone_status",
|
|
@@ -153,6 +158,7 @@ export const UNIT_REGISTRY = {
|
|
|
153
158
|
kind: "primary",
|
|
154
159
|
scopeClass: "standard",
|
|
155
160
|
phaseChain: ["research"],
|
|
161
|
+
promptTemplate: "research-slice",
|
|
156
162
|
toolContract: {
|
|
157
163
|
allowedGsdTools: [
|
|
158
164
|
"gsd_milestone_status",
|
|
@@ -169,6 +175,7 @@ export const UNIT_REGISTRY = {
|
|
|
169
175
|
kind: "primary",
|
|
170
176
|
scopeClass: "standard",
|
|
171
177
|
phaseChain: ["planning"],
|
|
178
|
+
promptTemplate: "plan-slice",
|
|
172
179
|
toolContract: {
|
|
173
180
|
allowedGsdTools: [
|
|
174
181
|
"gsd_milestone_status",
|
|
@@ -187,6 +194,7 @@ export const UNIT_REGISTRY = {
|
|
|
187
194
|
kind: "primary",
|
|
188
195
|
scopeClass: "standard",
|
|
189
196
|
phaseChain: ["planning"],
|
|
197
|
+
promptTemplate: "refine-slice",
|
|
190
198
|
toolContract: {
|
|
191
199
|
allowedGsdTools: [
|
|
192
200
|
"gsd_milestone_status",
|
|
@@ -203,6 +211,7 @@ export const UNIT_REGISTRY = {
|
|
|
203
211
|
kind: "primary",
|
|
204
212
|
scopeClass: "standard",
|
|
205
213
|
phaseChain: ["planning"],
|
|
214
|
+
promptTemplate: "replan-slice",
|
|
206
215
|
toolContract: {
|
|
207
216
|
allowedGsdTools: ["gsd_replan_slice", "gsd_decision_save"],
|
|
208
217
|
requiredWorkflowTools: ["gsd_replan_slice"],
|
|
@@ -212,6 +221,7 @@ export const UNIT_REGISTRY = {
|
|
|
212
221
|
kind: "primary",
|
|
213
222
|
scopeClass: "section-close",
|
|
214
223
|
phaseChain: ["completion"],
|
|
224
|
+
promptTemplate: "complete-slice",
|
|
215
225
|
toolContract: {
|
|
216
226
|
allowedGsdTools: [
|
|
217
227
|
"gsd_milestone_status",
|
|
@@ -246,6 +256,7 @@ export const UNIT_REGISTRY = {
|
|
|
246
256
|
kind: "primary",
|
|
247
257
|
scopeClass: "standard",
|
|
248
258
|
phaseChain: ["validation", "planning"],
|
|
259
|
+
promptTemplate: "reassess-roadmap",
|
|
249
260
|
toolContract: {
|
|
250
261
|
allowedGsdTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
|
|
251
262
|
requiredWorkflowTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
|
|
@@ -255,6 +266,7 @@ export const UNIT_REGISTRY = {
|
|
|
255
266
|
kind: "primary",
|
|
256
267
|
scopeClass: "execute-task",
|
|
257
268
|
phaseChain: ["execution"],
|
|
269
|
+
promptTemplate: "execute-task",
|
|
258
270
|
toolContract: {
|
|
259
271
|
allowedGsdTools: [
|
|
260
272
|
"gsd_task_complete",
|
|
@@ -299,6 +311,7 @@ export const UNIT_REGISTRY = {
|
|
|
299
311
|
kind: "primary",
|
|
300
312
|
scopeClass: "execute-task",
|
|
301
313
|
phaseChain: ["execution"],
|
|
314
|
+
promptTemplate: "reactive-execute",
|
|
302
315
|
toolContract: {
|
|
303
316
|
allowedGsdTools: [
|
|
304
317
|
"gsd_milestone_status",
|
|
@@ -314,6 +327,7 @@ export const UNIT_REGISTRY = {
|
|
|
314
327
|
kind: "primary",
|
|
315
328
|
scopeClass: "standard",
|
|
316
329
|
phaseChain: ["uat", "completion"],
|
|
330
|
+
promptTemplate: "run-uat",
|
|
317
331
|
toolContract: {
|
|
318
332
|
allowedGsdTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent"],
|
|
319
333
|
requiredWorkflowTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES],
|
|
@@ -328,6 +342,7 @@ export const UNIT_REGISTRY = {
|
|
|
328
342
|
kind: "primary",
|
|
329
343
|
scopeClass: "standard",
|
|
330
344
|
phaseChain: ["validation", "planning"],
|
|
345
|
+
promptTemplate: "gate-evaluate",
|
|
331
346
|
toolContract: {
|
|
332
347
|
allowedGsdTools: ["gsd_save_gate_result"],
|
|
333
348
|
requiredWorkflowTools: ["gsd_save_gate_result"],
|
|
@@ -337,6 +352,7 @@ export const UNIT_REGISTRY = {
|
|
|
337
352
|
kind: "primary",
|
|
338
353
|
scopeClass: "standard",
|
|
339
354
|
phaseChain: ["validation", "planning"],
|
|
355
|
+
promptTemplate: "rewrite-docs",
|
|
340
356
|
toolContract: {
|
|
341
357
|
allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
|
|
342
358
|
requiredWorkflowTools: [],
|
|
@@ -361,6 +377,7 @@ export const UNIT_REGISTRY = {
|
|
|
361
377
|
kind: "primary",
|
|
362
378
|
scopeClass: "standard",
|
|
363
379
|
phaseChain: ["discuss", "planning"],
|
|
380
|
+
promptTemplate: "guided-workflow-preferences",
|
|
364
381
|
toolContract: {
|
|
365
382
|
allowedGsdTools: ["gsd_summary_save"],
|
|
366
383
|
requiredWorkflowTools: [],
|
|
@@ -370,6 +387,7 @@ export const UNIT_REGISTRY = {
|
|
|
370
387
|
kind: "primary",
|
|
371
388
|
scopeClass: "standard",
|
|
372
389
|
phaseChain: ["discuss", "planning"],
|
|
390
|
+
promptTemplate: "guided-discuss-project",
|
|
373
391
|
toolContract: {
|
|
374
392
|
allowedGsdTools: ["gsd_summary_save", "gsd_decision_save", "gsd_requirement_save"],
|
|
375
393
|
requiredWorkflowTools: ["ask_user_questions", "gsd_summary_save"],
|
|
@@ -379,6 +397,7 @@ export const UNIT_REGISTRY = {
|
|
|
379
397
|
kind: "primary",
|
|
380
398
|
scopeClass: "standard",
|
|
381
399
|
phaseChain: ["discuss", "planning"],
|
|
400
|
+
promptTemplate: "guided-discuss-requirements",
|
|
382
401
|
toolContract: {
|
|
383
402
|
allowedGsdTools: ["gsd_requirement_save", "gsd_summary_save"],
|
|
384
403
|
requiredWorkflowTools: ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"],
|
|
@@ -388,6 +407,7 @@ export const UNIT_REGISTRY = {
|
|
|
388
407
|
kind: "primary",
|
|
389
408
|
scopeClass: "standard",
|
|
390
409
|
phaseChain: ["discuss", "planning"],
|
|
410
|
+
promptTemplate: "guided-research-decision",
|
|
391
411
|
toolContract: {
|
|
392
412
|
allowedGsdTools: ["gsd_summary_save"],
|
|
393
413
|
requiredWorkflowTools: ["ask_user_questions"],
|
|
@@ -400,6 +420,7 @@ export const UNIT_REGISTRY = {
|
|
|
400
420
|
kind: "primary",
|
|
401
421
|
scopeClass: "standard",
|
|
402
422
|
phaseChain: ["research"],
|
|
423
|
+
promptTemplate: "guided-research-project",
|
|
403
424
|
toolContract: {
|
|
404
425
|
allowedGsdTools: [],
|
|
405
426
|
requiredWorkflowTools: [],
|
|
@@ -419,3 +440,7 @@ export function getUnitDescriptor(unitType) {
|
|
|
419
440
|
export function getUnitPhaseChain(unitType) {
|
|
420
441
|
return getUnitDescriptor(unitType)?.phaseChain ?? null;
|
|
421
442
|
}
|
|
443
|
+
/** Prompt template id for units with a direct one-template builder. */
|
|
444
|
+
export function getUnitPromptTemplate(unitType) {
|
|
445
|
+
return getUnitDescriptor(unitType)?.promptTemplate;
|
|
446
|
+
}
|
|
@@ -93,6 +93,8 @@ export function writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, up
|
|
|
93
93
|
const path = runtimePath(basePath, unitType, unitId);
|
|
94
94
|
return withRecordLock(path, () => {
|
|
95
95
|
const prev = readUnitRuntimeRecord(basePath, unitType, unitId);
|
|
96
|
+
const sameRun = prev?.startedAt === startedAt;
|
|
97
|
+
const updatesHarnessAbort = Object.prototype.hasOwnProperty.call(updates, "harnessAbort");
|
|
96
98
|
const next = {
|
|
97
99
|
version: 1,
|
|
98
100
|
unitType,
|
|
@@ -109,11 +111,24 @@ export function writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, up
|
|
|
109
111
|
recovery: updates.recovery ?? prev?.recovery,
|
|
110
112
|
recoveryAttempts: updates.recoveryAttempts ?? prev?.recoveryAttempts ?? 0,
|
|
111
113
|
lastRecoveryReason: updates.lastRecoveryReason ?? prev?.lastRecoveryReason,
|
|
114
|
+
harnessAbort: updatesHarnessAbort
|
|
115
|
+
? updates.harnessAbort
|
|
116
|
+
: (sameRun ? prev?.harnessAbort : undefined),
|
|
112
117
|
};
|
|
113
118
|
atomicWriteSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
114
119
|
return next;
|
|
115
120
|
});
|
|
116
121
|
}
|
|
122
|
+
export function recordUnitHarnessAbort(basePath, unitType, unitId, startedAt, abort) {
|
|
123
|
+
return writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, {
|
|
124
|
+
harnessAbort: {
|
|
125
|
+
...abort,
|
|
126
|
+
recordedAt: abort.recordedAt ?? Date.now(),
|
|
127
|
+
},
|
|
128
|
+
lastProgressAt: Date.now(),
|
|
129
|
+
lastProgressKind: `harness-abort:${abort.kind}`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
117
132
|
export function readUnitRuntimeRecord(basePath, unitType, unitId) {
|
|
118
133
|
const path = runtimePath(basePath, unitType, unitId);
|
|
119
134
|
if (!existsSync(path))
|
|
@@ -125,6 +140,12 @@ export function readUnitRuntimeRecord(basePath, unitType, unitId) {
|
|
|
125
140
|
return null;
|
|
126
141
|
}
|
|
127
142
|
}
|
|
143
|
+
export function readUnitHarnessAbort(basePath, unitType, unitId, startedAt) {
|
|
144
|
+
const record = readUnitRuntimeRecord(basePath, unitType, unitId);
|
|
145
|
+
if (!record || record.startedAt !== startedAt)
|
|
146
|
+
return null;
|
|
147
|
+
return record.harnessAbort ?? null;
|
|
148
|
+
}
|
|
128
149
|
export function clearUnitRuntimeRecord(basePath, unitType, unitId) {
|
|
129
150
|
const path = runtimePath(basePath, unitType, unitId);
|
|
130
151
|
if (existsSync(path))
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
// gsd-pi + Workspace handle: single source of truth for path resolution per milestone
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
import { resolveGsdPathContract, normalizeRealPath } from "./paths.js";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { buildMilestoneFileName, canonicalPhaseDirName, isLegacyMilestonesLayout, milestonesDir, resolveGsdPathContract, resolveMilestoneFile, resolveMilestonePath, normalizeRealPath, } from "./paths.js";
|
|
4
4
|
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "./worktree-root.js";
|
|
5
5
|
function tryRealpath(p) {
|
|
6
6
|
return normalizeRealPath(p);
|
|
7
7
|
}
|
|
8
|
+
function isLegacyMilestoneDirectory(dir) {
|
|
9
|
+
const parent = dirname(dir);
|
|
10
|
+
return parent.endsWith("/milestones") || parent.endsWith("\\milestones");
|
|
11
|
+
}
|
|
12
|
+
function scopeMilestoneDir(basePath, milestoneId) {
|
|
13
|
+
return resolveMilestonePath(basePath, milestoneId) ?? join(milestonesDir(basePath), isLegacyMilestonesLayout(basePath) ? milestoneId : canonicalPhaseDirName(milestoneId));
|
|
14
|
+
}
|
|
15
|
+
function scopeMilestoneFile(basePath, milestoneId, suffix) {
|
|
16
|
+
const existing = resolveMilestoneFile(basePath, milestoneId, suffix);
|
|
17
|
+
if (existing)
|
|
18
|
+
return existing;
|
|
19
|
+
const dir = scopeMilestoneDir(basePath, milestoneId);
|
|
20
|
+
const filename = isLegacyMilestoneDirectory(dir)
|
|
21
|
+
? `${milestoneId}-${suffix}.md`
|
|
22
|
+
: buildMilestoneFileName(milestoneId, suffix);
|
|
23
|
+
return join(dir, filename);
|
|
24
|
+
}
|
|
8
25
|
/**
|
|
9
26
|
* Create an immutable GsdWorkspace handle from a raw base path.
|
|
10
27
|
* Resolves both the project root and (when applicable) the worktree root,
|
|
@@ -39,21 +56,22 @@ export function createWorkspace(rawBasePath) {
|
|
|
39
56
|
* Bind a milestoneId to a workspace, producing an immutable MilestoneScope
|
|
40
57
|
* with path-returning closures for DB-authoritative project state.
|
|
41
58
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
59
|
+
* DB paths route to contract.projectGsd. Milestone artifact paths resolve from
|
|
60
|
+
* the pinned project root with the layout-aware resolvers, so flat-phase and
|
|
61
|
+
* legacy projects use the same scope surface.
|
|
45
62
|
*/
|
|
46
63
|
export function scopeMilestone(workspace, milestoneId) {
|
|
47
64
|
const { contract } = workspace;
|
|
48
65
|
const gsd = contract.projectGsd;
|
|
66
|
+
const basePath = workspace.projectRoot;
|
|
49
67
|
const scope = Object.freeze({
|
|
50
68
|
workspace,
|
|
51
69
|
milestoneId,
|
|
52
|
-
contextFile: () =>
|
|
53
|
-
roadmapFile: () =>
|
|
70
|
+
contextFile: () => scopeMilestoneFile(basePath, milestoneId, "CONTEXT"),
|
|
71
|
+
roadmapFile: () => scopeMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
54
72
|
stateFile: () => join(gsd, "STATE.md"),
|
|
55
73
|
dbPath: () => contract.projectDb,
|
|
56
|
-
milestoneDir: () =>
|
|
74
|
+
milestoneDir: () => scopeMilestoneDir(basePath, milestoneId),
|
|
57
75
|
metaJson: () => join(gsd, `${milestoneId}-META.json`),
|
|
58
76
|
});
|
|
59
77
|
return scope;
|
|
@@ -102,6 +102,51 @@ class UserNotifiedError extends Error {
|
|
|
102
102
|
function isSamePathPhysical(a, b) {
|
|
103
103
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
104
104
|
}
|
|
105
|
+
function preflightBlockedMergeReason(blockedReason) {
|
|
106
|
+
if (blockedReason?.startsWith("unmerged-conflicts")) {
|
|
107
|
+
return "preflight-unmerged-conflicts";
|
|
108
|
+
}
|
|
109
|
+
return "preflight-dirty-overlap";
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Run a milestone merge behind the Worktree Lifecycle seam with the root-clean
|
|
113
|
+
* stash guard that protects user changes around merge.
|
|
114
|
+
*
|
|
115
|
+
* The helper owns the invariant ordering (preflight -> merge callback ->
|
|
116
|
+
* postflight on every attempted merge path) and returns the same typed result
|
|
117
|
+
* shape as `exitMilestone`. Auto closeout remains responsible for translating
|
|
118
|
+
* that result into loop policy (pause vs. stop, visible transcript
|
|
119
|
+
* preservation, projection rebuild).
|
|
120
|
+
*/
|
|
121
|
+
function runGuardedMilestoneMerge(request) {
|
|
122
|
+
const { merge, guard, milestoneId, notify } = request;
|
|
123
|
+
const preflight = guard.preflightCleanRoot(guard.projectRoot, milestoneId, notify);
|
|
124
|
+
if (preflight.blocked) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
reason: preflightBlockedMergeReason(preflight.blockedReason),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
let mergeError = null;
|
|
131
|
+
const exitResult = merge();
|
|
132
|
+
if (!exitResult.ok) {
|
|
133
|
+
mergeError = exitResult.cause ?? new Error(`exit ${exitResult.reason}`);
|
|
134
|
+
}
|
|
135
|
+
let postflight;
|
|
136
|
+
if (preflight.stashPushed) {
|
|
137
|
+
postflight = guard.postflightPopStash(guard.projectRoot, milestoneId, preflight.stashMarker, notify);
|
|
138
|
+
}
|
|
139
|
+
if (mergeError instanceof MergeConflictError) {
|
|
140
|
+
return { ok: false, reason: "merge-conflict", cause: mergeError, postflight };
|
|
141
|
+
}
|
|
142
|
+
if (mergeError) {
|
|
143
|
+
return { ok: false, reason: "merge-failed", cause: mergeError, postflight };
|
|
144
|
+
}
|
|
145
|
+
if (postflight?.needsManualRecovery) {
|
|
146
|
+
return { ok: false, reason: "postflight-stash-restore-failed", postflight };
|
|
147
|
+
}
|
|
148
|
+
return exitResult;
|
|
149
|
+
}
|
|
105
150
|
// ─── Validation ──────────────────────────────────────────────────────────
|
|
106
151
|
function isValidMilestoneId(milestoneId) {
|
|
107
152
|
return !/[\/\\]|\.\./.test(milestoneId);
|
|
@@ -645,7 +690,7 @@ function _mergeWorktreeModeImpl(deps, mctx) {
|
|
|
645
690
|
pushed: false,
|
|
646
691
|
};
|
|
647
692
|
}
|
|
648
|
-
const mergeResult = deps.
|
|
693
|
+
const mergeResult = deps.mergeMilestone(originalBasePath, milestoneId, roadmapResolution.content);
|
|
649
694
|
// #2945 Bug 3: mergeMilestoneToMain performs best-effort worktree
|
|
650
695
|
// cleanup internally (step 12), but it can silently fail on Windows
|
|
651
696
|
// or when the worktree directory is locked. Perform a secondary
|
|
@@ -780,7 +825,7 @@ function _mergeBranchModeImpl(deps, mctx) {
|
|
|
780
825
|
pushed: false,
|
|
781
826
|
};
|
|
782
827
|
}
|
|
783
|
-
const mergeResult = deps.
|
|
828
|
+
const mergeResult = deps.mergeMilestone(worktreeBasePath, milestoneId, roadmapResolution.content);
|
|
784
829
|
if (mergeResult.codeFilesChanged) {
|
|
785
830
|
notify(`Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`, "info");
|
|
786
831
|
}
|
|
@@ -983,14 +1028,31 @@ export class WorktreeLifecycle {
|
|
|
983
1028
|
* With `opts.merge === false`, runs auto-commit and teardown without
|
|
984
1029
|
* merging to main.
|
|
985
1030
|
*
|
|
1031
|
+
* When `opts.guardedMerge` is present with `opts.merge === true`, the
|
|
1032
|
+
* lifecycle verb also owns the root-clean guard around the merge: preflight
|
|
1033
|
+
* runs before the inner merge, postflight stash restore runs after every
|
|
1034
|
+
* attempted merge path, and guard failures are returned as typed
|
|
1035
|
+
* `ExitResult` reasons.
|
|
1036
|
+
*
|
|
986
1037
|
* Returns a typed `ExitResult`. `MergeConflictError` is surfaced as
|
|
987
1038
|
* `{ ok: false, reason: "merge-conflict", cause }` instead of thrown,
|
|
988
|
-
* giving callers a typed branch for the expected failure path.
|
|
989
|
-
*
|
|
990
|
-
*
|
|
1039
|
+
* giving callers a typed branch for the expected failure path. Guarded merge
|
|
1040
|
+
* failures surface as `preflight-dirty-overlap`,
|
|
1041
|
+
* `preflight-unmerged-conflicts`, `merge-failed`, or
|
|
1042
|
+
* `postflight-stash-restore-failed`. Unexpected failures (filesystem, git
|
|
1043
|
+
* permissions, etc.) are wrapped as
|
|
1044
|
+
* `{ ok: false, reason: "teardown-failed", cause }` so callers always
|
|
991
1045
|
* receive a discriminated union — no exceptions for any expected outcome.
|
|
992
1046
|
*/
|
|
993
1047
|
exitMilestone(milestoneId, opts, ctx) {
|
|
1048
|
+
if (opts.merge && opts.guardedMerge) {
|
|
1049
|
+
return runGuardedMilestoneMerge({
|
|
1050
|
+
merge: () => this.exitMilestone(milestoneId, { merge: true }, ctx),
|
|
1051
|
+
guard: opts.guardedMerge,
|
|
1052
|
+
milestoneId,
|
|
1053
|
+
notify: ctx.notify,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
994
1056
|
if (opts.merge) {
|
|
995
1057
|
try {
|
|
996
1058
|
const merged = this._mergeAndExit(milestoneId, ctx);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* 3. merge() — LLM-guided reconciliation of .gsd/ artifacts back to main
|
|
18
18
|
* 4. remove() — git worktree remove + branch cleanup
|
|
19
19
|
*/
|
|
20
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, unlinkSync } from "node:fs";
|
|
20
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { execFileSync } from "node:child_process";
|
|
22
22
|
import { join, resolve, sep } from "node:path";
|
|
23
23
|
import { GSDError, GSD_PARSE_ERROR, GSD_STALE_STATE, GSD_LOCK_HELD, GSD_GIT_ERROR, GSD_MERGE_CONFLICT } from "./errors.js";
|
|
@@ -163,6 +163,76 @@ function isRegisteredGitWorktreeAtPath(basePath, wtPath) {
|
|
|
163
163
|
return false;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
+
function inspectUncommittedWorktreeState(wtPath) {
|
|
167
|
+
try {
|
|
168
|
+
const status = execFileSync("git", ["status", "--porcelain=v1", "--untracked-files=all"], { cwd: wtPath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trimEnd();
|
|
169
|
+
return { dirty: status.length > 0, status };
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
logWarning("worktree", `dirty worktree inspection failed for ${wtPath}: ${message}`);
|
|
174
|
+
// Fail closed: unknown state must quarantine rather than force-remove.
|
|
175
|
+
return { dirty: true, status: `git status failed: ${message}` };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function timestampForPath() {
|
|
179
|
+
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
180
|
+
}
|
|
181
|
+
function uniqueQuarantinePath(basePath, name) {
|
|
182
|
+
const root = join(basePath, ".gsd", "quarantine", "worktrees");
|
|
183
|
+
const baseName = `${name}-${timestampForPath()}`;
|
|
184
|
+
let candidate = join(root, baseName);
|
|
185
|
+
let suffix = 2;
|
|
186
|
+
while (existsSync(candidate)) {
|
|
187
|
+
candidate = join(root, `${baseName}-${suffix}`);
|
|
188
|
+
suffix++;
|
|
189
|
+
}
|
|
190
|
+
return candidate;
|
|
191
|
+
}
|
|
192
|
+
function quarantineDirtyWorktree(basePath, name, branch, wtPath, status) {
|
|
193
|
+
const quarantinePath = uniqueQuarantinePath(basePath, name);
|
|
194
|
+
mkdirSync(join(basePath, ".gsd", "quarantine", "worktrees"), { recursive: true });
|
|
195
|
+
let moved = false;
|
|
196
|
+
try {
|
|
197
|
+
renameSync(wtPath, quarantinePath);
|
|
198
|
+
moved = true;
|
|
199
|
+
}
|
|
200
|
+
catch (renameErr) {
|
|
201
|
+
logWarning("worktree", `dirty worktree rename quarantine failed for ${name}: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}; trying copy snapshot`);
|
|
202
|
+
try {
|
|
203
|
+
cpSync(wtPath, quarantinePath, { recursive: true, force: false, errorOnExist: true });
|
|
204
|
+
}
|
|
205
|
+
catch (copyErr) {
|
|
206
|
+
logWarning("reconcile", `Dirty worktree quarantine failed; preserving original worktree ${wtPath}: ${copyErr instanceof Error ? copyErr.message : String(copyErr)}`, { worktree: name, path: wtPath });
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const gitFile = join(quarantinePath, ".git");
|
|
212
|
+
if (existsSync(gitFile) && lstatSync(gitFile).isFile()) {
|
|
213
|
+
unlinkSync(gitFile);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
logWarning("worktree", `failed to remove quarantined worktree git pointer: ${err instanceof Error ? err.message : String(err)}`);
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
writeFileSync(join(quarantinePath, ".gsd-quarantine.json"), JSON.stringify({
|
|
221
|
+
kind: "dirty-worktree",
|
|
222
|
+
worktree: name,
|
|
223
|
+
branch,
|
|
224
|
+
originalPath: wtPath,
|
|
225
|
+
moved,
|
|
226
|
+
quarantinedAt: new Date().toISOString(),
|
|
227
|
+
status,
|
|
228
|
+
}, null, 2) + "\n", "utf-8");
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
logWarning("worktree", `failed to write worktree quarantine metadata: ${err instanceof Error ? err.message : String(err)}`);
|
|
232
|
+
}
|
|
233
|
+
logWarning("reconcile", `Quarantined dirty worktree ${name} at ${quarantinePath} before removal. Branch ${branch} was preserved.`, { worktree: name, path: quarantinePath, branch });
|
|
234
|
+
return quarantinePath;
|
|
235
|
+
}
|
|
166
236
|
/** True when `wtPath` has a git worktree checkout marker (`.git` file with gitdir pointer). */
|
|
167
237
|
export function isLiveGitWorktreeCheckout(wtPath) {
|
|
168
238
|
const gitPath = join(wtPath, ".git");
|
|
@@ -610,6 +680,7 @@ export function removeWorktree(basePath, name, opts = {}) {
|
|
|
610
680
|
let wtPath = worktreePath(basePath, name);
|
|
611
681
|
const branch = opts.branch ?? worktreeBranchName(name);
|
|
612
682
|
const { deleteBranch = true, force = true } = opts;
|
|
683
|
+
let deleteBranchAfterRemoval = deleteBranch;
|
|
613
684
|
// Resolve the ACTUAL worktree path from git's worktree list.
|
|
614
685
|
// The computed path may differ when .gsd/ is (or was) a symlink to an
|
|
615
686
|
// external state directory — git resolves symlinks at worktree creation
|
|
@@ -664,10 +735,10 @@ export function removeWorktree(basePath, name, opts = {}) {
|
|
|
664
735
|
}
|
|
665
736
|
if (!existsSync(wtPath)) {
|
|
666
737
|
nativeWorktreePrune(basePath);
|
|
667
|
-
if (
|
|
738
|
+
if (deleteBranchAfterRemoval) {
|
|
668
739
|
deleteBranchIfPresent(basePath, branch, "nativeBranchDelete failed");
|
|
669
740
|
}
|
|
670
|
-
return;
|
|
741
|
+
return true;
|
|
671
742
|
}
|
|
672
743
|
// Submodule safety (#2337): detect submodules with uncommitted changes
|
|
673
744
|
// before force-removing the worktree. Force removal destroys all uncommitted
|
|
@@ -721,6 +792,27 @@ export function removeWorktree(basePath, name, opts = {}) {
|
|
|
721
792
|
}
|
|
722
793
|
}
|
|
723
794
|
}
|
|
795
|
+
// Dirty-worktree quarantine (#2365): once submodule (#2337) and nested-.git
|
|
796
|
+
// (#2616) rescue have run — they commit/clean the tree first — quarantine any
|
|
797
|
+
// uncommitted state that remains instead of force-deleting it. Gate on a live
|
|
798
|
+
// git checkout so a stale local directory left behind by state-sync (which
|
|
799
|
+
// has no `.git` file and is not a registered worktree) is removed normally
|
|
800
|
+
// rather than quarantined; otherwise its branch would never be deleted and
|
|
801
|
+
// the worktree would never be unregistered (#1852).
|
|
802
|
+
if (force && resolvedPathSafe && isLiveGitWorktreeCheckout(resolvedWtPath)) {
|
|
803
|
+
const dirtyState = inspectUncommittedWorktreeState(resolvedWtPath);
|
|
804
|
+
if (dirtyState.dirty) {
|
|
805
|
+
const quarantinePath = quarantineDirtyWorktree(basePath, name, branch, resolvedWtPath, dirtyState.status);
|
|
806
|
+
if (!quarantinePath) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
deleteBranchAfterRemoval = false;
|
|
810
|
+
if (!existsSync(resolvedWtPath)) {
|
|
811
|
+
nativeWorktreePrune(basePath);
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
724
816
|
// Remove worktree — only use force/rmSync when the path is safely contained
|
|
725
817
|
if (resolvedPathSafe) {
|
|
726
818
|
// Remove worktree: try non-force first when submodules have changes,
|
|
@@ -778,9 +870,10 @@ export function removeWorktree(basePath, name, opts = {}) {
|
|
|
778
870
|
}
|
|
779
871
|
// Prune stale entries so git knows the worktree is gone
|
|
780
872
|
nativeWorktreePrune(basePath);
|
|
781
|
-
if (
|
|
873
|
+
if (deleteBranchAfterRemoval) {
|
|
782
874
|
deleteBranchIfPresent(basePath, branch, "final branch delete failed");
|
|
783
875
|
}
|
|
876
|
+
return true;
|
|
784
877
|
}
|
|
785
878
|
/**
|
|
786
879
|
* Paths to skip in all worktree diffs (internal/runtime artifacts).
|