@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
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
getMilestoneSlices,
|
|
23
23
|
getSliceTasks,
|
|
24
24
|
updateSliceStatus,
|
|
25
|
+
insertGateRow,
|
|
26
|
+
saveGateResult,
|
|
25
27
|
_getAdapter,
|
|
26
28
|
} from '../gsd-db.ts';
|
|
27
29
|
import {
|
|
@@ -44,6 +46,7 @@ import {
|
|
|
44
46
|
parseSummary,
|
|
45
47
|
parseTaskPlanFile,
|
|
46
48
|
clearParseCache,
|
|
49
|
+
extractSection,
|
|
47
50
|
} from '../files.ts';
|
|
48
51
|
import { clearPathCache, _clearGsdRootCache } from '../paths.ts';
|
|
49
52
|
import { invalidateStateCache } from '../state.ts';
|
|
@@ -645,6 +648,125 @@ test('── markdown-renderer: slice plan summarizes task descriptions without
|
|
|
645
648
|
}
|
|
646
649
|
});
|
|
647
650
|
|
|
651
|
+
test('── markdown-renderer: renderPlanFromDb marks flagged slice gates ──', async () => {
|
|
652
|
+
const tmpDir = makeTmpDir();
|
|
653
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
654
|
+
openDatabase(dbPath);
|
|
655
|
+
clearAllCaches();
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
659
|
+
scaffoldDirs(tmpDir, 'M001', ['S01']);
|
|
660
|
+
insertSlice({
|
|
661
|
+
id: 'S01',
|
|
662
|
+
milestoneId: 'M001',
|
|
663
|
+
title: 'Risky slice',
|
|
664
|
+
status: 'pending',
|
|
665
|
+
demo: 'Reviewers can see flagged gates.',
|
|
666
|
+
planning: {
|
|
667
|
+
goal: 'Persist gate concern state.',
|
|
668
|
+
successCriteria: '- Flagged gates stay visible',
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
insertTask({
|
|
672
|
+
id: 'T01',
|
|
673
|
+
sliceId: 'S01',
|
|
674
|
+
milestoneId: 'M001',
|
|
675
|
+
title: 'Render gate state',
|
|
676
|
+
status: 'pending',
|
|
677
|
+
});
|
|
678
|
+
insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q3', scope: 'slice' });
|
|
679
|
+
insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q4', scope: 'slice' });
|
|
680
|
+
|
|
681
|
+
const findings = '- Shared gate findings.';
|
|
682
|
+
saveGateResult({
|
|
683
|
+
milestoneId: 'M001',
|
|
684
|
+
sliceId: 'S01',
|
|
685
|
+
gateId: 'Q3',
|
|
686
|
+
verdict: 'flag',
|
|
687
|
+
rationale: 'Concern recorded.',
|
|
688
|
+
findings,
|
|
689
|
+
});
|
|
690
|
+
saveGateResult({
|
|
691
|
+
milestoneId: 'M001',
|
|
692
|
+
sliceId: 'S01',
|
|
693
|
+
gateId: 'Q4',
|
|
694
|
+
verdict: 'pass',
|
|
695
|
+
rationale: 'No concern.',
|
|
696
|
+
findings,
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const rendered = await renderPlanFromDb(tmpDir, 'M001', 'S01');
|
|
700
|
+
const threatSurface = extractSection(rendered.content, 'Threat Surface');
|
|
701
|
+
const requirementImpact = extractSection(rendered.content, 'Requirement Impact');
|
|
702
|
+
|
|
703
|
+
assert.ok(threatSurface?.includes('> **Warning:** Verdict: flag - concerns recorded below'));
|
|
704
|
+
assert.ok(threatSurface?.includes(findings));
|
|
705
|
+
assert.ok(!requirementImpact?.includes('Verdict: flag'), 'passing gate should not render a flag warning');
|
|
706
|
+
assert.ok(requirementImpact?.includes(findings), 'passing gate still renders findings');
|
|
707
|
+
} finally {
|
|
708
|
+
closeDatabase();
|
|
709
|
+
cleanupDir(tmpDir);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
test('── markdown-renderer: renderTaskPlanFromDb marks flagged task gates ──', async () => {
|
|
714
|
+
const tmpDir = makeTmpDir();
|
|
715
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
716
|
+
openDatabase(dbPath);
|
|
717
|
+
clearAllCaches();
|
|
718
|
+
|
|
719
|
+
try {
|
|
720
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
721
|
+
scaffoldDirs(tmpDir, 'M001', ['S01']);
|
|
722
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
|
|
723
|
+
insertTask({
|
|
724
|
+
id: 'T01',
|
|
725
|
+
sliceId: 'S01',
|
|
726
|
+
milestoneId: 'M001',
|
|
727
|
+
title: 'Task gate render',
|
|
728
|
+
status: 'pending',
|
|
729
|
+
planning: {
|
|
730
|
+
description: 'Render task quality gates.',
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q5', scope: 'task', taskId: 'T01' });
|
|
734
|
+
insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q6', scope: 'task', taskId: 'T01' });
|
|
735
|
+
|
|
736
|
+
const findings = '- Same task findings.';
|
|
737
|
+
saveGateResult({
|
|
738
|
+
milestoneId: 'M001',
|
|
739
|
+
sliceId: 'S01',
|
|
740
|
+
taskId: 'T01',
|
|
741
|
+
gateId: 'Q5',
|
|
742
|
+
verdict: 'flag',
|
|
743
|
+
rationale: 'Concern recorded.',
|
|
744
|
+
findings,
|
|
745
|
+
});
|
|
746
|
+
saveGateResult({
|
|
747
|
+
milestoneId: 'M001',
|
|
748
|
+
sliceId: 'S01',
|
|
749
|
+
taskId: 'T01',
|
|
750
|
+
gateId: 'Q6',
|
|
751
|
+
verdict: 'pass',
|
|
752
|
+
rationale: 'No concern.',
|
|
753
|
+
findings,
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const rendered = await renderTaskPlanFromDb(tmpDir, 'M001', 'S01', 'T01');
|
|
757
|
+
const failureModes = extractSection(rendered.content, 'Failure Modes');
|
|
758
|
+
const loadProfile = extractSection(rendered.content, 'Load Profile');
|
|
759
|
+
|
|
760
|
+
assert.ok(failureModes?.includes('> **Warning:** Verdict: flag - concerns recorded below'));
|
|
761
|
+
assert.ok(failureModes?.includes(findings));
|
|
762
|
+
assert.ok(!loadProfile?.includes('Verdict: flag'), 'passing task gate should not render a flag warning');
|
|
763
|
+
assert.ok(loadProfile?.includes(findings), 'passing task gate still renders findings');
|
|
764
|
+
} finally {
|
|
765
|
+
closeDatabase();
|
|
766
|
+
cleanupDir(tmpDir);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
648
770
|
test('── markdown-renderer: renderTaskPlanFromDb throws for missing task ──', async () => {
|
|
649
771
|
const tmpDir = makeTmpDir();
|
|
650
772
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
@@ -95,7 +95,7 @@ function makeDeps(
|
|
|
95
95
|
isInAutoWorktree: () => true,
|
|
96
96
|
shouldUseWorktreeIsolation: () => true,
|
|
97
97
|
getIsolationMode: () => "worktree",
|
|
98
|
-
|
|
98
|
+
mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
|
|
99
99
|
syncWorktreeStateBack: () => ({ synced: [] }),
|
|
100
100
|
teardownAutoWorktree: () => undefined,
|
|
101
101
|
createAutoWorktree: () => "",
|
|
@@ -185,7 +185,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
|
|
|
185
185
|
resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
|
|
186
186
|
type === "ROADMAP" ? roadmapPath : null,
|
|
187
187
|
readFileSync: () => "# M001\n",
|
|
188
|
-
|
|
188
|
+
mergeMilestone: () => {
|
|
189
189
|
throw new MergeConflictError(conflicted, "squash", "worktree/M001", "main");
|
|
190
190
|
},
|
|
191
191
|
});
|
|
@@ -216,7 +216,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
|
|
|
216
216
|
resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
|
|
217
217
|
type === "ROADMAP" ? roadmapPath : null,
|
|
218
218
|
readFileSync: () => "# M001\n",
|
|
219
|
-
|
|
219
|
+
mergeMilestone: () => {
|
|
220
220
|
throw new FakePermError("EACCES: permission denied");
|
|
221
221
|
},
|
|
222
222
|
});
|
|
@@ -242,7 +242,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
|
|
|
242
242
|
resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
|
|
243
243
|
type === "ROADMAP" ? roadmapPath : null,
|
|
244
244
|
readFileSync: () => "# M001\n",
|
|
245
|
-
|
|
245
|
+
mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
const resolver = makeResolver(makeSession(baseDir), deps);
|
|
@@ -16,6 +16,7 @@ import { tmpdir } from "node:os";
|
|
|
16
16
|
import { execSync } from "node:child_process";
|
|
17
17
|
|
|
18
18
|
import { migrateToExternalState, recoverFailedMigration } from "../migrate-external.ts";
|
|
19
|
+
import { externalGsdRoot } from "../repo-identity.ts";
|
|
19
20
|
|
|
20
21
|
function run(command: string, cwd: string): string {
|
|
21
22
|
return execSync(command, {
|
|
@@ -119,6 +120,121 @@ describe("migrate-external worktree guard (#2970)", () => {
|
|
|
119
120
|
});
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
describe("migrateToExternalState rename fallback (#1179)", () => {
|
|
124
|
+
test("falls back to copy/delete when rename fails with EACCES", () => {
|
|
125
|
+
const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-migrate-eacces-")));
|
|
126
|
+
const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-eacces-")));
|
|
127
|
+
const previousStateDir = process.env.GSD_STATE_DIR;
|
|
128
|
+
const previousProjectId = process.env.GSD_PROJECT_ID;
|
|
129
|
+
const originalRenameSync = fs.renameSync;
|
|
130
|
+
const localGsd = join(base, ".gsd");
|
|
131
|
+
const migratingPath = join(base, ".gsd.migrating");
|
|
132
|
+
let renameAttempts = 0;
|
|
133
|
+
|
|
134
|
+
process.env.GSD_STATE_DIR = stateDir;
|
|
135
|
+
process.env.GSD_PROJECT_ID = "rename-eacces";
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
run("git init -b main", base);
|
|
139
|
+
mkdirSync(localGsd, { recursive: true });
|
|
140
|
+
writeFileSync(join(localGsd, "PREFERENCES.md"), "# prefs\n", "utf-8");
|
|
141
|
+
|
|
142
|
+
fs.renameSync = ((src, dst) => {
|
|
143
|
+
if (String(src) === localGsd && String(dst) === migratingPath) {
|
|
144
|
+
renameAttempts += 1;
|
|
145
|
+
const error = new Error("simulated WSL/DrvFs rename lock") as NodeJS.ErrnoException;
|
|
146
|
+
error.code = "EACCES";
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
return originalRenameSync(src, dst);
|
|
150
|
+
}) as typeof fs.renameSync;
|
|
151
|
+
syncBuiltinESMExports();
|
|
152
|
+
|
|
153
|
+
const result = migrateToExternalState(base);
|
|
154
|
+
|
|
155
|
+
assert.equal(result.migrated, true, "EACCES rename failure should use copy/delete fallback");
|
|
156
|
+
assert.equal(result.error, undefined);
|
|
157
|
+
assert.equal(renameAttempts, 1, "should exercise the EACCES rename fallback once");
|
|
158
|
+
assert.equal(readFileSync(join(stateDir, "projects", "rename-eacces", "PREFERENCES.md"), "utf-8"), "# prefs\n");
|
|
159
|
+
assert.equal(readFileSync(join(base, ".gsd", "PREFERENCES.md"), "utf-8"), "# prefs\n");
|
|
160
|
+
assert.ok(!existsSync(migratingPath), "backup staging dir must be removed after successful migration");
|
|
161
|
+
} finally {
|
|
162
|
+
fs.renameSync = originalRenameSync;
|
|
163
|
+
syncBuiltinESMExports();
|
|
164
|
+
if (previousStateDir === undefined) {
|
|
165
|
+
delete process.env.GSD_STATE_DIR;
|
|
166
|
+
} else {
|
|
167
|
+
process.env.GSD_STATE_DIR = previousStateDir;
|
|
168
|
+
}
|
|
169
|
+
if (previousProjectId === undefined) {
|
|
170
|
+
delete process.env.GSD_PROJECT_ID;
|
|
171
|
+
} else {
|
|
172
|
+
process.env.GSD_PROJECT_ID = previousProjectId;
|
|
173
|
+
}
|
|
174
|
+
rmSync(base, { recursive: true, force: true });
|
|
175
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("migrateToExternalState already-migrated external state guard (#1178)", () => {
|
|
181
|
+
test("skips migration when external state already exists for a real local .gsd directory", () => {
|
|
182
|
+
const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-migrate-already-external-")));
|
|
183
|
+
const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-already-external-")));
|
|
184
|
+
const previousStateDir = process.env.GSD_STATE_DIR;
|
|
185
|
+
const previousProjectId = process.env.GSD_PROJECT_ID;
|
|
186
|
+
process.env.GSD_STATE_DIR = stateDir;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
run("git init -b main", base);
|
|
190
|
+
run('git config user.name "Test"', base);
|
|
191
|
+
run('git config user.email "test@example.com"', base);
|
|
192
|
+
writeFileSync(join(base, "README.md"), "# Test\n", "utf-8");
|
|
193
|
+
run("git add README.md", base);
|
|
194
|
+
run('git commit -m "init"', base);
|
|
195
|
+
|
|
196
|
+
const externalPath = externalGsdRoot(base);
|
|
197
|
+
const identity = externalPath.split(/[\\/]/).pop();
|
|
198
|
+
assert.ok(identity, "external state path must include an identity segment");
|
|
199
|
+
mkdirSync(externalPath, { recursive: true });
|
|
200
|
+
writeFileSync(join(base, ".gsd-id"), `${identity}\n`, "utf-8");
|
|
201
|
+
writeFileSync(join(externalPath, "gsd.db"), "newer external state\n", "utf-8");
|
|
202
|
+
|
|
203
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
204
|
+
writeFileSync(join(base, ".gsd", "gsd.db"), "stale local state\n", "utf-8");
|
|
205
|
+
|
|
206
|
+
const result = migrateToExternalState(base);
|
|
207
|
+
|
|
208
|
+
assert.equal(result.migrated, false, "already-migrated project must not re-run migration");
|
|
209
|
+
assert.match(result.error ?? "", /external state already exists/i);
|
|
210
|
+
assert.equal(
|
|
211
|
+
readFileSync(join(externalPath, "gsd.db"), "utf-8"),
|
|
212
|
+
"newer external state\n",
|
|
213
|
+
"newer external gsd.db must not be overwritten by stale local state",
|
|
214
|
+
);
|
|
215
|
+
assert.equal(
|
|
216
|
+
readFileSync(join(base, ".gsd", "gsd.db"), "utf-8"),
|
|
217
|
+
"stale local state\n",
|
|
218
|
+
"local real .gsd directory should be left for explicit recovery",
|
|
219
|
+
);
|
|
220
|
+
assert.ok(!existsSync(join(base, ".gsd.migrating")), "migration staging dir must not be created");
|
|
221
|
+
} finally {
|
|
222
|
+
if (previousStateDir === undefined) {
|
|
223
|
+
delete process.env.GSD_STATE_DIR;
|
|
224
|
+
} else {
|
|
225
|
+
process.env.GSD_STATE_DIR = previousStateDir;
|
|
226
|
+
}
|
|
227
|
+
if (previousProjectId === undefined) {
|
|
228
|
+
delete process.env.GSD_PROJECT_ID;
|
|
229
|
+
} else {
|
|
230
|
+
process.env.GSD_PROJECT_ID = previousProjectId;
|
|
231
|
+
}
|
|
232
|
+
rmSync(base, { recursive: true, force: true });
|
|
233
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
122
238
|
describe("migrateToExternalState copy failure rollback (#1047)", () => {
|
|
123
239
|
test("restores .gsd when a source entry fails to copy", () => {
|
|
124
240
|
const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-migrate-copy-fail-")));
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
_runMilestoneMergeOnceWithStashRestore,
|
|
9
|
+
_runMilestoneMergeWithStashRestore,
|
|
10
|
+
} from "../auto/closeout.js";
|
|
8
11
|
import type { IterationContext } from "../auto/types.js";
|
|
9
12
|
import { MergeConflictError } from "../git-service.js";
|
|
10
13
|
import type {
|
|
@@ -58,6 +61,30 @@ function buildIc(opts: {
|
|
|
58
61
|
},
|
|
59
62
|
};
|
|
60
63
|
|
|
64
|
+
const plainExitMilestone = (exitOpts: { merge: boolean }) => {
|
|
65
|
+
log.mergeCalls += 1;
|
|
66
|
+
if (opts.mergeBehavior === "succeed") {
|
|
67
|
+
return { ok: true, merged: exitOpts.merge, codeFilesChanged: false } as const;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
opts.mergeBehavior();
|
|
71
|
+
return { ok: true, merged: exitOpts.merge, codeFilesChanged: false } as const;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
// Mirror Lifecycle's typed-result wrapping of MergeConflictError
|
|
74
|
+
// and other thrown values per worktree-lifecycle.exitMilestone.
|
|
75
|
+
const isMergeConflict =
|
|
76
|
+
err !== null &&
|
|
77
|
+
typeof err === "object" &&
|
|
78
|
+
err !== undefined &&
|
|
79
|
+
(err as { name?: string }).name === "MergeConflictError";
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: isMergeConflict ? "merge-conflict" : "teardown-failed",
|
|
83
|
+
cause: err,
|
|
84
|
+
} as const;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
61
88
|
const deps = {
|
|
62
89
|
preflightCleanRoot: () => {
|
|
63
90
|
log.preflightCalls += 1;
|
|
@@ -76,28 +103,72 @@ function buildIc(opts: {
|
|
|
76
103
|
},
|
|
77
104
|
},
|
|
78
105
|
lifecycle: {
|
|
79
|
-
exitMilestone: (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
exitMilestone: (
|
|
107
|
+
_mid: string,
|
|
108
|
+
exitOpts: {
|
|
109
|
+
merge: boolean;
|
|
110
|
+
guardedMerge?: {
|
|
111
|
+
projectRoot: string;
|
|
112
|
+
preflightCleanRoot: (
|
|
113
|
+
basePath: string,
|
|
114
|
+
milestoneId: string,
|
|
115
|
+
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
116
|
+
) => PreflightResult;
|
|
117
|
+
postflightPopStash: (
|
|
118
|
+
basePath: string,
|
|
119
|
+
milestoneId: string,
|
|
120
|
+
stashMarker: string | undefined,
|
|
121
|
+
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
122
|
+
) => PostflightResult;
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
exitCtx?: { notify: (message: string, level: "info" | "warning" | "error") => void },
|
|
126
|
+
) => {
|
|
127
|
+
const notify = exitCtx?.notify ?? (() => {});
|
|
128
|
+
const guarded = exitOpts.guardedMerge;
|
|
129
|
+
if (exitOpts.merge && guarded) {
|
|
130
|
+
const preflight = guarded.preflightCleanRoot(guarded.projectRoot, _mid, notify);
|
|
131
|
+
if (preflight.blocked) {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
reason: preflight.blockedReason?.startsWith("unmerged-conflicts")
|
|
135
|
+
? "preflight-unmerged-conflicts"
|
|
136
|
+
: "preflight-dirty-overlap",
|
|
137
|
+
} as const;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const mergeResult = plainExitMilestone({ merge: true });
|
|
141
|
+
const postflight = preflight.stashPushed
|
|
142
|
+
? guarded.postflightPopStash(
|
|
143
|
+
guarded.projectRoot,
|
|
144
|
+
_mid,
|
|
145
|
+
preflight.stashMarker,
|
|
146
|
+
notify,
|
|
147
|
+
)
|
|
148
|
+
: undefined;
|
|
149
|
+
|
|
150
|
+
if (!mergeResult.ok) {
|
|
151
|
+
if (mergeResult.reason === "merge-conflict") {
|
|
152
|
+
return { ...mergeResult, postflight } as const;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
reason: "merge-failed",
|
|
157
|
+
cause: mergeResult.cause,
|
|
158
|
+
postflight,
|
|
159
|
+
} as const;
|
|
160
|
+
}
|
|
161
|
+
if (postflight?.needsManualRecovery) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
reason: "postflight-stash-restore-failed",
|
|
165
|
+
postflight,
|
|
166
|
+
} as const;
|
|
167
|
+
}
|
|
168
|
+
return mergeResult;
|
|
100
169
|
}
|
|
170
|
+
|
|
171
|
+
return plainExitMilestone(exitOpts);
|
|
101
172
|
},
|
|
102
173
|
},
|
|
103
174
|
stopAuto: async (
|
|
@@ -155,6 +226,14 @@ const PREFLIGHT_UNMERGED: PreflightResult = {
|
|
|
155
226
|
summary: "Working tree has unresolved Git conflicts before milestone M002 merge.",
|
|
156
227
|
};
|
|
157
228
|
|
|
229
|
+
const PREFLIGHT_UNMERGED_EVAL_FAILED: PreflightResult = {
|
|
230
|
+
stashPushed: false,
|
|
231
|
+
blocked: true,
|
|
232
|
+
blockedReason: "unmerged-conflicts-eval-failed",
|
|
233
|
+
conflictedPaths: ["todo.js"],
|
|
234
|
+
summary: "Unable to fully evaluate unresolved Git conflicts before milestone M002 merge.",
|
|
235
|
+
};
|
|
236
|
+
|
|
158
237
|
const POP_OK: PostflightResult = {
|
|
159
238
|
restored: true,
|
|
160
239
|
needsManualRecovery: false,
|
|
@@ -344,6 +423,52 @@ test("unmerged conflicts: preflight stops before merge and postflight restore",
|
|
|
344
423
|
);
|
|
345
424
|
});
|
|
346
425
|
|
|
426
|
+
test("unmerged conflict evaluation failure stops as preflight-unmerged-conflicts", async () => {
|
|
427
|
+
const { ic, log } = buildIc({
|
|
428
|
+
preflightResult: PREFLIGHT_UNMERGED_EVAL_FAILED,
|
|
429
|
+
mergeBehavior: "succeed",
|
|
430
|
+
postflightResult: POP_OK,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
|
|
434
|
+
|
|
435
|
+
assert.deepEqual(result, {
|
|
436
|
+
action: "break",
|
|
437
|
+
reason: "preflight-unmerged-conflicts",
|
|
438
|
+
});
|
|
439
|
+
assert.equal(
|
|
440
|
+
log.mergeCalls,
|
|
441
|
+
0,
|
|
442
|
+
"failed conflict evaluation must not start milestone merge",
|
|
443
|
+
);
|
|
444
|
+
assert.equal(log.postflightCalls, 0, "blocked preflight must not attempt stash restore");
|
|
445
|
+
assert.equal(log.stopAutoCalls.length, 1);
|
|
446
|
+
assert.match(
|
|
447
|
+
log.stopAutoCalls[0] ?? "",
|
|
448
|
+
/Pre-merge unresolved Git conflicts block milestone M002/,
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("already-merged milestone skips guarded merge to terminate duplicate closeout loops", async () => {
|
|
453
|
+
const { ic, log } = buildIc({
|
|
454
|
+
preflightResult: STASH_PUSHED,
|
|
455
|
+
mergeBehavior: "succeed",
|
|
456
|
+
postflightResult: POP_OK,
|
|
457
|
+
});
|
|
458
|
+
log.milestoneMergedInPhases = true;
|
|
459
|
+
|
|
460
|
+
const result = await _runMilestoneMergeOnceWithStashRestore(ic, "M002");
|
|
461
|
+
|
|
462
|
+
assert.equal(result, null);
|
|
463
|
+
assert.equal(log.preflightCalls, 0, "already-merged guard must not re-trigger preflight");
|
|
464
|
+
assert.equal(log.mergeCalls, 0, "already-merged guard must not re-trigger merge");
|
|
465
|
+
assert.equal(
|
|
466
|
+
log.postflightCalls,
|
|
467
|
+
0,
|
|
468
|
+
"already-merged guard must not re-trigger stash restore",
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
|
|
347
472
|
test("merge succeeds but stash pop needs manual recovery -> postflight-stash-restore-failed break", async () => {
|
|
348
473
|
const { ic, log } = buildIc({
|
|
349
474
|
preflightResult: STASH_PUSHED,
|
|
@@ -358,6 +483,11 @@ test("merge succeeds but stash pop needs manual recovery -> postflight-stash-res
|
|
|
358
483
|
reason: "postflight-stash-restore-failed",
|
|
359
484
|
});
|
|
360
485
|
assert.equal(log.postflightCalls, 1);
|
|
486
|
+
assert.equal(
|
|
487
|
+
log.milestoneMergedInPhases,
|
|
488
|
+
true,
|
|
489
|
+
"successful merge must set the flag before postflight recovery stops auto-mode",
|
|
490
|
+
);
|
|
361
491
|
assert.equal(log.stopAutoCalls.length, 1);
|
|
362
492
|
assert.match(
|
|
363
493
|
log.stopAutoCalls[0] ?? "",
|
|
@@ -13,7 +13,7 @@ test("runMilestoneMergeTransaction delegates to the supplied merge runner", () =
|
|
|
13
13
|
const calls: unknown[][] = [];
|
|
14
14
|
const result = runMilestoneMergeTransaction(
|
|
15
15
|
{
|
|
16
|
-
|
|
16
|
+
mergeMilestone: (basePath, milestoneId, roadmapContent) => {
|
|
17
17
|
calls.push([basePath, milestoneId, roadmapContent]);
|
|
18
18
|
return { pushed: true, codeFilesChanged: true, commitMessage: "merge M001" };
|
|
19
19
|
},
|
|
@@ -55,8 +55,8 @@ describe("pendingAutoStart scope pinning (C1)", () => {
|
|
|
55
55
|
assert.ok(entry.scope, "entry.scope should be set");
|
|
56
56
|
assert.equal(entry.scope.milestoneId, "M001");
|
|
57
57
|
|
|
58
|
-
const expectedContext = join(base, ".gsd", "
|
|
59
|
-
const expectedRoadmap = join(base, ".gsd", "
|
|
58
|
+
const expectedContext = join(base, ".gsd", "phases", "01-m001", "01-CONTEXT.md");
|
|
59
|
+
const expectedRoadmap = join(base, ".gsd", "phases", "01-m001", "01-ROADMAP.md");
|
|
60
60
|
const expectedState = join(base, ".gsd", "STATE.md");
|
|
61
61
|
|
|
62
62
|
assert.equal(entry.scope.contextFile(), expectedContext);
|
|
@@ -82,13 +82,57 @@ const ic = {
|
|
|
82
82
|
return { ok: true, needsManualRecovery: false };
|
|
83
83
|
},
|
|
84
84
|
lifecycle: {
|
|
85
|
-
exitMilestone
|
|
85
|
+
// Mirror WorktreeLifecycle.exitMilestone's guarded-merge branch
|
|
86
|
+
// (preflight -> inner merge -> postflight). Since #1169 the closeout
|
|
87
|
+
// routes the guard through the lifecycle verb, so a non-conflict inner
|
|
88
|
+
// failure ("teardown-failed") is mapped to "merge-failed" here rather
|
|
89
|
+
// than by the caller.
|
|
90
|
+
exitMilestone(
|
|
91
|
+
milestoneId: string,
|
|
92
|
+
exitOpts: {
|
|
93
|
+
merge: boolean;
|
|
94
|
+
guardedMerge?: {
|
|
95
|
+
projectRoot: string;
|
|
96
|
+
preflightCleanRoot: (
|
|
97
|
+
basePath: string,
|
|
98
|
+
milestoneId: string,
|
|
99
|
+
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
100
|
+
) => { blocked?: boolean; blockedReason?: string; stashPushed: boolean; stashMarker?: string };
|
|
101
|
+
postflightPopStash: (
|
|
102
|
+
basePath: string,
|
|
103
|
+
milestoneId: string,
|
|
104
|
+
stashMarker: string | undefined,
|
|
105
|
+
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
106
|
+
) => { needsManualRecovery?: boolean };
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
exitCtx?: { notify: (message: string, level: "info" | "warning" | "error") => void },
|
|
110
|
+
) {
|
|
111
|
+
const notify = exitCtx?.notify ?? (() => {});
|
|
112
|
+
const guarded = exitOpts.guardedMerge;
|
|
113
|
+
if (exitOpts.merge && guarded) {
|
|
114
|
+
const preflight = guarded.preflightCleanRoot(guarded.projectRoot, milestoneId, notify);
|
|
115
|
+
if (preflight.blocked) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
reason: preflight.blockedReason?.startsWith("unmerged-conflicts")
|
|
119
|
+
? "preflight-unmerged-conflicts"
|
|
120
|
+
: "preflight-dirty-overlap",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Inner merge fails with a non-conflict error. The lifecycle still
|
|
124
|
+
// runs postflight stash restore on the failed-merge path, then maps
|
|
125
|
+
// the non-conflict failure to "merge-failed" (a MergeConflictError
|
|
126
|
+
// would instead map to "merge-conflict").
|
|
127
|
+
calls.push("merge");
|
|
128
|
+
const cause = new Error("remote rejected push");
|
|
129
|
+
if (preflight.stashPushed) {
|
|
130
|
+
guarded.postflightPopStash(guarded.projectRoot, milestoneId, preflight.stashMarker, notify);
|
|
131
|
+
}
|
|
132
|
+
return { ok: false, reason: "merge-failed", cause };
|
|
133
|
+
}
|
|
86
134
|
calls.push("merge");
|
|
87
|
-
return {
|
|
88
|
-
ok: false,
|
|
89
|
-
reason: "teardown-failed",
|
|
90
|
-
cause: new Error("remote rejected push"),
|
|
91
|
-
};
|
|
135
|
+
return { ok: false, reason: "teardown-failed", cause: new Error("remote rejected push") };
|
|
92
136
|
},
|
|
93
137
|
},
|
|
94
138
|
async stopAuto(_ctx: unknown, _pi: unknown, reason?: string) {
|
|
@@ -34,6 +34,7 @@ const BASE_VARS = {
|
|
|
34
34
|
planTemplatePath: "C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\plan.md",
|
|
35
35
|
taskPlanTemplatePath: "C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-plan.md",
|
|
36
36
|
skillActivation: "Load the relevant skills.",
|
|
37
|
+
repoRegistry: "",
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
const DEFAULT_SKILL_ACTIVATION = "If a `GSD Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.";
|
|
@@ -61,6 +62,22 @@ test("plan-slice prompt: all variables substituted", () => {
|
|
|
61
62
|
assert.ok(result.includes("S01"));
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
test("plan-slice prompt: repoRegistry block surfaces declared repositories when non-empty", () => {
|
|
66
|
+
const block = "### Declared Repositories\n\n- `frontend` (frontend/)\n- `backend` (backend/)\n\nAssign each task's `targetRepositories`";
|
|
67
|
+
const result = loadPrompt("plan-slice", { ...BASE_VARS, repoRegistry: block, commitInstruction: "Do not commit." });
|
|
68
|
+
assert.ok(result.includes("### Declared Repositories"), "declared repositories block should appear when provided");
|
|
69
|
+
assert.ok(result.includes("`frontend`"), "declared repo ids should be visible to the planner");
|
|
70
|
+
assert.ok(result.includes("targetRepositories"), "repo-assignment guidance should appear");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("plan-slice prompt: empty repoRegistry leaves no orphan marker", () => {
|
|
74
|
+
const result = loadPrompt("plan-slice", { ...BASE_VARS, repoRegistry: "", commitInstruction: "Do not commit." });
|
|
75
|
+
assert.ok(!result.includes("{{repoRegistry}}"), "the repoRegistry placeholder must be substituted");
|
|
76
|
+
// The block heading only renders when repoRegistry is non-empty; the rule-9
|
|
77
|
+
// guidance text mentions "Declared Repositories" but is not a rendered block.
|
|
78
|
+
assert.ok(!result.includes("### Declared Repositories"), "single-repo prompts must not show a declared-repositories block");
|
|
79
|
+
});
|
|
80
|
+
|
|
64
81
|
test("plan-slice prompt: DB-backed tool names survive template substitution", () => {
|
|
65
82
|
const result = loadPrompt("plan-slice", { ...BASE_VARS, commitInstruction: "Do not commit." });
|
|
66
83
|
assert.ok(result.includes("gsd_plan_slice"), "gsd_plan_slice should appear in rendered prompt");
|