@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,7 +22,7 @@ import {
|
|
|
22
22
|
isSessionSwitchAbortGraceActive,
|
|
23
23
|
} from "../auto/resolve.js";
|
|
24
24
|
import { runUnit, shouldDeferUnitFailsafeTimeout } from "../auto/run-unit.js";
|
|
25
|
-
import { scheduleAutoWakeup, _resetAutoWakeupsForTest } from "../auto/schedule-wakeup.js";
|
|
25
|
+
import { consumeAutoWakeup, scheduleAutoWakeup, _resetAutoWakeupsForTest } from "../auto/schedule-wakeup.js";
|
|
26
26
|
import { writeUnitRuntimeRecord, readUnitRuntimeRecord } from "../unit-runtime.js";
|
|
27
27
|
import { autoLoop as rawAutoLoop } from "../auto/loop.js";
|
|
28
28
|
import { runPreDispatch } from "../auto/pre-dispatch.js";
|
|
@@ -67,6 +67,76 @@ async function drainMicrotasks(turns = 20): Promise<void> {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function guardedExitMilestoneForTest(
|
|
71
|
+
merge: (milestoneId: string, opts: { merge: boolean }) => {
|
|
72
|
+
ok: boolean;
|
|
73
|
+
merged?: boolean;
|
|
74
|
+
codeFilesChanged?: boolean;
|
|
75
|
+
reason?: string;
|
|
76
|
+
cause?: unknown;
|
|
77
|
+
},
|
|
78
|
+
) {
|
|
79
|
+
return (
|
|
80
|
+
milestoneId: string,
|
|
81
|
+
opts: {
|
|
82
|
+
merge: boolean;
|
|
83
|
+
guardedMerge?: {
|
|
84
|
+
projectRoot: string;
|
|
85
|
+
preflightCleanRoot: (
|
|
86
|
+
basePath: string,
|
|
87
|
+
milestoneId: string,
|
|
88
|
+
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
89
|
+
) => { blocked?: boolean; blockedReason?: string; stashPushed: boolean; stashMarker?: string };
|
|
90
|
+
postflightPopStash: (
|
|
91
|
+
basePath: string,
|
|
92
|
+
milestoneId: string,
|
|
93
|
+
stashMarker: string | undefined,
|
|
94
|
+
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
95
|
+
) => { needsManualRecovery?: boolean };
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
ctx?: { notify: (message: string, level: "info" | "warning" | "error") => void },
|
|
99
|
+
) => {
|
|
100
|
+
const notify = ctx?.notify ?? (() => {});
|
|
101
|
+
const guard = opts.guardedMerge;
|
|
102
|
+
if (opts.merge && guard) {
|
|
103
|
+
const preflight = guard.preflightCleanRoot(guard.projectRoot, milestoneId, notify);
|
|
104
|
+
if (preflight.blocked) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
reason: preflight.blockedReason?.startsWith("unmerged-conflicts")
|
|
108
|
+
? "preflight-unmerged-conflicts"
|
|
109
|
+
: "preflight-dirty-overlap",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const mergeResult = merge(milestoneId, { merge: true });
|
|
114
|
+
const postflight = preflight.stashPushed
|
|
115
|
+
? guard.postflightPopStash(
|
|
116
|
+
guard.projectRoot,
|
|
117
|
+
milestoneId,
|
|
118
|
+
preflight.stashMarker,
|
|
119
|
+
notify,
|
|
120
|
+
)
|
|
121
|
+
: undefined;
|
|
122
|
+
|
|
123
|
+
if (!mergeResult.ok) {
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
reason: mergeResult.reason === "merge-conflict" ? "merge-conflict" : "merge-failed",
|
|
127
|
+
cause: mergeResult.cause,
|
|
128
|
+
postflight,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (postflight?.needsManualRecovery) {
|
|
132
|
+
return { ok: false, reason: "postflight-stash-restore-failed", postflight };
|
|
133
|
+
}
|
|
134
|
+
return mergeResult;
|
|
135
|
+
}
|
|
136
|
+
return merge(milestoneId, opts);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
70
140
|
async function waitForMicrotasks(
|
|
71
141
|
condition: () => boolean,
|
|
72
142
|
label: string,
|
|
@@ -482,6 +552,62 @@ test("runUnit honors ScheduleWakeup by continuing the same unit session", async
|
|
|
482
552
|
assert.equal(pi.calls.length, 2);
|
|
483
553
|
});
|
|
484
554
|
|
|
555
|
+
test("runUnit clears scheduled wakeups when the unit is cancelled", async () => {
|
|
556
|
+
_resetPendingResolve();
|
|
557
|
+
_resetAutoWakeupsForTest();
|
|
558
|
+
|
|
559
|
+
const ctx = {
|
|
560
|
+
...makeMockCtx(),
|
|
561
|
+
ui: {
|
|
562
|
+
notify: () => {},
|
|
563
|
+
setStatus: () => {},
|
|
564
|
+
setWorkingMessage: () => {},
|
|
565
|
+
},
|
|
566
|
+
sessionManager: {
|
|
567
|
+
getEntries: () => [],
|
|
568
|
+
},
|
|
569
|
+
modelRegistry: {
|
|
570
|
+
getProviderAuthMode: () => undefined,
|
|
571
|
+
isProviderRequestReady: () => true,
|
|
572
|
+
},
|
|
573
|
+
} as any;
|
|
574
|
+
const pi = makeMockPi();
|
|
575
|
+
const s = makeMockSession();
|
|
576
|
+
|
|
577
|
+
const resultPromise = runUnit(
|
|
578
|
+
ctx,
|
|
579
|
+
pi,
|
|
580
|
+
s,
|
|
581
|
+
"execute-task",
|
|
582
|
+
"M001/S01/T02",
|
|
583
|
+
"submit external job",
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
await waitForMicrotasks(() => pi.calls.length === 1, "initial unit dispatch");
|
|
587
|
+
scheduleAutoWakeup({
|
|
588
|
+
basePath: s.basePath,
|
|
589
|
+
unitType: "execute-task",
|
|
590
|
+
unitId: "M001/S01/T02",
|
|
591
|
+
delayMs: 0,
|
|
592
|
+
prompt: "stale prompt from cancelled unit",
|
|
593
|
+
reason: "poll external job",
|
|
594
|
+
createdAt: Date.now(),
|
|
595
|
+
});
|
|
596
|
+
resolveAgentEndCancelled({
|
|
597
|
+
message: "Auto-mode paused",
|
|
598
|
+
category: "aborted",
|
|
599
|
+
isTransient: true,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const result = await resultPromise;
|
|
603
|
+
assert.equal(result.status, "cancelled");
|
|
604
|
+
assert.equal(
|
|
605
|
+
consumeAutoWakeup(s.basePath, "execute-task", "M001/S01/T02"),
|
|
606
|
+
null,
|
|
607
|
+
"cancelled units must not leave stale ScheduleWakeup prompts for later retries",
|
|
608
|
+
);
|
|
609
|
+
});
|
|
610
|
+
|
|
485
611
|
test("runUnit suppresses the global working-message loader for auto dashboard runs", async () => {
|
|
486
612
|
_resetPendingResolve();
|
|
487
613
|
const workingMessages: unknown[] = [];
|
|
@@ -1335,11 +1461,11 @@ function makeMockDeps(
|
|
|
1335
1461
|
GitServiceImpl: class {} as any,
|
|
1336
1462
|
lifecycle: {
|
|
1337
1463
|
enterMilestone: () => ({ ok: true, mode: "worktree", path: "/tmp/project" }),
|
|
1338
|
-
exitMilestone: (_mid
|
|
1464
|
+
exitMilestone: guardedExitMilestoneForTest((_mid, opts) => ({
|
|
1339
1465
|
ok: true,
|
|
1340
1466
|
merged: opts.merge,
|
|
1341
1467
|
codeFilesChanged: false,
|
|
1342
|
-
}),
|
|
1468
|
+
})),
|
|
1343
1469
|
} as any,
|
|
1344
1470
|
worktreeProjection: new WorktreeStateProjection(),
|
|
1345
1471
|
postUnitPreVerification: async () => {
|
|
@@ -1762,10 +1888,10 @@ test("autoLoop marks transition merge complete before postflight recovery stop",
|
|
|
1762
1888
|
enterMilestone: () => {
|
|
1763
1889
|
assert.fail("must not enter the next milestone after postflight recovery fails");
|
|
1764
1890
|
},
|
|
1765
|
-
exitMilestone: (_mid
|
|
1891
|
+
exitMilestone: guardedExitMilestoneForTest((_mid, opts) => {
|
|
1766
1892
|
if (opts.merge) mergeCalls += 1;
|
|
1767
1893
|
return { ok: true, merged: opts.merge, codeFilesChanged: false };
|
|
1768
|
-
},
|
|
1894
|
+
}),
|
|
1769
1895
|
} as any,
|
|
1770
1896
|
stopAuto: async (_ctx, _pi, reason) => {
|
|
1771
1897
|
deps.callLog.push("stopAuto");
|
|
@@ -49,6 +49,24 @@ test("recoverFailedMigration returns false when both .gsd and .gsd.migrating exi
|
|
|
49
49
|
assert.ok(existsSync(join(base, ".gsd.migrating")), ".gsd.migrating must still exist");
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
test("recoverFailedMigration removes orphan when .gsd is a real intact directory", (t) => {
|
|
53
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-migrating-real-orphan-"));
|
|
54
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
55
|
+
|
|
56
|
+
const localGsd = join(base, ".gsd");
|
|
57
|
+
mkdirSync(join(localGsd, "phases"), { recursive: true });
|
|
58
|
+
mkdirSync(join(localGsd, "activity"), { recursive: true });
|
|
59
|
+
writeFileSync(join(localGsd, "STATE.md"), "# State\n", "utf-8");
|
|
60
|
+
writeFileSync(join(localGsd, "gsd.db"), "not empty\n", "utf-8");
|
|
61
|
+
mkdirSync(join(base, ".gsd.migrating"), { recursive: true });
|
|
62
|
+
|
|
63
|
+
const recovered = recoverFailedMigration(base);
|
|
64
|
+
|
|
65
|
+
assert.equal(recovered, true, "expected orphan cleanup to succeed");
|
|
66
|
+
assert.ok(existsSync(localGsd), ".gsd real directory must remain");
|
|
67
|
+
assert.ok(!existsSync(join(base, ".gsd.migrating")), ".gsd.migrating orphan must be removed");
|
|
68
|
+
});
|
|
69
|
+
|
|
52
70
|
test("recoverFailedMigration removes orphan when .gsd is an intact external-state junction", (t) => {
|
|
53
71
|
const base = mkdtempSync(join(tmpdir(), "gsd-migrating-orphan-"));
|
|
54
72
|
const stateDir = mkdtempSync(join(tmpdir(), "gsd-migrating-state-"));
|
|
@@ -63,6 +63,62 @@ test("resolvePreferredModelConfig synthesizes heavy routing ceiling when models
|
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
test("resolvePreferredModelConfig treats token-profile model defaults as synthesized routing", () => {
|
|
67
|
+
const originalCwd = process.cwd();
|
|
68
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
69
|
+
const tempProject = makeTempDir("gsd-routing-profile-project-");
|
|
70
|
+
const tempGsdHome = makeTempDir("gsd-routing-profile-home-");
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
74
|
+
writeFileSync(
|
|
75
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
76
|
+
[
|
|
77
|
+
"---",
|
|
78
|
+
"token_profile: quality",
|
|
79
|
+
"dynamic_routing:",
|
|
80
|
+
" enabled: true",
|
|
81
|
+
" tier_models:",
|
|
82
|
+
" light: claude-haiku-4-5",
|
|
83
|
+
" standard: claude-sonnet-4-6",
|
|
84
|
+
" heavy: claude-opus-4-6",
|
|
85
|
+
"---",
|
|
86
|
+
].join("\n"),
|
|
87
|
+
"utf-8",
|
|
88
|
+
);
|
|
89
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
90
|
+
process.chdir(tempProject);
|
|
91
|
+
|
|
92
|
+
const config = resolvePreferredModelConfig(
|
|
93
|
+
"plan-slice",
|
|
94
|
+
{
|
|
95
|
+
provider: "anthropic",
|
|
96
|
+
id: "claude-sonnet-4-6",
|
|
97
|
+
},
|
|
98
|
+
true,
|
|
99
|
+
tempProject,
|
|
100
|
+
[
|
|
101
|
+
"anthropic/claude-haiku-4-5",
|
|
102
|
+
"anthropic/claude-sonnet-4-6",
|
|
103
|
+
"anthropic/claude-opus-4-6",
|
|
104
|
+
],
|
|
105
|
+
"anthropic/claude-sonnet-4-6",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
assert.deepEqual(config, {
|
|
109
|
+
primary: "claude-opus-4-6",
|
|
110
|
+
fallbacks: [],
|
|
111
|
+
source: "synthesized",
|
|
112
|
+
});
|
|
113
|
+
} finally {
|
|
114
|
+
process.chdir(originalCwd);
|
|
115
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
116
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
117
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
118
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
66
122
|
test("resolvePreferredModelConfig falls back to auto start model when heavy tier is absent", () => {
|
|
67
123
|
const originalCwd = process.cwd();
|
|
68
124
|
const originalGsdHome = process.env.GSD_HOME;
|
|
@@ -152,6 +208,128 @@ test("resolvePreferredModelConfig keeps explicit phase models as the ceiling", (
|
|
|
152
208
|
}
|
|
153
209
|
});
|
|
154
210
|
|
|
211
|
+
// #1115 regression: a token_profile with dynamic_routing enabled but NO
|
|
212
|
+
// tier_models pins must still resolve the profile's per-phase model as a
|
|
213
|
+
// synthesized routing ceiling. Before the fix, resolvePreferredModelConfig
|
|
214
|
+
// bailed to undefined here (explicit detection skips profile defaults, and the
|
|
215
|
+
// synthesis path required tier_models), so auto dispatch fell back to the
|
|
216
|
+
// start model for every unit and silently discarded the profile.
|
|
217
|
+
test("resolvePreferredModelConfig resolves profile per-phase models as synthesized when tier_models are absent", () => {
|
|
218
|
+
const originalCwd = process.cwd();
|
|
219
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
220
|
+
const tempProject = makeTempDir("gsd-routing-profile-notier-");
|
|
221
|
+
const tempGsdHome = makeTempDir("gsd-routing-profile-notier-home-");
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
225
|
+
writeFileSync(
|
|
226
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
227
|
+
[
|
|
228
|
+
"---",
|
|
229
|
+
"token_profile: budget",
|
|
230
|
+
"dynamic_routing:",
|
|
231
|
+
" enabled: true",
|
|
232
|
+
"---",
|
|
233
|
+
].join("\n"),
|
|
234
|
+
"utf-8",
|
|
235
|
+
);
|
|
236
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
237
|
+
process.chdir(tempProject);
|
|
238
|
+
|
|
239
|
+
const availableModelIds = [
|
|
240
|
+
"anthropic/claude-haiku-4-5",
|
|
241
|
+
"anthropic/claude-sonnet-4-6",
|
|
242
|
+
"anthropic/claude-opus-4-6",
|
|
243
|
+
];
|
|
244
|
+
const start = { provider: "anthropic", id: "claude-sonnet-4-6" };
|
|
245
|
+
// preferredModelId is intentionally left undefined so profile tier
|
|
246
|
+
// resolution is exercised rather than pinning every tier to the session
|
|
247
|
+
// model (a heavy session model can satisfy every tier).
|
|
248
|
+
const resolve = (unitType: string) =>
|
|
249
|
+
resolvePreferredModelConfig(unitType, start, true, tempProject, availableModelIds);
|
|
250
|
+
|
|
251
|
+
// budget profile: research=light, planning=standard, execution=standard,
|
|
252
|
+
// execution_simple=light, completion=light (PROFILE_TIER_MAP).
|
|
253
|
+
assert.deepEqual(resolve("research-milestone"), {
|
|
254
|
+
primary: "claude-haiku-4-5",
|
|
255
|
+
fallbacks: [],
|
|
256
|
+
source: "synthesized",
|
|
257
|
+
});
|
|
258
|
+
assert.deepEqual(resolve("plan-milestone"), {
|
|
259
|
+
primary: "claude-sonnet-4-6",
|
|
260
|
+
fallbacks: [],
|
|
261
|
+
source: "synthesized",
|
|
262
|
+
});
|
|
263
|
+
assert.deepEqual(resolve("execute-task"), {
|
|
264
|
+
primary: "claude-sonnet-4-6",
|
|
265
|
+
fallbacks: [],
|
|
266
|
+
source: "synthesized",
|
|
267
|
+
});
|
|
268
|
+
assert.deepEqual(resolve("execute-task-simple"), {
|
|
269
|
+
primary: "claude-haiku-4-5",
|
|
270
|
+
fallbacks: [],
|
|
271
|
+
source: "synthesized",
|
|
272
|
+
});
|
|
273
|
+
assert.deepEqual(resolve("complete-slice"), {
|
|
274
|
+
primary: "claude-haiku-4-5",
|
|
275
|
+
fallbacks: [],
|
|
276
|
+
source: "synthesized",
|
|
277
|
+
});
|
|
278
|
+
} finally {
|
|
279
|
+
process.chdir(originalCwd);
|
|
280
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
281
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
282
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
283
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// #1115 guard: the explicit flat-rate opt-out must suppress synthesized routing
|
|
288
|
+
// even on the profile-derived (no tier_models) path — the opt-out moved ahead
|
|
289
|
+
// of both synthesis branches, so a flat-rate start model with
|
|
290
|
+
// allow_flat_rate_providers:false yields no config (start model is used as-is).
|
|
291
|
+
test("resolvePreferredModelConfig honors flat-rate opt-out on the profile path (no tier_models)", () => {
|
|
292
|
+
const originalCwd = process.cwd();
|
|
293
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
294
|
+
const tempProject = makeTempDir("gsd-routing-flatrate-notier-");
|
|
295
|
+
const tempGsdHome = makeTempDir("gsd-routing-flatrate-notier-home-");
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
299
|
+
writeFileSync(
|
|
300
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
301
|
+
[
|
|
302
|
+
"---",
|
|
303
|
+
"token_profile: budget",
|
|
304
|
+
"dynamic_routing:",
|
|
305
|
+
" enabled: true",
|
|
306
|
+
" allow_flat_rate_providers: false",
|
|
307
|
+
"---",
|
|
308
|
+
].join("\n"),
|
|
309
|
+
"utf-8",
|
|
310
|
+
);
|
|
311
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
312
|
+
process.chdir(tempProject);
|
|
313
|
+
|
|
314
|
+
const config = resolvePreferredModelConfig(
|
|
315
|
+
"execute-task",
|
|
316
|
+
// claude-code is a built-in flat-rate provider.
|
|
317
|
+
{ provider: "claude-code", id: "claude-opus-4-6" },
|
|
318
|
+
true,
|
|
319
|
+
tempProject,
|
|
320
|
+
["anthropic/claude-haiku-4-5", "anthropic/claude-sonnet-4-6", "anthropic/claude-opus-4-6"],
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
assert.equal(config, undefined);
|
|
324
|
+
} finally {
|
|
325
|
+
process.chdir(originalCwd);
|
|
326
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
327
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
328
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
329
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
155
333
|
test("selectAndApplyModel honors explicit phase models without downgrading (#3617)", async () => {
|
|
156
334
|
const originalCwd = process.cwd();
|
|
157
335
|
const originalGsdHome = process.env.GSD_HOME;
|
|
@@ -61,6 +61,7 @@ async function runSuccessfulFinalize(s: AutoSession) {
|
|
|
61
61
|
},
|
|
62
62
|
stopAuto: async () => {},
|
|
63
63
|
pauseAuto: async () => {},
|
|
64
|
+
checkpointWorkflowDatabase() {},
|
|
64
65
|
updateProgressWidget() {},
|
|
65
66
|
postUnitPreVerification: async () => "continue",
|
|
66
67
|
runPostUnitVerification: async () => "continue",
|
|
@@ -410,9 +411,13 @@ test("runFinalize stops before merge when an isolated unit leaks app files into
|
|
|
410
411
|
const notifications: Array<{ message: string; level?: string }> = [];
|
|
411
412
|
const stopCalls: Array<{ reason?: string; preserve?: boolean }> = [];
|
|
412
413
|
const mergeCalls: string[] = [];
|
|
414
|
+
const checkpointCalls: string[] = [];
|
|
413
415
|
const result = await runFinalizeWithDeps(
|
|
414
416
|
s,
|
|
415
417
|
{
|
|
418
|
+
checkpointWorkflowDatabase() {
|
|
419
|
+
checkpointCalls.push("checkpoint");
|
|
420
|
+
},
|
|
416
421
|
stopAuto: async (_ctx: unknown, _pi: unknown, reason?: string, options?: { preserveCompletedMilestoneBranch?: boolean }) => {
|
|
417
422
|
stopCalls.push({ reason, preserve: options?.preserveCompletedMilestoneBranch });
|
|
418
423
|
},
|
|
@@ -438,16 +443,53 @@ test("runFinalize stops before merge when an isolated unit leaks app files into
|
|
|
438
443
|
|
|
439
444
|
assert.equal(result.action, "break");
|
|
440
445
|
assert.equal(result.reason, "root-write-leak");
|
|
446
|
+
assert.deepEqual(checkpointCalls, ["checkpoint"], "root-write leak should flush DB before stopAuto");
|
|
441
447
|
assert.deepEqual(stopCalls, [{ reason: "Root-write leak during isolated auto-mode", preserve: true }]);
|
|
442
448
|
assert.deepEqual(mergeCalls, [], "root-write leak must stop before merge preflight");
|
|
443
449
|
const message = notifications.find((n) => n.level === "error")?.message ?? "";
|
|
444
450
|
assert.match(message, /execute-task M001\/S01\/T01/);
|
|
445
451
|
assert.match(message, /Project root:/);
|
|
446
452
|
assert.match(message, /Expected worktree:/);
|
|
447
|
-
assert.
|
|
453
|
+
assert.doesNotMatch(message, /index\.html/);
|
|
448
454
|
assert.match(message, /tests\/verify-s09\.sh/);
|
|
449
455
|
});
|
|
450
456
|
|
|
457
|
+
test("runFinalize ignores tracked root artifact changes during isolated units", async (t) => {
|
|
458
|
+
const root = mkdtempSync(join(tmpdir(), "gsd-root-leak-tracked-"));
|
|
459
|
+
const worktree = join(root, ".gsd", "worktrees", "M001");
|
|
460
|
+
t.after(() => {
|
|
461
|
+
rmSync(root, { recursive: true, force: true });
|
|
462
|
+
});
|
|
463
|
+
initRepo(root);
|
|
464
|
+
writeFileSync(join(root, "openapi.json"), "{}\n");
|
|
465
|
+
runGit(root, ["add", "openapi.json"]);
|
|
466
|
+
runGit(root, ["commit", "-m", "chore: add generated spec"]);
|
|
467
|
+
mkdirSync(worktree, { recursive: true });
|
|
468
|
+
|
|
469
|
+
const s = new AutoSession();
|
|
470
|
+
s.basePath = worktree;
|
|
471
|
+
s.originalBasePath = root;
|
|
472
|
+
s.currentMilestoneId = "M001";
|
|
473
|
+
s.rootWriteBaseline = captureRootDirtySnapshot(root);
|
|
474
|
+
s.currentUnit = {
|
|
475
|
+
type: "execute-task",
|
|
476
|
+
id: "M001/S01/T01",
|
|
477
|
+
startedAt: Date.now(),
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
writeFileSync(join(root, "openapi.json"), "{\"generated\":true}\n");
|
|
481
|
+
|
|
482
|
+
const stopCalls: string[] = [];
|
|
483
|
+
const result = await runFinalizeWithDeps(s, {
|
|
484
|
+
stopAuto: async (_ctx: unknown, _pi: unknown, reason?: string) => {
|
|
485
|
+
stopCalls.push(reason ?? "");
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
assert.equal(result.action, "next");
|
|
490
|
+
assert.deepEqual(stopCalls, []);
|
|
491
|
+
});
|
|
492
|
+
|
|
451
493
|
test("runFinalize allows root .gsd-only changes during isolated units", async (t) => {
|
|
452
494
|
const root = mkdtempSync(join(tmpdir(), "gsd-root-leak-gsd-"));
|
|
453
495
|
const worktree = join(root, ".gsd", "worktrees", "M001");
|
|
@@ -838,6 +838,60 @@ test("verifyExpectedArtifact plan-slice fails when a task plan file is missing (
|
|
|
838
838
|
}
|
|
839
839
|
});
|
|
840
840
|
|
|
841
|
+
test("verifyExpectedArtifact accepts flat-phase plan-slice with embedded tasks and no task plan files", () => {
|
|
842
|
+
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
|
843
|
+
try {
|
|
844
|
+
const phaseDir = join(base, ".gsd", "phases", "01-test");
|
|
845
|
+
mkdirSync(join(phaseDir, "tasks"), { recursive: true });
|
|
846
|
+
writeFileSync(join(phaseDir, "01-01-PLAN.md"), [
|
|
847
|
+
"# S01: Test Slice",
|
|
848
|
+
"",
|
|
849
|
+
"<tasks>",
|
|
850
|
+
"- [ ] **T01**: Implement feature _(1h)_",
|
|
851
|
+
" - Files: `src/index.ts`",
|
|
852
|
+
" - Verify: pnpm test",
|
|
853
|
+
"- [ ] **T02**: Write tests _(1h)_",
|
|
854
|
+
"</tasks>",
|
|
855
|
+
].join("\n"));
|
|
856
|
+
|
|
857
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
858
|
+
assert.equal(result, true, "flat-phase embedded tasks should not require tasks/T##-PLAN.md files");
|
|
859
|
+
} finally {
|
|
860
|
+
cleanup(base);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("verifyExpectedArtifact plan-slice still verifies per-task files for legacy ## Tasks plan with a stray empty <tasks> block", () => {
|
|
865
|
+
const base = makeTmpBase();
|
|
866
|
+
try {
|
|
867
|
+
const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
868
|
+
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
869
|
+
// Legacy layout: real tasks live in a "## Tasks" section with separate
|
|
870
|
+
// per-task PLAN files, but the plan also carries an empty <tasks></tasks>
|
|
871
|
+
// block. The empty block must NOT flip the plan into flat-phase mode and
|
|
872
|
+
// skip the per-task artifact checks.
|
|
873
|
+
const planContent = [
|
|
874
|
+
"# S01: Test Slice",
|
|
875
|
+
"",
|
|
876
|
+
"<tasks>",
|
|
877
|
+
"</tasks>",
|
|
878
|
+
"",
|
|
879
|
+
"## Tasks",
|
|
880
|
+
"",
|
|
881
|
+
"- [ ] **T01: First task** `est:1h`",
|
|
882
|
+
"- [ ] **T02: Second task** `est:2h`",
|
|
883
|
+
].join("\n");
|
|
884
|
+
writeFileSync(planPath, planContent);
|
|
885
|
+
// Only T01-PLAN.md exists; T02's artifact is missing.
|
|
886
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
|
|
887
|
+
|
|
888
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
889
|
+
assert.equal(result, false, "a stray <tasks> block must not skip per-task file verification for a legacy ## Tasks plan");
|
|
890
|
+
} finally {
|
|
891
|
+
cleanup(base);
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
841
895
|
test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", () => {
|
|
842
896
|
const base = makeTmpBase();
|
|
843
897
|
try {
|
|
@@ -124,3 +124,44 @@ test("forceStopAutoRemote escalates a live remote PID and releases worker state"
|
|
|
124
124
|
);
|
|
125
125
|
assert.equal(readCrashLock(base), null, "force stop should remove the visible remote lock");
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
test("forceStopAutoRemote does not SIGKILL a PID that exits during the grace window", (t) => {
|
|
129
|
+
const base = makeBase();
|
|
130
|
+
t.after(() => cleanup(base));
|
|
131
|
+
|
|
132
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
133
|
+
const workerId = registerAutoWorker({ projectRootRealpath: normalizeRealPath(base) });
|
|
134
|
+
const pid = 525_252;
|
|
135
|
+
setWorkerPid(workerId, pid);
|
|
136
|
+
writeLegacyLock(base, pid);
|
|
137
|
+
|
|
138
|
+
// The process is alive until it receives SIGTERM, then exits cooperatively
|
|
139
|
+
// (liveness probe throws ESRCH) — so the grace loop should break before SIGKILL.
|
|
140
|
+
const signals: Array<NodeJS.Signals | 0> = [];
|
|
141
|
+
let alive = true;
|
|
142
|
+
const originalKill = process.kill;
|
|
143
|
+
process.kill = ((target: number, signal?: NodeJS.Signals | number) => {
|
|
144
|
+
assert.equal(target, pid);
|
|
145
|
+
if (signal === 0) {
|
|
146
|
+
if (!alive) {
|
|
147
|
+
const err = new Error("no such process") as NodeJS.ErrnoException;
|
|
148
|
+
err.code = "ESRCH";
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
signals.push((signal ?? 0) as NodeJS.Signals | 0);
|
|
154
|
+
if (signal === "SIGTERM") alive = false; // cooperative exit on SIGTERM
|
|
155
|
+
return true;
|
|
156
|
+
}) as typeof process.kill;
|
|
157
|
+
t.after(() => {
|
|
158
|
+
process.kill = originalKill;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const result = forceStopAutoRemote(base);
|
|
162
|
+
|
|
163
|
+
assert.deepEqual(result, { found: true, pid });
|
|
164
|
+
assert.ok(signals.includes("SIGTERM"), "force stop should request graceful termination first");
|
|
165
|
+
assert.ok(!signals.includes("SIGKILL"), "force stop must not escalate when the PID exits during the grace window");
|
|
166
|
+
assert.equal(readCrashLock(base), null, "force stop should remove the visible remote lock");
|
|
167
|
+
});
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
6
9
|
|
|
7
10
|
import {
|
|
8
11
|
autoSession,
|
|
@@ -11,6 +14,10 @@ import {
|
|
|
11
14
|
getAutoRuntimeSnapshot,
|
|
12
15
|
recordAutoToolSurfaceSnapshot,
|
|
13
16
|
} from "../auto-runtime-state.ts";
|
|
17
|
+
import {
|
|
18
|
+
readUnitHarnessAbort,
|
|
19
|
+
recordUnitHarnessAbort,
|
|
20
|
+
} from "../unit-runtime.ts";
|
|
14
21
|
|
|
15
22
|
test("getAutoRuntimeSnapshot includes orchestration phase when available", () => {
|
|
16
23
|
autoSession.reset();
|
|
@@ -81,6 +88,40 @@ test("clearToolInvocationError clears stale tool error state for active auto ses
|
|
|
81
88
|
autoSession.reset();
|
|
82
89
|
});
|
|
83
90
|
|
|
91
|
+
test("clearToolInvocationError clears stale tool error even when a harness abort is durable", () => {
|
|
92
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-auto-runtime-state-"));
|
|
93
|
+
const startedAt = 123456;
|
|
94
|
+
try {
|
|
95
|
+
autoSession.reset();
|
|
96
|
+
autoSession.active = true;
|
|
97
|
+
autoSession.basePath = base;
|
|
98
|
+
autoSession.setCurrentUnit({
|
|
99
|
+
type: "gate-evaluate",
|
|
100
|
+
id: "M001/S01/gates+Q3",
|
|
101
|
+
startedAt,
|
|
102
|
+
workspaceRoot: base,
|
|
103
|
+
});
|
|
104
|
+
autoSession.lastToolInvocationError = "gsd_save_gate_result: simulated stale tool error";
|
|
105
|
+
recordUnitHarnessAbort(base, "gate-evaluate", "M001/S01/gates+Q3", startedAt, {
|
|
106
|
+
kind: "tool-error",
|
|
107
|
+
reason: "Tool execution failed before the unit could complete its gate evaluation.",
|
|
108
|
+
toolName: "gsd_uat_exec",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
clearToolInvocationError();
|
|
112
|
+
|
|
113
|
+
assert.equal(autoSession.lastToolInvocationError, null);
|
|
114
|
+
assert.equal(
|
|
115
|
+
readUnitHarnessAbort(base, "gate-evaluate", "M001/S01/gates+Q3", startedAt)?.kind,
|
|
116
|
+
"tool-error",
|
|
117
|
+
"durable harness abort remains available for result-save blocking",
|
|
118
|
+
);
|
|
119
|
+
} finally {
|
|
120
|
+
autoSession.reset();
|
|
121
|
+
rmSync(base, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
84
125
|
test("getAutoRuntimeSnapshot omits orchestration phase when seam not wired", () => {
|
|
85
126
|
autoSession.reset();
|
|
86
127
|
|
|
@@ -121,8 +121,8 @@ describe("AutoSession.scope — project mode (basePath equals originalBasePath)"
|
|
|
121
121
|
|
|
122
122
|
assert.ok(s.scope, "scope should be set");
|
|
123
123
|
const gsd = join(projectDir, ".gsd");
|
|
124
|
-
assert.equal(s.scope.contextFile(), join(gsd, "
|
|
125
|
-
assert.equal(s.scope.roadmapFile(), join(gsd, "
|
|
124
|
+
assert.equal(s.scope.contextFile(), join(gsd, "phases", "02-m002", "02-CONTEXT.md"));
|
|
125
|
+
assert.equal(s.scope.roadmapFile(), join(gsd, "phases", "02-m002", "02-ROADMAP.md"));
|
|
126
126
|
assert.equal(s.scope.stateFile(), join(gsd, "STATE.md"));
|
|
127
127
|
});
|
|
128
128
|
});
|
|
@@ -236,8 +236,8 @@ describe("AutoSession.scope — milestoneId change rebuilds scope", () => {
|
|
|
236
236
|
assert.ok(ctxM001, "M001 contextFile should be set");
|
|
237
237
|
assert.ok(ctxM002, "M002 contextFile should be set");
|
|
238
238
|
assert.notEqual(ctxM001, ctxM002, "contextFile must differ between milestone IDs");
|
|
239
|
-
assert.ok(ctxM001.includes("
|
|
240
|
-
assert.ok(ctxM002.includes("
|
|
239
|
+
assert.ok(ctxM001.includes("01-m001"), "M001 path should contain its phase directory");
|
|
240
|
+
assert.ok(ctxM002.includes("02-m002"), "M002 path should contain its phase directory");
|
|
241
241
|
});
|
|
242
242
|
});
|
|
243
243
|
|
|
@@ -17,14 +17,17 @@ import {
|
|
|
17
17
|
clearPendingAutoStart,
|
|
18
18
|
setPendingAutoStart,
|
|
19
19
|
} from "../guided-flow.ts";
|
|
20
|
+
import { closeDatabase, openDatabase } from "../gsd-db.ts";
|
|
20
21
|
|
|
21
22
|
test.afterEach(() => {
|
|
23
|
+
closeDatabase();
|
|
22
24
|
clearPendingAutoStart();
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
test("checkAutoStartAfterDiscuss waits until discussion artifacts exist before re-entering auto-mode", (t) => {
|
|
26
28
|
const base = mkdtempSync(join(tmpdir(), "gsd-bootstrap-await-"));
|
|
27
29
|
const notifications: string[] = [];
|
|
30
|
+
openDatabase(":memory:");
|
|
28
31
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
29
32
|
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
30
33
|
setPendingAutoStart(base, {
|