@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,447 @@
|
|
|
1
|
+
"""Tests for GsdMcpClient milestone creation (subprocess-owned, stream-driven).
|
|
2
|
+
|
|
3
|
+
These tests pin the A1 design from issue #1162's grilling:
|
|
4
|
+
- create_milestone spawns `gsd headless --supervised new-milestone`
|
|
5
|
+
- a background stream-reader drives NotificationService on blocker/terminal events
|
|
6
|
+
- cancel_milestone SIGTERMs the held subprocess
|
|
7
|
+
- respond_to_milestone_blocker writes an extension_ui_response to stdin
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from io import BytesIO
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
from unittest.mock import MagicMock, patch
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from open_gsd_hermes.config import GsdConfig
|
|
20
|
+
from open_gsd_hermes.gsd_client import GsdMcpClient
|
|
21
|
+
from open_gsd_hermes.types import ProgressSnapshot
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _config() -> GsdConfig:
|
|
25
|
+
return GsdConfig(cli_path="/usr/local/bin/gsd")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# create_milestone — argument construction
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _FakeProc:
|
|
34
|
+
"""Minimal stand-in for subprocess.Popen used by create_milestone."""
|
|
35
|
+
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
self.pid = 4242
|
|
38
|
+
self.stdin = MagicMock()
|
|
39
|
+
self.stdout = MagicMock()
|
|
40
|
+
self.stderr = BytesIO()
|
|
41
|
+
self.poll = MagicMock(return_value=None)
|
|
42
|
+
self.terminate = MagicMock()
|
|
43
|
+
self.wait = MagicMock(return_value=0)
|
|
44
|
+
self.kill = MagicMock()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_create_milestone_builds_supervised_new_milestone_command_with_context_text() -> None:
|
|
48
|
+
"""create_milestone must invoke `gsd headless --supervised new-milestone --context-text <spec>`."""
|
|
49
|
+
client = GsdMcpClient(_config())
|
|
50
|
+
fake_proc = _FakeProc()
|
|
51
|
+
|
|
52
|
+
captured: dict[str, object] = {}
|
|
53
|
+
|
|
54
|
+
def fake_popen(args, **kwargs): # type: ignore[no-untyped-def]
|
|
55
|
+
captured["args"] = args
|
|
56
|
+
captured["cwd"] = kwargs.get("cwd")
|
|
57
|
+
captured["env"] = kwargs.get("env")
|
|
58
|
+
return fake_proc
|
|
59
|
+
|
|
60
|
+
with patch.object(client, "ensure_version"):
|
|
61
|
+
with patch("subprocess.Popen", side_effect=fake_popen):
|
|
62
|
+
with patch.object(client, "_milestone_stream_loop"):
|
|
63
|
+
client.create_milestone("/proj", context_text="Build a REST API")
|
|
64
|
+
|
|
65
|
+
args = captured["args"]
|
|
66
|
+
assert args[0] == "/usr/local/bin/gsd"
|
|
67
|
+
assert "headless" in args
|
|
68
|
+
assert "--supervised" in args
|
|
69
|
+
assert "--response-timeout" in args
|
|
70
|
+
timeout_idx = args.index("--response-timeout")
|
|
71
|
+
assert args[timeout_idx + 1] == str(GsdMcpClient._SUPERVISED_RESPONSE_TIMEOUT_MS)
|
|
72
|
+
assert "new-milestone" in args
|
|
73
|
+
assert "--context-text" in args
|
|
74
|
+
text_idx = args.index("--context-text")
|
|
75
|
+
assert args[text_idx + 1] == "Build a REST API"
|
|
76
|
+
assert captured["cwd"] == "/proj"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_create_milestone_builds_command_with_context_file() -> None:
|
|
80
|
+
"""--file form passes --context <path> instead of --context-text."""
|
|
81
|
+
client = GsdMcpClient(_config())
|
|
82
|
+
fake_proc = _FakeProc()
|
|
83
|
+
captured: dict[str, object] = {}
|
|
84
|
+
|
|
85
|
+
def fake_popen(args, **kwargs): # type: ignore[no-untyped-def]
|
|
86
|
+
captured["args"] = args
|
|
87
|
+
return fake_proc
|
|
88
|
+
|
|
89
|
+
with patch.object(client, "ensure_version"):
|
|
90
|
+
with patch("subprocess.Popen", side_effect=fake_popen):
|
|
91
|
+
with patch.object(client, "_milestone_stream_loop"):
|
|
92
|
+
client.create_milestone("/proj", context_file="/path/to/spec.md")
|
|
93
|
+
|
|
94
|
+
args = captured["args"]
|
|
95
|
+
assert "--context" in args
|
|
96
|
+
ctx_idx = args.index("--context")
|
|
97
|
+
assert args[ctx_idx + 1] == "/path/to/spec.md"
|
|
98
|
+
assert "--context-text" not in args
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_create_milestone_requires_either_context_text_or_file() -> None:
|
|
102
|
+
"""No spec at all is a usage error, raised before any subprocess spawns."""
|
|
103
|
+
client = GsdMcpClient(_config())
|
|
104
|
+
with pytest.raises(ValueError, match="spec"):
|
|
105
|
+
client.create_milestone("/proj")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_create_milestone_rejects_both_context_text_and_file() -> None:
|
|
109
|
+
"""Ambiguous input (both forms) is a usage error."""
|
|
110
|
+
client = GsdMcpClient(_config())
|
|
111
|
+
with pytest.raises(ValueError, match="spec"):
|
|
112
|
+
client.create_milestone(
|
|
113
|
+
"/proj", context_text="x", context_file="/p.md"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# Stream reader — sessionId capture, blocker, terminal
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_create_milestone_returns_local_session_id_from_pid() -> None:
|
|
123
|
+
"""create_milestone returns a local session id without waiting for init_result."""
|
|
124
|
+
client = GsdMcpClient(_config())
|
|
125
|
+
fake_proc = _FakeProc()
|
|
126
|
+
|
|
127
|
+
with patch.object(client, "ensure_version"):
|
|
128
|
+
with patch("subprocess.Popen", return_value=fake_proc):
|
|
129
|
+
with patch.object(client, "_milestone_stream_loop"):
|
|
130
|
+
session_id = client.create_milestone("/proj", context_text="spec")
|
|
131
|
+
|
|
132
|
+
assert session_id == "milestone-4242"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_stream_loop_captures_session_id_from_init_result() -> None:
|
|
136
|
+
"""The reader must pull sessionId out of the init_result stream event."""
|
|
137
|
+
client = GsdMcpClient(_config())
|
|
138
|
+
client._handle_milestone_event({"type": "init_result", "sessionId": "S-789"})
|
|
139
|
+
|
|
140
|
+
assert client.milestone_session_id() == "S-789"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_stream_loop_notifies_blocker_on_supervised_select_request() -> None:
|
|
144
|
+
"""Supervised planning prompts (select/input/confirm) must fire notify_blocker."""
|
|
145
|
+
client = GsdMcpClient(_config())
|
|
146
|
+
notifications = MagicMock()
|
|
147
|
+
blocker_event = {
|
|
148
|
+
"type": "extension_ui_request",
|
|
149
|
+
"method": "select",
|
|
150
|
+
"id": "sel-1",
|
|
151
|
+
"title": "Choose milestone scope",
|
|
152
|
+
"options": ["A", "B"],
|
|
153
|
+
}
|
|
154
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
155
|
+
client._handle_milestone_event(blocker_event)
|
|
156
|
+
|
|
157
|
+
notifications.notify_blocker.assert_called_once()
|
|
158
|
+
assert client.milestone_pending_blocker_id() == "sel-1"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_stream_loop_notifies_blocker_on_extension_ui_request() -> None:
|
|
162
|
+
"""Blocked-notice notify events are fire-and-forget; they must not prompt /gsd reply."""
|
|
163
|
+
client = GsdMcpClient(_config())
|
|
164
|
+
notifications = MagicMock()
|
|
165
|
+
# Realistic blocked-notice message — headless auto-acks notify in supervised mode.
|
|
166
|
+
blocker_event = {
|
|
167
|
+
"type": "extension_ui_request",
|
|
168
|
+
"method": "notify",
|
|
169
|
+
"id": "blk-1",
|
|
170
|
+
"message": "Auto-mode paused — blocked: choose an option",
|
|
171
|
+
}
|
|
172
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
173
|
+
client._handle_milestone_event(blocker_event)
|
|
174
|
+
|
|
175
|
+
notifications.notify_blocker.assert_not_called()
|
|
176
|
+
assert client.milestone_pending_blocker_id() is None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_build_milestone_completion_message_uses_query_and_progress() -> None:
|
|
180
|
+
client = GsdMcpClient(_config())
|
|
181
|
+
with patch.object(
|
|
182
|
+
client,
|
|
183
|
+
"project_query",
|
|
184
|
+
return_value={"milestones": [{"id": "M002", "hasRoadmap": True}]},
|
|
185
|
+
):
|
|
186
|
+
with patch.object(
|
|
187
|
+
client,
|
|
188
|
+
"progress",
|
|
189
|
+
return_value=ProgressSnapshot(
|
|
190
|
+
active_milestone={"id": "M002"},
|
|
191
|
+
slices={"total": 3},
|
|
192
|
+
tasks={"total": 9},
|
|
193
|
+
),
|
|
194
|
+
):
|
|
195
|
+
msg = client._build_milestone_completion_message("/proj")
|
|
196
|
+
|
|
197
|
+
assert msg == "✅ Milestone M002 ready — 3 slices, 9 tasks. Run `/gsd auto` to start."
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_build_milestone_completion_message_falls_back_when_query_empty() -> None:
|
|
201
|
+
client = GsdMcpClient(_config())
|
|
202
|
+
with patch.object(client, "project_query", return_value={"milestones": []}):
|
|
203
|
+
msg = client._build_milestone_completion_message("/proj")
|
|
204
|
+
|
|
205
|
+
assert msg == "✅ Milestone created. Run `/gsd status` for details."
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_stream_loop_notifies_terminal_on_process_exit() -> None:
|
|
209
|
+
"""When the stream ends (process exited), a milestone completion notification fires."""
|
|
210
|
+
client = GsdMcpClient(_config())
|
|
211
|
+
notifications = MagicMock()
|
|
212
|
+
fake_proc = _FakeProc()
|
|
213
|
+
fake_proc.poll.return_value = 0
|
|
214
|
+
fake_proc.stdout.readline.side_effect = [b""]
|
|
215
|
+
client._milestone_session_id = "milestone-4242"
|
|
216
|
+
client._milestone_pending_blocker_id = "old-blocker"
|
|
217
|
+
client._milestone_pending_blocker_method = "input"
|
|
218
|
+
|
|
219
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
220
|
+
with patch.object(client, "_milestone_project_dir", "/proj"):
|
|
221
|
+
with patch.object(
|
|
222
|
+
client,
|
|
223
|
+
"_build_milestone_completion_message",
|
|
224
|
+
return_value="✅ Milestone M001 ready — 2 slices, 5 tasks. Run `/gsd auto` to start.",
|
|
225
|
+
):
|
|
226
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
227
|
+
client._milestone_stream_loop(fake_proc)
|
|
228
|
+
|
|
229
|
+
notifications.notify_milestone_complete.assert_called_once_with(
|
|
230
|
+
"✅ Milestone M001 ready — 2 slices, 5 tasks. Run `/gsd auto` to start."
|
|
231
|
+
)
|
|
232
|
+
notifications.notify_terminal.assert_not_called()
|
|
233
|
+
assert client.milestone_active() is False
|
|
234
|
+
assert client.milestone_session_id() is None
|
|
235
|
+
assert client.milestone_pending_blocker_id() is None
|
|
236
|
+
assert client._milestone_pending_blocker_method is None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_stream_loop_failure_reports_drained_stderr() -> None:
|
|
240
|
+
"""Failure notifications must use stderr retained by the drain thread."""
|
|
241
|
+
client = GsdMcpClient(_config())
|
|
242
|
+
notifications = MagicMock()
|
|
243
|
+
fake_proc = _FakeProc()
|
|
244
|
+
fake_proc.poll.return_value = 2
|
|
245
|
+
fake_proc.stderr = BytesIO(b"fatal diagnostics\n")
|
|
246
|
+
fake_proc.stdout.readline.side_effect = [b""]
|
|
247
|
+
|
|
248
|
+
client._drain_milestone_stderr(fake_proc)
|
|
249
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
250
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
251
|
+
client._milestone_stream_loop(fake_proc)
|
|
252
|
+
|
|
253
|
+
notifications.notify_terminal.assert_called_once_with(
|
|
254
|
+
"failed", "fatal diagnostics"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_stream_loop_releases_blocked_exit_with_pending_blocker() -> None:
|
|
259
|
+
"""Exit 10 after stdout EOF is terminal even with a pending blocker id."""
|
|
260
|
+
client = GsdMcpClient(_config())
|
|
261
|
+
notifications = MagicMock()
|
|
262
|
+
fake_proc = _FakeProc()
|
|
263
|
+
fake_proc.poll.return_value = 10
|
|
264
|
+
fake_proc.stdout.readline.side_effect = [b""]
|
|
265
|
+
client._milestone_session_id = "milestone-4242"
|
|
266
|
+
|
|
267
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
268
|
+
with patch.object(client, "_milestone_pending_blocker_id", "sel-1"):
|
|
269
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
270
|
+
client._milestone_stream_loop(fake_proc)
|
|
271
|
+
|
|
272
|
+
notifications.notify_terminal.assert_called_once_with("failed", "Planning blocked")
|
|
273
|
+
assert client.milestone_active() is False
|
|
274
|
+
assert client.milestone_session_id() is None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_stream_loop_releases_noninteractive_blocked_exit() -> None:
|
|
278
|
+
"""Exit 10 without a pending blocker must not leave milestone ownership stuck."""
|
|
279
|
+
client = GsdMcpClient(_config())
|
|
280
|
+
notifications = MagicMock()
|
|
281
|
+
fake_proc = _FakeProc()
|
|
282
|
+
fake_proc.poll.return_value = 10
|
|
283
|
+
fake_proc.stdout.readline.side_effect = [b""]
|
|
284
|
+
client._milestone_session_id = "milestone-4242"
|
|
285
|
+
|
|
286
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
287
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
288
|
+
client._milestone_stream_loop(fake_proc)
|
|
289
|
+
|
|
290
|
+
notifications.notify_terminal.assert_called_once_with("failed", "Planning blocked")
|
|
291
|
+
assert client.milestone_active() is False
|
|
292
|
+
assert client.milestone_session_id() is None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_stream_loop_retains_running_proc_on_stream_loss() -> None:
|
|
296
|
+
"""If stdout closes while the child is alive, /gsd cancel must still work."""
|
|
297
|
+
client = GsdMcpClient(_config())
|
|
298
|
+
notifications = MagicMock()
|
|
299
|
+
on_terminal = MagicMock()
|
|
300
|
+
fake_proc = _FakeProc()
|
|
301
|
+
fake_proc.poll.return_value = None
|
|
302
|
+
fake_proc.wait.side_effect = subprocess.TimeoutExpired(cmd="gsd", timeout=5)
|
|
303
|
+
fake_proc.stdout.readline.side_effect = [b""]
|
|
304
|
+
client._milestone_session_id = "milestone-4242"
|
|
305
|
+
|
|
306
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
307
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
308
|
+
with patch.object(client, "_milestone_on_terminal", on_terminal):
|
|
309
|
+
client._milestone_stream_loop(fake_proc)
|
|
310
|
+
|
|
311
|
+
notifications.notify_terminal.assert_called_once_with(
|
|
312
|
+
"failed", "stream lost — run /gsd cancel"
|
|
313
|
+
)
|
|
314
|
+
on_terminal.assert_called_once_with("failed")
|
|
315
|
+
assert client.milestone_active() is True
|
|
316
|
+
assert client.milestone_session_id() == "milestone-4242"
|
|
317
|
+
|
|
318
|
+
client.cancel_milestone()
|
|
319
|
+
|
|
320
|
+
fake_proc.terminate.assert_called_once()
|
|
321
|
+
assert client.milestone_active() is False
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def test_stream_loop_handles_blocking_command_block() -> None:
|
|
325
|
+
"""gsd-command-block failures must not be reported as milestone complete."""
|
|
326
|
+
client = GsdMcpClient(_config())
|
|
327
|
+
notifications = MagicMock()
|
|
328
|
+
fake_proc = _FakeProc()
|
|
329
|
+
client._milestone_session_id = "milestone-4242"
|
|
330
|
+
command_block = {
|
|
331
|
+
"type": "message_start",
|
|
332
|
+
"message": {
|
|
333
|
+
"role": "custom",
|
|
334
|
+
"customType": "gsd-command-block",
|
|
335
|
+
"content": (
|
|
336
|
+
"/gsd auto cannot run because the active milestone "
|
|
337
|
+
"is blocked by validation."
|
|
338
|
+
),
|
|
339
|
+
},
|
|
340
|
+
}
|
|
341
|
+
fake_proc.poll.return_value = 0
|
|
342
|
+
fake_proc.stdout.readline.side_effect = [
|
|
343
|
+
(json.dumps(command_block) + "\n").encode(),
|
|
344
|
+
b"",
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
348
|
+
with patch.object(client, "_milestone_notifications", notifications):
|
|
349
|
+
client._milestone_stream_loop(fake_proc)
|
|
350
|
+
|
|
351
|
+
notifications.notify_terminal.assert_called_with(
|
|
352
|
+
"failed",
|
|
353
|
+
"/gsd auto cannot run because the active milestone is blocked by validation.",
|
|
354
|
+
)
|
|
355
|
+
notifications.notify_milestone_complete.assert_not_called()
|
|
356
|
+
assert client.milestone_active() is False
|
|
357
|
+
assert client.milestone_session_id() is None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# ---------------------------------------------------------------------------
|
|
361
|
+
# cancel_milestone / respond_to_milestone_blocker / milestone_active
|
|
362
|
+
# ---------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def test_cancel_milestone_terminates_held_subprocess() -> None:
|
|
366
|
+
client = GsdMcpClient(_config())
|
|
367
|
+
fake_proc = _FakeProc()
|
|
368
|
+
|
|
369
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
370
|
+
client.cancel_milestone()
|
|
371
|
+
|
|
372
|
+
fake_proc.terminate.assert_called_once()
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def test_cancel_milestone_when_no_proc_is_a_noop() -> None:
|
|
376
|
+
"""Calling cancel with no active milestone proc must not raise."""
|
|
377
|
+
client = GsdMcpClient(_config())
|
|
378
|
+
# should not raise
|
|
379
|
+
client.cancel_milestone()
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def test_close_terminates_active_milestone_subprocess() -> None:
|
|
383
|
+
"""close() must tear down the owned milestone subprocess as well as MCP."""
|
|
384
|
+
client = GsdMcpClient(_config())
|
|
385
|
+
fake_proc = _FakeProc()
|
|
386
|
+
|
|
387
|
+
with (
|
|
388
|
+
patch.object(client, "_terminate_process"),
|
|
389
|
+
patch.object(client, "_milestone_proc", fake_proc),
|
|
390
|
+
):
|
|
391
|
+
client.close()
|
|
392
|
+
|
|
393
|
+
fake_proc.terminate.assert_called_once()
|
|
394
|
+
assert client.milestone_active() is False
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def test_respond_to_milestone_blocker_writes_extension_ui_response_to_stdin() -> None:
|
|
398
|
+
"""The reply must write the supervised-mode stdin JSONL protocol."""
|
|
399
|
+
client = GsdMcpClient(_config())
|
|
400
|
+
fake_proc = _FakeProc()
|
|
401
|
+
|
|
402
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
403
|
+
with patch.object(client, "_milestone_pending_blocker_id", "blk-7"):
|
|
404
|
+
with patch.object(client, "_milestone_pending_blocker_method", "input"):
|
|
405
|
+
client.respond_to_milestone_blocker("use option B")
|
|
406
|
+
|
|
407
|
+
assert fake_proc.stdin.write.called
|
|
408
|
+
written = fake_proc.stdin.write.call_args[0][0]
|
|
409
|
+
payload = json.loads(written.decode() if isinstance(written, bytes) else written)
|
|
410
|
+
assert payload["type"] == "extension_ui_response"
|
|
411
|
+
assert payload["id"] == "blk-7"
|
|
412
|
+
assert payload["value"] == "use option B"
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def test_respond_to_milestone_blocker_writes_confirmed_for_confirm_prompt() -> None:
|
|
416
|
+
"""Confirm blockers must answer with a confirmed boolean, not free text."""
|
|
417
|
+
client = GsdMcpClient(_config())
|
|
418
|
+
fake_proc = _FakeProc()
|
|
419
|
+
|
|
420
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
421
|
+
with patch.object(client, "_milestone_pending_blocker_id", "cfm-1"):
|
|
422
|
+
with patch.object(client, "_milestone_pending_blocker_method", "confirm"):
|
|
423
|
+
client.respond_to_milestone_blocker("yes")
|
|
424
|
+
|
|
425
|
+
written = fake_proc.stdin.write.call_args[0][0]
|
|
426
|
+
payload = json.loads(written.decode() if isinstance(written, bytes) else written)
|
|
427
|
+
assert payload["type"] == "extension_ui_response"
|
|
428
|
+
assert payload["id"] == "cfm-1"
|
|
429
|
+
assert payload["confirmed"] is True
|
|
430
|
+
assert "value" not in payload
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def test_respond_to_milestone_blocker_without_pending_blocker_raises() -> None:
|
|
434
|
+
"""No pending blocker id means nothing to reply to — fail loud, don't pretend."""
|
|
435
|
+
client = GsdMcpClient(_config())
|
|
436
|
+
fake_proc = _FakeProc()
|
|
437
|
+
with patch.object(client, "_milestone_proc", fake_proc):
|
|
438
|
+
with pytest.raises(RuntimeError, match="No pending blocker"):
|
|
439
|
+
client.respond_to_milestone_blocker("answer")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def test_milestone_active_reflects_held_proc() -> None:
|
|
443
|
+
client = GsdMcpClient(_config())
|
|
444
|
+
assert client.milestone_active() is False
|
|
445
|
+
with patch.object(client, "_milestone_proc", _FakeProc()):
|
|
446
|
+
assert client.milestone_active() is True
|
|
447
|
+
assert client.milestone_active() is False
|