@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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* doctor-workspace.test.ts — Parent-workspace declared-repository probe (#818).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - declared child repo path missing on disk → workspace_repo_path_missing
|
|
6
|
+
* - declared child repo path exists but is not a git repo → workspace_repo_not_a_repo
|
|
7
|
+
* - valid child repos → no workspace issues
|
|
8
|
+
* - single-repo (project-mode) project → probe is a no-op
|
|
9
|
+
* - non-git parent root is handled gracefully (the common layout)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { chmodSync, mkdtempSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { delimiter, join } from "node:path";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
|
|
19
|
+
import { runGSDDoctor } from "../doctor.ts";
|
|
20
|
+
import { GIT_NO_PROMPT_ENV } from "../git-constants.ts";
|
|
21
|
+
|
|
22
|
+
function gitInit(cwd: string): void {
|
|
23
|
+
execFileSync("git", ["init"], { cwd, stdio: "ignore" });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeParentPrefs(base: string, repos: Record<string, { path: string }>): void {
|
|
27
|
+
const repoLines = Object.entries(repos)
|
|
28
|
+
.map(([id, cfg]) => ` ${id}:\n path: ${cfg.path}`)
|
|
29
|
+
.join("\n");
|
|
30
|
+
writeFileSync(
|
|
31
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
32
|
+
`---\nversion: 1\nworkspace:\n mode: parent\n repositories:\n${repoLines}\n---\n`,
|
|
33
|
+
"utf-8",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test("doctor flags a declared child repo whose path is missing on disk", async () => {
|
|
38
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-missing-"));
|
|
39
|
+
try {
|
|
40
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
41
|
+
gitInit(base);
|
|
42
|
+
// `frontend` is declared but never created on disk.
|
|
43
|
+
writeParentPrefs(base, { frontend: { path: "frontend" } });
|
|
44
|
+
|
|
45
|
+
const report = await runGSDDoctor(base);
|
|
46
|
+
const issue = report.issues.find((i) => i.code === "workspace_repo_path_missing");
|
|
47
|
+
assert.ok(issue, "expected a workspace_repo_path_missing issue");
|
|
48
|
+
assert.equal(issue?.unitId, "workspace.repositories.frontend");
|
|
49
|
+
assert.match(issue?.message ?? "", /does not exist on disk/);
|
|
50
|
+
} finally {
|
|
51
|
+
rmSync(base, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("doctor flags a declared child repo path that is not a git repository", async () => {
|
|
56
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-notrepo-"));
|
|
57
|
+
try {
|
|
58
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
59
|
+
gitInit(base);
|
|
60
|
+
// `backend` exists as a plain directory but is not a git repo.
|
|
61
|
+
mkdirSync(join(base, "backend"), { recursive: true });
|
|
62
|
+
writeParentPrefs(base, { backend: { path: "backend" } });
|
|
63
|
+
|
|
64
|
+
const report = await runGSDDoctor(base);
|
|
65
|
+
const issue = report.issues.find((i) => i.code === "workspace_repo_not_a_repo");
|
|
66
|
+
assert.ok(issue, "expected a workspace_repo_not_a_repo issue");
|
|
67
|
+
assert.equal(issue?.unitId, "workspace.repositories.backend");
|
|
68
|
+
assert.match(issue?.message ?? "", /not a git repository/);
|
|
69
|
+
} finally {
|
|
70
|
+
rmSync(base, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("doctor reports no workspace issues when declared child repos are valid", async () => {
|
|
75
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-valid-"));
|
|
76
|
+
try {
|
|
77
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
78
|
+
gitInit(base);
|
|
79
|
+
mkdirSync(join(base, "frontend"), { recursive: true });
|
|
80
|
+
gitInit(join(base, "frontend"));
|
|
81
|
+
writeParentPrefs(base, { frontend: { path: "frontend" } });
|
|
82
|
+
|
|
83
|
+
const report = await runGSDDoctor(base);
|
|
84
|
+
const wsIssues = report.issues.filter(
|
|
85
|
+
(i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
|
|
86
|
+
);
|
|
87
|
+
assert.deepEqual(wsIssues, []);
|
|
88
|
+
} finally {
|
|
89
|
+
rmSync(base, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("doctor workspace git probe uses safe env and canonical toplevel comparison", async (t) => {
|
|
94
|
+
if (process.platform === "win32") {
|
|
95
|
+
t.skip("POSIX-only git shim regression");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const originalProcessGitDir = process.env.GIT_DIR;
|
|
100
|
+
const originalProcessGitWorkTree = process.env.GIT_WORK_TREE;
|
|
101
|
+
const originalGitEnvPath = GIT_NO_PROMPT_ENV.PATH;
|
|
102
|
+
const originalGitEnvRealGit = GIT_NO_PROMPT_ENV.GSD_REAL_GIT;
|
|
103
|
+
const originalGitEnvFakeCwd = GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD;
|
|
104
|
+
const originalGitEnvFakeToplevel = GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL;
|
|
105
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-safeenv-"));
|
|
106
|
+
|
|
107
|
+
t.after(() => {
|
|
108
|
+
if (originalProcessGitDir === undefined) delete process.env.GIT_DIR;
|
|
109
|
+
else process.env.GIT_DIR = originalProcessGitDir;
|
|
110
|
+
if (originalProcessGitWorkTree === undefined) delete process.env.GIT_WORK_TREE;
|
|
111
|
+
else process.env.GIT_WORK_TREE = originalProcessGitWorkTree;
|
|
112
|
+
if (originalGitEnvPath === undefined) delete GIT_NO_PROMPT_ENV.PATH;
|
|
113
|
+
else GIT_NO_PROMPT_ENV.PATH = originalGitEnvPath;
|
|
114
|
+
if (originalGitEnvRealGit === undefined) delete GIT_NO_PROMPT_ENV.GSD_REAL_GIT;
|
|
115
|
+
else GIT_NO_PROMPT_ENV.GSD_REAL_GIT = originalGitEnvRealGit;
|
|
116
|
+
if (originalGitEnvFakeCwd === undefined) delete GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD;
|
|
117
|
+
else GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD = originalGitEnvFakeCwd;
|
|
118
|
+
if (originalGitEnvFakeToplevel === undefined) delete GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL;
|
|
119
|
+
else GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL = originalGitEnvFakeToplevel;
|
|
120
|
+
rmSync(base, { recursive: true, force: true });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
124
|
+
const realChild = join(base, "frontend-real");
|
|
125
|
+
const linkedChild = join(base, "frontend");
|
|
126
|
+
mkdirSync(realChild, { recursive: true });
|
|
127
|
+
gitInit(realChild);
|
|
128
|
+
symlinkSync(realChild, linkedChild, "dir");
|
|
129
|
+
writeParentPrefs(base, { frontend: { path: "frontend" } });
|
|
130
|
+
|
|
131
|
+
const shimDir = join(base, "bin");
|
|
132
|
+
mkdirSync(shimDir, { recursive: true });
|
|
133
|
+
const realGit = execFileSync("which", ["git"], { encoding: "utf-8" }).trim();
|
|
134
|
+
const shim = join(shimDir, "git");
|
|
135
|
+
writeFileSync(
|
|
136
|
+
shim,
|
|
137
|
+
[
|
|
138
|
+
"#!/usr/bin/env bash",
|
|
139
|
+
"set -euo pipefail",
|
|
140
|
+
"if [ -n \"${GIT_DIR:-}\" ] || [ -n \"${GIT_WORK_TREE:-}\" ]; then",
|
|
141
|
+
" echo \"leaked git env\" >&2",
|
|
142
|
+
" exit 97",
|
|
143
|
+
"fi",
|
|
144
|
+
"if [ \"${1:-}\" = \"rev-parse\" ] && [ \"${2:-}\" = \"--show-toplevel\" ] && [ \"$(pwd -P)\" = \"$GSD_FAKE_TOPLEVEL_CWD\" ]; then",
|
|
145
|
+
" printf '%s\\n' \"$GSD_FAKE_TOPLEVEL\"",
|
|
146
|
+
" exit 0",
|
|
147
|
+
"fi",
|
|
148
|
+
"exec \"$GSD_REAL_GIT\" \"$@\"",
|
|
149
|
+
"",
|
|
150
|
+
].join("\n"),
|
|
151
|
+
"utf-8",
|
|
152
|
+
);
|
|
153
|
+
chmodSync(shim, 0o755);
|
|
154
|
+
|
|
155
|
+
process.env.GIT_DIR = join(base, ".git");
|
|
156
|
+
process.env.GIT_WORK_TREE = base;
|
|
157
|
+
GIT_NO_PROMPT_ENV.PATH = `${shimDir}${delimiter}${process.env.PATH ?? ""}`;
|
|
158
|
+
GIT_NO_PROMPT_ENV.GSD_REAL_GIT = realGit;
|
|
159
|
+
GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD = realChild;
|
|
160
|
+
GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL = linkedChild;
|
|
161
|
+
|
|
162
|
+
const report = await runGSDDoctor(base);
|
|
163
|
+
const wsIssues = report.issues.filter(
|
|
164
|
+
(i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
|
|
165
|
+
);
|
|
166
|
+
assert.deepEqual(wsIssues, []);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("doctor workspace probe is a no-op for single-repo (project-mode) projects", async () => {
|
|
170
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-noproj-"));
|
|
171
|
+
try {
|
|
172
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
173
|
+
gitInit(base);
|
|
174
|
+
// No workspace config → project mode (default).
|
|
175
|
+
const report = await runGSDDoctor(base);
|
|
176
|
+
const wsIssues = report.issues.filter(
|
|
177
|
+
(i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
|
|
178
|
+
);
|
|
179
|
+
assert.deepEqual(wsIssues, []);
|
|
180
|
+
} finally {
|
|
181
|
+
rmSync(base, { recursive: true, force: true });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("doctor runs cleanly when the parent root is itself not a git repo (common layout)", async () => {
|
|
186
|
+
// Regression guard (#818 cross-cutting): a parent folder holding child git
|
|
187
|
+
// repos need not be a git repo itself. Doctor must not crash and must not
|
|
188
|
+
// treat the parent as a missing/non-repo child.
|
|
189
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-nongitparent-"));
|
|
190
|
+
try {
|
|
191
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
192
|
+
// NOTE: no gitInit(base) — parent is a plain folder.
|
|
193
|
+
mkdirSync(join(base, "frontend"), { recursive: true });
|
|
194
|
+
gitInit(join(base, "frontend"));
|
|
195
|
+
writeParentPrefs(base, { frontend: { path: "frontend" } });
|
|
196
|
+
|
|
197
|
+
const report = await runGSDDoctor(base);
|
|
198
|
+
// The declared child repo is valid; the non-git parent must not produce a
|
|
199
|
+
// child-repo issue or crash the run.
|
|
200
|
+
const wsIssues = report.issues.filter(
|
|
201
|
+
(i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
|
|
202
|
+
);
|
|
203
|
+
assert.deepEqual(wsIssues, []);
|
|
204
|
+
} finally {
|
|
205
|
+
rmSync(base, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// File Purpose: Tests the one-time migration from nested to flat-phase layout.
|
|
3
3
|
import test, { afterEach } from "node:test";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
|
-
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readdirSync, readFileSync, utimesSync } from "node:fs";
|
|
5
|
+
import { mkdtempSync, mkdirSync, renameSync, rmSync, writeFileSync, existsSync, readdirSync, readFileSync, utimesSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
_setFlatPhaseMigrationFsOpsForTest,
|
|
11
12
|
migrateToFlatPhase,
|
|
12
13
|
needsFlatPhaseMigration,
|
|
13
14
|
pruneStaleFlatPhaseBackups,
|
|
@@ -64,6 +65,42 @@ test("migrateToFlatPhase moves content from milestones/ to phases/", async () =>
|
|
|
64
65
|
assert.ok(!existsSync(join(base, ".gsd", "milestones")), "milestones/ should be removed");
|
|
65
66
|
});
|
|
66
67
|
|
|
68
|
+
test("migrateToFlatPhase ignores and removes stale phase dirs from prior aborted runs", async () => {
|
|
69
|
+
const base = makeTmp();
|
|
70
|
+
const stalePhaseDir = join(base, ".gsd", "phases", "99-stale-aborted-run");
|
|
71
|
+
mkdirSync(stalePhaseDir, { recursive: true });
|
|
72
|
+
|
|
73
|
+
await migrateToFlatPhase(base);
|
|
74
|
+
|
|
75
|
+
assert.ok(existsSync(join(base, ".gsd", "phases", "01-foundation")), "current DB phase should render");
|
|
76
|
+
assert.equal(existsSync(stalePhaseDir), false, "stale pre-existing phase dir should be removed");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("migrateToFlatPhase falls back to copy-delete when legacy rename is blocked", async (t) => {
|
|
80
|
+
const base = makeTmp();
|
|
81
|
+
const milestonesPath = join(base, ".gsd", "milestones");
|
|
82
|
+
const migratingPath = join(base, ".gsd", "milestones.migrating");
|
|
83
|
+
let blockedRenameAttempts = 0;
|
|
84
|
+
|
|
85
|
+
const restoreFsOps = _setFlatPhaseMigrationFsOpsForTest({
|
|
86
|
+
renameSync(src, dst) {
|
|
87
|
+
if (src === milestonesPath && dst === migratingPath) {
|
|
88
|
+
blockedRenameAttempts++;
|
|
89
|
+
throw Object.assign(new Error("simulated busy handle"), { code: "EPERM" });
|
|
90
|
+
}
|
|
91
|
+
return renameSync(src, dst);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
t.after(restoreFsOps);
|
|
95
|
+
|
|
96
|
+
await migrateToFlatPhase(base);
|
|
97
|
+
|
|
98
|
+
assert.equal(blockedRenameAttempts, 1, "test should exercise the rename fallback path");
|
|
99
|
+
assert.ok(existsSync(join(base, ".gsd", "phases", "01-foundation")), "flat phase should render");
|
|
100
|
+
assert.equal(existsSync(milestonesPath), false, "legacy milestones dir should be removed");
|
|
101
|
+
assert.equal(existsSync(migratingPath), false, "staging dir should be removed after success");
|
|
102
|
+
});
|
|
103
|
+
|
|
67
104
|
test("migrateToFlatPhase creates a backup", async () => {
|
|
68
105
|
const base = makeTmp();
|
|
69
106
|
await migrateToFlatPhase(base);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Regression
|
|
3
|
-
*
|
|
4
|
-
* per request — routing only degrades quality with no cost benefit.
|
|
2
|
+
* Regression tests for flat-rate provider detection and the explicit opt-out
|
|
3
|
+
* that suppresses dynamic routing for subscription providers.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
import { describe, test } from "node:test";
|
|
@@ -9,7 +8,7 @@ import assert from "node:assert/strict";
|
|
|
9
8
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
10
9
|
import { tmpdir } from "node:os";
|
|
11
10
|
import { join } from "node:path";
|
|
12
|
-
import { buildFlatRateContext, isFlatRateProvider, resolvePreferredModelConfig } from "../auto-model-selection.ts";
|
|
11
|
+
import { buildFlatRateContext, isFlatRateProvider, resolvePreferredModelConfig, selectAndApplyModel } from "../auto-model-selection.ts";
|
|
13
12
|
|
|
14
13
|
describe("flat-rate provider routing guard (#3453)", () => {
|
|
15
14
|
|
|
@@ -35,23 +34,18 @@ describe("flat-rate provider routing guard (#3453)", () => {
|
|
|
35
34
|
assert.equal(isFlatRateProvider("openai"), false);
|
|
36
35
|
});
|
|
37
36
|
|
|
38
|
-
test("resolvePreferredModelConfig
|
|
37
|
+
test("resolvePreferredModelConfig synthesizes routing config for copilot start model when routing is enabled", () => {
|
|
39
38
|
const originalCwd = process.cwd();
|
|
40
39
|
const originalGsdHome = process.env.GSD_HOME;
|
|
41
40
|
const tempProject = mkdtempSync(join(tmpdir(), "gsd-flat-rate-project-"));
|
|
42
41
|
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-flat-rate-home-"));
|
|
43
42
|
|
|
44
|
-
// When the user's start model is on a flat-rate provider,
|
|
45
|
-
// resolvePreferredModelConfig should not synthesize a routing
|
|
46
|
-
// config from tier_models — it should return undefined so the
|
|
47
|
-
// user's selected model is preserved.
|
|
48
43
|
try {
|
|
49
44
|
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
50
45
|
writeFileSync(
|
|
51
46
|
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
52
47
|
[
|
|
53
48
|
"---",
|
|
54
|
-
"token_profile: burn-max",
|
|
55
49
|
"dynamic_routing:",
|
|
56
50
|
" enabled: true",
|
|
57
51
|
" tier_models:",
|
|
@@ -70,10 +64,83 @@ describe("flat-rate provider routing guard (#3453)", () => {
|
|
|
70
64
|
id: "claude-sonnet-4",
|
|
71
65
|
});
|
|
72
66
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
assert.deepEqual(result, {
|
|
68
|
+
primary: "claude-opus-4-6",
|
|
69
|
+
fallbacks: [],
|
|
70
|
+
source: "synthesized",
|
|
71
|
+
});
|
|
72
|
+
} finally {
|
|
73
|
+
process.chdir(originalCwd);
|
|
74
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
75
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
76
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
77
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("selectAndApplyModel runs synthesized dynamic routing for flat-rate start model", async () => {
|
|
82
|
+
const originalCwd = process.cwd();
|
|
83
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
84
|
+
const tempProject = mkdtempSync(join(tmpdir(), "gsd-flat-rate-project-"));
|
|
85
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-flat-rate-home-"));
|
|
86
|
+
const setModelCalls: string[] = [];
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
90
|
+
writeFileSync(
|
|
91
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
92
|
+
[
|
|
93
|
+
"---",
|
|
94
|
+
"dynamic_routing:",
|
|
95
|
+
" enabled: true",
|
|
96
|
+
" hooks: false",
|
|
97
|
+
" budget_pressure: false",
|
|
98
|
+
" tier_models:",
|
|
99
|
+
" light: claude-haiku-4-5",
|
|
100
|
+
" standard: claude-sonnet-4-6",
|
|
101
|
+
" heavy: claude-opus-4-6",
|
|
102
|
+
"---",
|
|
103
|
+
].join("\n"),
|
|
104
|
+
"utf-8",
|
|
105
|
+
);
|
|
106
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
107
|
+
process.chdir(tempProject);
|
|
108
|
+
|
|
109
|
+
const availableModels = [
|
|
110
|
+
{ id: "claude-haiku-4-5", provider: "claude-code", api: "anthropic-messages" },
|
|
111
|
+
{ id: "claude-sonnet-4-6", provider: "claude-code", api: "anthropic-messages" },
|
|
112
|
+
{ id: "claude-opus-4-6", provider: "claude-code", api: "anthropic-messages" },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const result = await selectAndApplyModel(
|
|
116
|
+
{
|
|
117
|
+
modelRegistry: { getAvailable: () => availableModels },
|
|
118
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
119
|
+
ui: { notify: () => {} },
|
|
120
|
+
model: { provider: "claude-code", id: "claude-opus-4-6", api: "anthropic-messages" },
|
|
121
|
+
} as any,
|
|
122
|
+
{
|
|
123
|
+
setModel: async (model: { provider: string; id: string }) => {
|
|
124
|
+
setModelCalls.push(`${model.provider}/${model.id}`);
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
127
|
+
emitBeforeModelSelect: async () => undefined,
|
|
128
|
+
getActiveTools: () => [],
|
|
129
|
+
emitAdjustToolSet: async () => undefined,
|
|
130
|
+
setActiveTools: () => {},
|
|
131
|
+
} as any,
|
|
132
|
+
"execute-task",
|
|
133
|
+
"M001/S01/T01",
|
|
134
|
+
tempProject,
|
|
135
|
+
undefined,
|
|
136
|
+
false,
|
|
137
|
+
{ provider: "claude-code", id: "claude-opus-4-6" },
|
|
138
|
+
undefined,
|
|
139
|
+
true,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
assert.deepEqual(setModelCalls, ["claude-code/claude-sonnet-4-6"]);
|
|
143
|
+
assert.deepEqual(result.routing, { tier: "standard", modelDowngraded: true });
|
|
77
144
|
} finally {
|
|
78
145
|
process.chdir(originalCwd);
|
|
79
146
|
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
@@ -220,9 +287,9 @@ describe("buildFlatRateContext()", () => {
|
|
|
220
287
|
});
|
|
221
288
|
});
|
|
222
289
|
|
|
223
|
-
// ─── #4386: allow_flat_rate_providers
|
|
290
|
+
// ─── #4386/#1115: allow_flat_rate_providers override ────────────────────────
|
|
224
291
|
|
|
225
|
-
describe("flat-rate routing
|
|
292
|
+
describe("flat-rate routing override (#4386/#1115)", () => {
|
|
226
293
|
function withPrefs(prefsYaml: string, fn: () => void): void {
|
|
227
294
|
const originalCwd = process.cwd();
|
|
228
295
|
const originalGsdHome = process.env.GSD_HOME;
|
|
@@ -247,7 +314,7 @@ describe("flat-rate routing opt-in (#4386)", () => {
|
|
|
247
314
|
}
|
|
248
315
|
}
|
|
249
316
|
|
|
250
|
-
test("default (
|
|
317
|
+
test("default (allow absent): flat-rate start model still synthesizes routing config", () => {
|
|
251
318
|
withPrefs(
|
|
252
319
|
[
|
|
253
320
|
"dynamic_routing:",
|
|
@@ -260,12 +327,13 @@ describe("flat-rate routing opt-in (#4386)", () => {
|
|
|
260
327
|
provider: "claude-code",
|
|
261
328
|
id: "claude-opus-4-6",
|
|
262
329
|
});
|
|
263
|
-
assert.
|
|
330
|
+
assert.ok(result, "routing config should be synthesized");
|
|
331
|
+
assert.equal(result!.primary, "claude-opus-4-6");
|
|
264
332
|
},
|
|
265
333
|
);
|
|
266
334
|
});
|
|
267
335
|
|
|
268
|
-
test("
|
|
336
|
+
test("explicit allow: synthesizes a routing config for flat-rate start model", () => {
|
|
269
337
|
withPrefs(
|
|
270
338
|
[
|
|
271
339
|
"dynamic_routing:",
|
|
@@ -14,6 +14,7 @@ import { tmpdir } from "node:os";
|
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
checkAutoStartAfterDiscuss,
|
|
17
|
+
_getPendingAutoStart,
|
|
17
18
|
setPendingAutoStart,
|
|
18
19
|
clearPendingAutoStart,
|
|
19
20
|
} from "../guided-flow.ts";
|
|
@@ -21,6 +22,7 @@ import { drainLogs } from "../workflow-logger.ts";
|
|
|
21
22
|
import {
|
|
22
23
|
openDatabase,
|
|
23
24
|
closeDatabase,
|
|
25
|
+
getMilestone,
|
|
24
26
|
insertMilestone,
|
|
25
27
|
} from "../gsd-db.ts";
|
|
26
28
|
|
|
@@ -59,6 +61,12 @@ function mkBase(): string {
|
|
|
59
61
|
return base;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
function mkFlatBase(): string {
|
|
65
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-gate1b-flat-"));
|
|
66
|
+
mkdirSync(join(base, ".gsd", "phases", "01-m001"), { recursive: true });
|
|
67
|
+
return base;
|
|
68
|
+
}
|
|
69
|
+
|
|
62
70
|
function writeContext(base: string): void {
|
|
63
71
|
writeFileSync(
|
|
64
72
|
join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
|
|
@@ -66,6 +74,13 @@ function writeContext(base: string): void {
|
|
|
66
74
|
);
|
|
67
75
|
}
|
|
68
76
|
|
|
77
|
+
function writeFlatContext(base: string): void {
|
|
78
|
+
writeFileSync(
|
|
79
|
+
join(base, ".gsd", "phases", "01-m001", "01-CONTEXT.md"),
|
|
80
|
+
"# M001: Test Milestone\n\nContext written by discuss phase.\n",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
69
84
|
describe("Gate 1b discussion handoff in checkAutoStartAfterDiscuss", () => {
|
|
70
85
|
let base: string;
|
|
71
86
|
let cap: MockCapture;
|
|
@@ -142,4 +157,78 @@ describe("Gate 1b discussion handoff in checkAutoStartAfterDiscuss", () => {
|
|
|
142
157
|
);
|
|
143
158
|
assert.equal(gate1bLog, undefined, "Gate 1b must not log when CONTEXT.md is absent");
|
|
144
159
|
});
|
|
160
|
+
|
|
161
|
+
test("flat-phase CONTEXT.md accepts handoff without a legacy milestone context file", () => {
|
|
162
|
+
base = mkFlatBase();
|
|
163
|
+
openDatabase(":memory:");
|
|
164
|
+
insertMilestone({ id: "M001", title: "Test Milestone", status: "queued" });
|
|
165
|
+
writeFlatContext(base);
|
|
166
|
+
|
|
167
|
+
cap = mkCapture();
|
|
168
|
+
setPendingAutoStart(base, {
|
|
169
|
+
basePath: base,
|
|
170
|
+
milestoneId: "M001",
|
|
171
|
+
startAuto: false,
|
|
172
|
+
ctx: mkCtx(cap),
|
|
173
|
+
pi: mkPi(cap),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const result = checkAutoStartAfterDiscuss();
|
|
177
|
+
|
|
178
|
+
assert.equal(result, true, "flat-phase context must be found by the handoff");
|
|
179
|
+
assert.equal(cap.messages.length, 0, "startAuto=false must suppress scheduling");
|
|
180
|
+
assert.deepEqual(cap.notifies[0], {
|
|
181
|
+
msg: "Milestone M001 context captured. Continuing the planning pipeline.",
|
|
182
|
+
level: "success",
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("DB-unavailable recovery blocks handoff instead of reporting success", () => {
|
|
187
|
+
base = mkFlatBase();
|
|
188
|
+
writeFlatContext(base);
|
|
189
|
+
|
|
190
|
+
cap = mkCapture();
|
|
191
|
+
setPendingAutoStart(base, {
|
|
192
|
+
basePath: base,
|
|
193
|
+
milestoneId: "M001",
|
|
194
|
+
startAuto: false,
|
|
195
|
+
ctx: mkCtx(cap),
|
|
196
|
+
pi: mkPi(cap),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const result = checkAutoStartAfterDiscuss();
|
|
200
|
+
|
|
201
|
+
assert.equal(result, false, "handoff must fail closed when the DB row cannot be confirmed");
|
|
202
|
+
assert.equal(cap.messages.length, 0, "must not schedule auto-start when DB is unavailable");
|
|
203
|
+
assert.equal(
|
|
204
|
+
cap.notifies.some(n => n.level === "success"),
|
|
205
|
+
false,
|
|
206
|
+
"must not report a successful handoff when DB recovery was skipped",
|
|
207
|
+
);
|
|
208
|
+
assert.ok(_getPendingAutoStart(base), "pending handoff should remain for a later recovery attempt");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("missing DB row with CONTEXT.md inserts a queued row and accepts handoff", () => {
|
|
212
|
+
base = mkFlatBase();
|
|
213
|
+
openDatabase(":memory:");
|
|
214
|
+
writeFlatContext(base);
|
|
215
|
+
|
|
216
|
+
cap = mkCapture();
|
|
217
|
+
setPendingAutoStart(base, {
|
|
218
|
+
basePath: base,
|
|
219
|
+
milestoneId: "M001",
|
|
220
|
+
startAuto: false,
|
|
221
|
+
ctx: mkCtx(cap),
|
|
222
|
+
pi: mkPi(cap),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const result = checkAutoStartAfterDiscuss();
|
|
226
|
+
|
|
227
|
+
assert.equal(result, true, "successful insert should recover the missing row");
|
|
228
|
+
assert.equal(getMilestone("M001")?.status, "queued");
|
|
229
|
+
assert.deepEqual(cap.notifies[0], {
|
|
230
|
+
msg: "Milestone M001 context captured. Continuing the planning pipeline.",
|
|
231
|
+
level: "success",
|
|
232
|
+
});
|
|
233
|
+
});
|
|
145
234
|
});
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
clearPendingAutoStart,
|
|
21
21
|
checkAutoStartAfterDiscuss,
|
|
22
22
|
} from "../guided-flow.ts";
|
|
23
|
+
import { closeDatabase, openDatabase } from "../gsd-db.ts";
|
|
23
24
|
|
|
24
25
|
function pendingInput(basePath: string, milestoneId: string) {
|
|
25
26
|
return {
|
|
@@ -34,6 +35,7 @@ function pendingInput(basePath: string, milestoneId: string) {
|
|
|
34
35
|
|
|
35
36
|
describe("#2985 Bug 3 — concurrent discuss sessions must be independent", () => {
|
|
36
37
|
beforeEach(() => {
|
|
38
|
+
closeDatabase();
|
|
37
39
|
clearPendingAutoStart();
|
|
38
40
|
});
|
|
39
41
|
|
|
@@ -75,6 +77,7 @@ describe("#2985 Bug 3 — concurrent discuss sessions must be independent", () =
|
|
|
75
77
|
|
|
76
78
|
describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", () => {
|
|
77
79
|
beforeEach(() => {
|
|
80
|
+
closeDatabase();
|
|
78
81
|
clearPendingAutoStart();
|
|
79
82
|
});
|
|
80
83
|
|
|
@@ -110,6 +113,7 @@ describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", (
|
|
|
110
113
|
test("checkAutoStartAfterDiscuss ignores missing manifest for single-milestone discuss on established project", () => {
|
|
111
114
|
const base = mkdtempSync(join(tmpdir(), "gsd-auto-start-manifest-"));
|
|
112
115
|
try {
|
|
116
|
+
openDatabase(":memory:");
|
|
113
117
|
const gsdDir = join(base, ".gsd");
|
|
114
118
|
const milestoneDir = join(gsdDir, "milestones", "M001");
|
|
115
119
|
mkdirSync(milestoneDir, { recursive: true });
|
|
@@ -132,6 +136,7 @@ test("checkAutoStartAfterDiscuss ignores missing manifest for single-milestone d
|
|
|
132
136
|
const started = checkAutoStartAfterDiscuss();
|
|
133
137
|
assert.equal(started, true, "project history alone should not require a manifest");
|
|
134
138
|
} finally {
|
|
139
|
+
closeDatabase();
|
|
135
140
|
clearPendingAutoStart();
|
|
136
141
|
rmSync(base, { recursive: true, force: true });
|
|
137
142
|
}
|
|
@@ -151,6 +156,7 @@ test("checkAutoStartAfterDiscuss(basePath) selects the matching pending entry wh
|
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
try {
|
|
159
|
+
openDatabase(":memory:");
|
|
154
160
|
clearPendingAutoStart();
|
|
155
161
|
writeReadyArtifacts(projectA, "M001");
|
|
156
162
|
writeReadyArtifacts(projectB, "M002");
|
|
@@ -172,6 +178,7 @@ test("checkAutoStartAfterDiscuss(basePath) selects the matching pending entry wh
|
|
|
172
178
|
assert.equal(getDiscussionMilestoneId(projectA), "M001", "projectA should remain pending");
|
|
173
179
|
assert.equal(getDiscussionMilestoneId(projectB), null, "projectB should be cleared after start");
|
|
174
180
|
} finally {
|
|
181
|
+
closeDatabase();
|
|
175
182
|
clearPendingAutoStart();
|
|
176
183
|
rmSync(projectA, { recursive: true, force: true });
|
|
177
184
|
rmSync(projectB, { recursive: true, force: true });
|
|
@@ -183,6 +190,7 @@ test("checkAutoStartAfterDiscuss can accept context handoff without scheduling a
|
|
|
183
190
|
let waitForIdleCalls = 0;
|
|
184
191
|
const notifications: string[] = [];
|
|
185
192
|
try {
|
|
193
|
+
openDatabase(":memory:");
|
|
186
194
|
const gsdDir = join(base, ".gsd");
|
|
187
195
|
const milestoneDir = join(gsdDir, "milestones", "M001");
|
|
188
196
|
mkdirSync(milestoneDir, { recursive: true });
|
|
@@ -215,6 +223,7 @@ test("checkAutoStartAfterDiscuss can accept context handoff without scheduling a
|
|
|
215
223
|
assert.equal(waitForIdleCalls, 0, "headless-owned auto start must not schedule guided-flow auto");
|
|
216
224
|
assert.equal(getDiscussionMilestoneId(base), null, "accepted handoff should still be cleared");
|
|
217
225
|
} finally {
|
|
226
|
+
closeDatabase();
|
|
218
227
|
clearPendingAutoStart();
|
|
219
228
|
rmSync(base, { recursive: true, force: true });
|
|
220
229
|
}
|
|
@@ -90,6 +90,8 @@ import { clearPathCache } from "../../paths.ts";
|
|
|
90
90
|
// Fixture Helpers
|
|
91
91
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
92
92
|
|
|
93
|
+
const UAT_VERDICT_GATE_RULE_NAME = "uat-verdict-gate (non-PASS observed; closeout enforces)";
|
|
94
|
+
|
|
93
95
|
function makeTempDir(): string {
|
|
94
96
|
return mkdtempSync(join(tmpdir(), "gsd-edge-cases-"));
|
|
95
97
|
}
|
|
@@ -280,7 +282,7 @@ function buildDispatchCtx(
|
|
|
280
282
|
|
|
281
283
|
function getUatVerdictGate(): import("../../auto-dispatch.ts").DispatchRule {
|
|
282
284
|
const rule = DISPATCH_RULES.find(
|
|
283
|
-
r => r.name ===
|
|
285
|
+
r => r.name === UAT_VERDICT_GATE_RULE_NAME,
|
|
284
286
|
);
|
|
285
287
|
assert.ok(rule, "uat-verdict-gate rule should be registered");
|
|
286
288
|
return rule;
|
|
@@ -867,7 +869,7 @@ describe("dispatch failure modes", () => {
|
|
|
867
869
|
// Verify critical ordering constraints
|
|
868
870
|
const summarizeIdx = ruleNames.indexOf("summarizing → complete-slice");
|
|
869
871
|
const runUatIdx = ruleNames.indexOf("run-uat (post-completion)");
|
|
870
|
-
const uatGateIdx = ruleNames.indexOf(
|
|
872
|
+
const uatGateIdx = ruleNames.indexOf(UAT_VERDICT_GATE_RULE_NAME);
|
|
871
873
|
const executeIdx = ruleNames.indexOf("executing → execute-task");
|
|
872
874
|
|
|
873
875
|
// summarizing should come before execute-task
|