@opengsd/gsd-pi 1.2.0-dev.84c56d87 → 1.2.0-dev.9ad8ae33
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/dist/headless-events.js +7 -5
- package/dist/mcp-server.js +2 -1
- package/dist/resource-loader.d.ts +10 -5
- package/dist/resource-loader.js +121 -6
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +5 -4
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
- package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
- package/dist/resources/extensions/async-jobs/index.js +65 -0
- package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
- package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
- package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
- package/dist/resources/extensions/bg-shell/overlay.js +9 -6
- package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
- package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
- package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
- package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
- package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +89 -54
- package/dist/resources/extensions/gsd/auto/phases.js +49 -6
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
- package/dist/resources/extensions/gsd/auto-dispatch.js +50 -58
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -13
- package/dist/resources/extensions/gsd/auto-post-unit.js +30 -12
- package/dist/resources/extensions/gsd/auto-prompts.js +78 -19
- package/dist/resources/extensions/gsd/auto-start.js +35 -15
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +5 -4
- package/dist/resources/extensions/gsd/auto-verification.js +23 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +14 -1
- package/dist/resources/extensions/gsd/auto.js +37 -1
- package/dist/resources/extensions/gsd/blocked-models.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +23 -6
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +145 -50
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +302 -80
- package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
- package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
- package/dist/resources/extensions/gsd/commands/context.js +16 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
- package/dist/resources/extensions/gsd/consent-question.js +353 -0
- package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
- package/dist/resources/extensions/gsd/constants.js +0 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
- package/dist/resources/extensions/gsd/db/queries.js +26 -0
- package/dist/resources/extensions/gsd/db-writer.js +8 -17
- package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
- package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
- package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
- package/dist/resources/extensions/gsd/files.js +33 -19
- package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
- package/dist/resources/extensions/gsd/gsd-db.js +2 -1
- package/dist/resources/extensions/gsd/guidance.js +60 -0
- package/dist/resources/extensions/gsd/guided-flow.js +6 -3
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
- package/dist/resources/extensions/gsd/milestone-closeout.js +85 -24
- package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
- package/dist/resources/extensions/gsd/milestone-reopen-events.js +3 -5
- package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
- package/dist/resources/extensions/gsd/preferences-models.js +2 -2
- package/dist/resources/extensions/gsd/projection-flush.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +7 -5
- package/dist/resources/extensions/gsd/prompts/system.md +5 -2
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
- package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
- package/dist/resources/extensions/gsd/session-lock.js +1 -1
- package/dist/resources/extensions/gsd/state.js +5 -0
- package/dist/resources/extensions/gsd/tool-contract.js +14 -3
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +22 -12
- package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
- package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
- package/dist/resources/extensions/gsd/uat-policy.js +40 -15
- package/dist/resources/extensions/gsd/unit-context-composer.js +65 -0
- package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
- package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +91 -0
- package/dist/resources/extensions/gsd/workflow-event-vocabulary.js +46 -0
- package/dist/resources/extensions/gsd/workflow-events.js +6 -18
- package/dist/resources/extensions/gsd/workflow-reconcile.js +21 -56
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +3 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +7 -1
- package/dist/resources/extensions/gsd/worktree.js +8 -1
- package/dist/resources/extensions/shared/gsd-browser-cli.js +45 -3
- package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
- package/dist/resources/shared/package-manager-detection.js +1 -1
- package/dist/resources/shared/package.json +3 -0
- package/dist/resources/skills/create-skill/SKILL.md +3 -0
- package/dist/resources/skills/create-skill/references/skill-structure.md +1 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +24 -1
- package/dist/update-cmd.js +20 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- 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/api/update/route.js.nft.json +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 +6 -6
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
- package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
- package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
- package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
- package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
- package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
- package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
- package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
- package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
- package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
- package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
- package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
- package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
- package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
- package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
- package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
- package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
- package/dist/web/standalone/node_modules/postcss/package.json +48 -48
- package/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/rpc.d.ts +1 -0
- package/packages/contracts/dist/rpc.d.ts.map +1 -1
- package/packages/contracts/dist/rpc.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/cli.js +10 -5
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +4 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +99 -38
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
- package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
- package/packages/pi-agent-core/dist/index.d.ts +1 -0
- package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/index.js +3 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/README.md +1 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts +2 -2
- package/packages/pi-ai/dist/image-models.generated.js +6 -6
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +419 -221
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +460 -261
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +12 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +12 -3
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
- package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
- package/packages/pi-coding-agent/dist/utils/shell.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/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +5 -4
- package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
- package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
- package/src/resources/extensions/async-jobs/index.ts +79 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
- package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
- package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
- package/src/resources/extensions/bg-shell/overlay.ts +9 -5
- package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
- package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
- package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +40 -1
- package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
- package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
- package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +98 -56
- package/src/resources/extensions/gsd/auto/phases.ts +65 -26
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -61
- package/src/resources/extensions/gsd/auto-model-selection.ts +41 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +33 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +115 -35
- package/src/resources/extensions/gsd/auto-start.ts +36 -18
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +4 -4
- package/src/resources/extensions/gsd/auto-verification.ts +26 -28
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -1
- package/src/resources/extensions/gsd/auto.ts +44 -1
- package/src/resources/extensions/gsd/blocked-models.ts +49 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +23 -6
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +163 -55
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +350 -86
- package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
- package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
- package/src/resources/extensions/gsd/commands/context.ts +16 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
- package/src/resources/extensions/gsd/consent-question.ts +431 -0
- package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
- package/src/resources/extensions/gsd/constants.ts +0 -3
- package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
- package/src/resources/extensions/gsd/db/queries.ts +37 -0
- package/src/resources/extensions/gsd/db-writer.ts +11 -19
- package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
- package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
- package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/files.ts +33 -12
- package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
- package/src/resources/extensions/gsd/gsd-db.ts +4 -3
- package/src/resources/extensions/gsd/guidance.ts +78 -0
- package/src/resources/extensions/gsd/guided-flow.ts +21 -26
- package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
- package/src/resources/extensions/gsd/milestone-closeout.ts +109 -24
- package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
- package/src/resources/extensions/gsd/milestone-reopen-events.ts +3 -6
- package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
- package/src/resources/extensions/gsd/preferences-models.ts +2 -1
- package/src/resources/extensions/gsd/projection-flush.ts +20 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +7 -5
- package/src/resources/extensions/gsd/prompts/system.md +5 -2
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
- package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
- package/src/resources/extensions/gsd/session-lock.ts +1 -1
- package/src/resources/extensions/gsd/state.ts +5 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +97 -1
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +198 -26
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +236 -0
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/consent-question.test.ts +351 -0
- package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +15 -4
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
- package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/guidance.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
- package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +138 -0
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -1
- package/src/resources/extensions/gsd/tool-contract.ts +38 -3
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +22 -12
- package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
- package/src/resources/extensions/gsd/uat-policy.ts +60 -15
- package/src/resources/extensions/gsd/unit-context-composer.ts +99 -0
- package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
- package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +131 -0
- package/src/resources/extensions/gsd/workflow-event-vocabulary.ts +59 -0
- package/src/resources/extensions/gsd/workflow-events.ts +12 -20
- package/src/resources/extensions/gsd/workflow-reconcile.ts +29 -62
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +3 -8
- package/src/resources/extensions/gsd/worktree-manager.ts +6 -1
- package/src/resources/extensions/gsd/worktree.ts +7 -1
- package/src/resources/extensions/shared/gsd-browser-cli.ts +54 -3
- package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
- package/src/resources/shared/package-manager-detection.ts +1 -1
- package/src/resources/shared/package.json +3 -0
- package/src/resources/skills/create-skill/SKILL.md +3 -0
- package/src/resources/skills/create-skill/references/skill-structure.md +1 -0
- package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
- package/dist/resources/skills/gsd-browser/SKILL.md +0 -41
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
- package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
- package/src/resources/skills/gsd-browser/SKILL.md +0 -41
- /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot confirmation token for destructive bash commands.
|
|
3
|
+
*
|
|
4
|
+
* The destructive-command guard hard-blocks classified commands (force push,
|
|
5
|
+
* rm -rf, SQL drop, etc.) in all modes. The block instructs the model to
|
|
6
|
+
* confirm via ask_user_questions and re-issue the command. This module is the
|
|
7
|
+
* missing escape hatch: it records the user's confirmation and lets the exact
|
|
8
|
+
* confirmed command through exactly once.
|
|
9
|
+
*
|
|
10
|
+
* Design constraints:
|
|
11
|
+
* - In-memory only, never persisted. A confirmation token written to disk
|
|
12
|
+
* could silently auto-approve a destructive command in a later session —
|
|
13
|
+
* confirmation must be re-obtained every process lifetime.
|
|
14
|
+
* - One-shot. Consuming a token clears it, so a second destructive command
|
|
15
|
+
* (even an identical one) re-blocks and re-prompts.
|
|
16
|
+
* - Command-bound. The token only matches the exact (normalized) command
|
|
17
|
+
* string the user confirmed. A reworded command re-blocks, which is safe.
|
|
18
|
+
* - Per basePath, so concurrent workspaces in one process never share tokens.
|
|
19
|
+
*
|
|
20
|
+
* Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { resolve } from "node:path";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Question-id substring that marks an ask_user_questions call as a
|
|
27
|
+
* destructive-command confirmation. The tool_result handler promotes the
|
|
28
|
+
* pending command to a confirmed token when an affirmative answer arrives for
|
|
29
|
+
* a question whose id contains this marker.
|
|
30
|
+
*/
|
|
31
|
+
export const DESTRUCTIVE_CONFIRM_GATE_MARKER = "destructive_confirm";
|
|
32
|
+
|
|
33
|
+
interface DestructiveConfirmationState {
|
|
34
|
+
/** Command awaiting confirmation, captured when the guard blocked it. */
|
|
35
|
+
pendingCommand: string | null;
|
|
36
|
+
/** Confirmed command cleared on first matching consume. */
|
|
37
|
+
confirmedCommand: string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const statesByBasePath = new Map<string, DestructiveConfirmationState>();
|
|
41
|
+
|
|
42
|
+
function stateKey(basePath: string): string {
|
|
43
|
+
return resolve(basePath);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getState(basePath: string): DestructiveConfirmationState {
|
|
47
|
+
const key = stateKey(basePath);
|
|
48
|
+
let state = statesByBasePath.get(key);
|
|
49
|
+
if (!state) {
|
|
50
|
+
state = { pendingCommand: null, confirmedCommand: null };
|
|
51
|
+
statesByBasePath.set(key, state);
|
|
52
|
+
}
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Normalize a command for stable matching across block → confirm → retry.
|
|
58
|
+
* Trims surrounding whitespace and collapses internal runs of whitespace so
|
|
59
|
+
* cosmetic reformatting of the same command still matches the token.
|
|
60
|
+
*/
|
|
61
|
+
export function normalizeDestructiveCommand(command: string): string {
|
|
62
|
+
return command.replace(/\s+/g, " ").trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Whether an ask_user_questions question id is a destructive-confirm gate.
|
|
67
|
+
*/
|
|
68
|
+
export function isDestructiveConfirmGateId(questionId: unknown): boolean {
|
|
69
|
+
return typeof questionId === "string" && questionId.includes(DESTRUCTIVE_CONFIRM_GATE_MARKER);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Record that a destructive command was blocked and is awaiting confirmation.
|
|
74
|
+
* Called by the guard at block time. Overwrites any prior pending command —
|
|
75
|
+
* only the most recently blocked command can be confirmed.
|
|
76
|
+
*/
|
|
77
|
+
export function requestDestructiveConfirmation(
|
|
78
|
+
command: string,
|
|
79
|
+
basePath: string = process.cwd(),
|
|
80
|
+
): void {
|
|
81
|
+
const state = getState(basePath);
|
|
82
|
+
state.pendingCommand = normalizeDestructiveCommand(command);
|
|
83
|
+
// A fresh request invalidates any stale confirmed token for a different
|
|
84
|
+
// command so confirmation cannot leak across distinct destructive actions.
|
|
85
|
+
state.confirmedCommand = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Promote the pending command to a confirmed, one-shot token. Called by the
|
|
90
|
+
* tool_result handler when the user gives an affirmative answer to a
|
|
91
|
+
* destructive-confirm gate. Returns the confirmed command, or null if there
|
|
92
|
+
* was nothing pending (e.g. confirmation arrived without a preceding block).
|
|
93
|
+
*/
|
|
94
|
+
export function confirmDestructiveCommand(
|
|
95
|
+
basePath: string = process.cwd(),
|
|
96
|
+
): string | null {
|
|
97
|
+
const state = getState(basePath);
|
|
98
|
+
if (!state.pendingCommand) return null;
|
|
99
|
+
state.confirmedCommand = state.pendingCommand;
|
|
100
|
+
state.pendingCommand = null;
|
|
101
|
+
return state.confirmedCommand;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check whether the given command has been confirmed, consuming the token if
|
|
106
|
+
* so. Returns true exactly once per confirmation; subsequent calls (or a
|
|
107
|
+
* non-matching command) return false. Called by the guard before blocking.
|
|
108
|
+
*/
|
|
109
|
+
export function consumeDestructiveConfirmation(
|
|
110
|
+
command: string,
|
|
111
|
+
basePath: string = process.cwd(),
|
|
112
|
+
): boolean {
|
|
113
|
+
const state = getState(basePath);
|
|
114
|
+
if (!state.confirmedCommand) return false;
|
|
115
|
+
if (state.confirmedCommand !== normalizeDestructiveCommand(command)) return false;
|
|
116
|
+
state.confirmedCommand = null;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Inspect the pending command without consuming it (diagnostics/tests).
|
|
122
|
+
*/
|
|
123
|
+
export function peekPendingDestructiveCommand(
|
|
124
|
+
basePath: string = process.cwd(),
|
|
125
|
+
): string | null {
|
|
126
|
+
return getState(basePath).pendingCommand;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Clear all destructive-confirmation state for a basePath (tests / flow reset).
|
|
131
|
+
*/
|
|
132
|
+
export function resetDestructiveConfirmation(basePath: string = process.cwd()): void {
|
|
133
|
+
statesByBasePath.delete(stateKey(basePath));
|
|
134
|
+
}
|
|
@@ -387,7 +387,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
387
387
|
// #3218: Provide actionable workaround when lock recovery fails
|
|
388
388
|
const lockDirPath = lockTarget + ".lock";
|
|
389
389
|
const reason = existingPid
|
|
390
|
-
? `Another auto-mode session (PID ${existingPid}) appears to be running.\
|
|
390
|
+
? `Another auto-mode session (PID ${existingPid}) appears to be running.\nRun \`/gsd stop\` for graceful shutdown, or choose "Force start" from \`/gsd auto\` to terminate it.`
|
|
391
391
|
: `Another auto-mode session lock is stuck on this project.\nRun: rm -rf "${lockDirPath}" && rm -f "${lp}"`;
|
|
392
392
|
|
|
393
393
|
return { acquired: false, reason, existingPid };
|
|
@@ -13,6 +13,11 @@ import type {
|
|
|
13
13
|
MilestoneRegistryEntry,
|
|
14
14
|
} from './types.js';
|
|
15
15
|
|
|
16
|
+
// Pre-migration fallback ONLY (ADR-017): deriveState must work on projects
|
|
17
|
+
// whose DB does not exist yet (before md-importer runs), so it parses markdown
|
|
18
|
+
// projections when `isDbAvailable()` is false or the DB has no rows. Once the
|
|
19
|
+
// DB is populated, decision reads go through gsd-db queries — these parsers
|
|
20
|
+
// must never be consulted when DB data is present.
|
|
16
21
|
import {
|
|
17
22
|
parseRoadmap,
|
|
18
23
|
parsePlan,
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
|
|
10
10
|
import { ModelPolicyDispatchBlockedError, resolvePreferredModelConfig, resolveModelId, selectAndApplyModel, floorThinkingLevelForUnit } from "../auto-model-selection.js";
|
|
11
|
+
import { blockModelUntil, clearTemporaryModelBlocksForTest } from "../blocked-models.ts";
|
|
11
12
|
|
|
12
13
|
function makeTempDir(prefix: string): string {
|
|
13
14
|
return mkdtempSync(join(tmpdir(), prefix));
|
|
@@ -223,6 +224,74 @@ test("selectAndApplyModel honors explicit phase models without downgrading (#361
|
|
|
223
224
|
}
|
|
224
225
|
});
|
|
225
226
|
|
|
227
|
+
test("selectAndApplyModel skips a rate-limited primary until its reset time", async () => {
|
|
228
|
+
const originalCwd = process.cwd();
|
|
229
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
230
|
+
const tempProject = makeTempDir("gsd-rate-limit-fallback-project-");
|
|
231
|
+
const tempGsdHome = makeTempDir("gsd-rate-limit-fallback-home-");
|
|
232
|
+
const setModelCalls: string[] = [];
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
clearTemporaryModelBlocksForTest();
|
|
236
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
237
|
+
writeFileSync(
|
|
238
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
239
|
+
[
|
|
240
|
+
"---",
|
|
241
|
+
"models:",
|
|
242
|
+
" execution:",
|
|
243
|
+
" model: gpt-5.5",
|
|
244
|
+
" provider: openai-codex",
|
|
245
|
+
" fallbacks:",
|
|
246
|
+
" - anthropic/claude-sonnet-4-6",
|
|
247
|
+
"---",
|
|
248
|
+
].join("\n"),
|
|
249
|
+
"utf-8",
|
|
250
|
+
);
|
|
251
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
252
|
+
process.chdir(tempProject);
|
|
253
|
+
|
|
254
|
+
const availableModels = [
|
|
255
|
+
{ id: "gpt-5.5", provider: "openai-codex", api: "responses" },
|
|
256
|
+
{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
|
|
257
|
+
];
|
|
258
|
+
const ctx = {
|
|
259
|
+
modelRegistry: { getAvailable: () => availableModels },
|
|
260
|
+
sessionManager: { getSessionId: () => "test-session" },
|
|
261
|
+
ui: { notify: () => {} },
|
|
262
|
+
model: { provider: "openai-codex", id: "gpt-5.5", api: "responses" },
|
|
263
|
+
} as any;
|
|
264
|
+
const pi = {
|
|
265
|
+
setModel: async (model: { provider: string; id: string }) => {
|
|
266
|
+
setModelCalls.push(`${model.provider}/${model.id}`);
|
|
267
|
+
return true;
|
|
268
|
+
},
|
|
269
|
+
emitBeforeModelSelect: async () => undefined,
|
|
270
|
+
getActiveTools: () => [],
|
|
271
|
+
emitAdjustToolSet: async () => undefined,
|
|
272
|
+
setActiveTools: () => {},
|
|
273
|
+
} as any;
|
|
274
|
+
|
|
275
|
+
blockModelUntil(tempProject, "openai-codex", "gpt-5.5", Date.now() + 60_000, "session limit");
|
|
276
|
+
await selectAndApplyModel(ctx, pi, "execute-task", "M001/S01/T01", tempProject, undefined, false, { provider: "openai-codex", id: "gpt-5.5" }, undefined, true);
|
|
277
|
+
|
|
278
|
+
blockModelUntil(tempProject, "openai-codex", "gpt-5.5", Date.now() - 1, "expired");
|
|
279
|
+
await selectAndApplyModel(ctx, pi, "execute-task", "M001/S01/T02", tempProject, undefined, false, { provider: "openai-codex", id: "gpt-5.5" }, undefined, true);
|
|
280
|
+
|
|
281
|
+
assert.deepEqual(setModelCalls, [
|
|
282
|
+
"anthropic/claude-sonnet-4-6",
|
|
283
|
+
"openai-codex/gpt-5.5",
|
|
284
|
+
]);
|
|
285
|
+
} finally {
|
|
286
|
+
clearTemporaryModelBlocksForTest();
|
|
287
|
+
process.chdir(originalCwd);
|
|
288
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
289
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
290
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
291
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
226
295
|
test("selectAndApplyModel lets explicit unit models bypass stale cross-provider lock (#116)", async () => {
|
|
227
296
|
const originalCwd = process.cwd();
|
|
228
297
|
const originalGsdHome = process.env.GSD_HOME;
|
|
@@ -803,7 +872,12 @@ test("selectAndApplyModel clamps explicit no-model thinking via ctx.model when r
|
|
|
803
872
|
|
|
804
873
|
await selectAndApplyModel(
|
|
805
874
|
{
|
|
806
|
-
|
|
875
|
+
// Empty registry results force the clamp source to fall back to ctx.model.
|
|
876
|
+
modelRegistry: {
|
|
877
|
+
getAvailable: () => [],
|
|
878
|
+
getAll: () => [],
|
|
879
|
+
isProviderRequestReady: () => true,
|
|
880
|
+
},
|
|
807
881
|
sessionManager: { getSessionId: () => "test-session" },
|
|
808
882
|
ui: { notify: () => {} },
|
|
809
883
|
model: ctxModel,
|
|
@@ -857,3 +931,25 @@ test("resolveModelId: claude-code wins when session is claude-code regardless of
|
|
|
857
931
|
assert.ok(result, "should resolve a model");
|
|
858
932
|
assert.equal(result.provider, "claude-code", "claude-code must win when it is the session provider");
|
|
859
933
|
});
|
|
934
|
+
|
|
935
|
+
test("resolveModelId: openai-codex wins over openai for bare GPT IDs when both are available", () => {
|
|
936
|
+
const availableModels = [
|
|
937
|
+
{ id: "gpt-5.5", provider: "openai" },
|
|
938
|
+
{ id: "gpt-5.5", provider: "openai-codex" },
|
|
939
|
+
];
|
|
940
|
+
|
|
941
|
+
const result = resolveModelId("gpt-5.5", availableModels, undefined);
|
|
942
|
+
assert.ok(result, "should resolve a model");
|
|
943
|
+
assert.equal(result.provider, "openai-codex", "ChatGPT OAuth must win over platform API for bare IDs");
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
test("resolveModelId: github-copilot wins over openai when both offer the same GPT model", () => {
|
|
947
|
+
const availableModels = [
|
|
948
|
+
{ id: "gpt-5.5", provider: "openai" },
|
|
949
|
+
{ id: "gpt-5.5", provider: "github-copilot" },
|
|
950
|
+
];
|
|
951
|
+
|
|
952
|
+
const result = resolveModelId("gpt-5.5", availableModels, undefined);
|
|
953
|
+
assert.ok(result);
|
|
954
|
+
assert.equal(result.provider, "github-copilot");
|
|
955
|
+
});
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
createAutoOrchestrator,
|
|
23
23
|
decideOrchestratorDispatch,
|
|
24
24
|
resolveLiveOrchestratorBasePath,
|
|
25
|
-
STUCK_WINDOW_SIZE,
|
|
26
25
|
} from "../auto/orchestrator.js";
|
|
26
|
+
import { STUCK_WINDOW_SIZE } from "../auto/dispatch-history.js";
|
|
27
27
|
import type { OrchestratorContext } from "../auto/orchestrator.js";
|
|
28
28
|
import type { AutoOrchestrationModule, AutoSessionContext } from "../auto/contracts.js";
|
|
29
29
|
import type { GSDState } from "../types.js";
|
|
@@ -41,6 +41,10 @@ import {
|
|
|
41
41
|
openDatabase,
|
|
42
42
|
} from "../gsd-db.js";
|
|
43
43
|
import { AutoSession } from "../auto/session.js";
|
|
44
|
+
import { registerAutoWorker } from "../db/auto-workers.js";
|
|
45
|
+
import { claimMilestoneLease } from "../db/milestone-leases.js";
|
|
46
|
+
import { recordDispatchClaim, markFailed } from "../db/unit-dispatches.js";
|
|
47
|
+
import { normalizeRealPath } from "../paths.js";
|
|
44
48
|
import { acquireSessionLock, releaseSessionLock } from "../session-lock.js";
|
|
45
49
|
import { queryJournal } from "../journal.js";
|
|
46
50
|
import { invalidateAllCaches } from "../cache.js";
|
|
@@ -518,13 +522,12 @@ test("advance() is idempotent for the same active unit", async (t) => {
|
|
|
518
522
|
if (first.kind === "advanced") {
|
|
519
523
|
assert.deepEqual(first.unit, { unitType: "execute-task", unitId: "M001/S01/T01" });
|
|
520
524
|
}
|
|
521
|
-
assert.equal(second.kind, "
|
|
522
|
-
if (second.kind !== "
|
|
525
|
+
assert.equal(second.kind, "skipped");
|
|
526
|
+
if (second.kind !== "skipped") return;
|
|
523
527
|
assert.equal(second.reason, "idempotent advance: unit already active");
|
|
524
|
-
assert.equal(second.action, "pause");
|
|
525
528
|
});
|
|
526
529
|
|
|
527
|
-
test("idempotency
|
|
530
|
+
test("idempotency skip fires with its own reason before saturation", async (t) => {
|
|
528
531
|
const f = makeFixture();
|
|
529
532
|
t.after(() => f.cleanup());
|
|
530
533
|
|
|
@@ -532,10 +535,9 @@ test("idempotency block fires with its own reason before saturation", async (t)
|
|
|
532
535
|
const second = await f.orchestrator.advance();
|
|
533
536
|
|
|
534
537
|
assert.equal(first.kind, "advanced");
|
|
535
|
-
assert.equal(second.kind, "
|
|
536
|
-
if (second.kind !== "
|
|
538
|
+
assert.equal(second.kind, "skipped");
|
|
539
|
+
if (second.kind !== "skipped") return;
|
|
537
540
|
assert.equal(second.reason, "idempotent advance: unit already active");
|
|
538
|
-
assert.equal(second.action, "pause");
|
|
539
541
|
});
|
|
540
542
|
|
|
541
543
|
test("completeActiveUnit clears in-flight idempotency and stops stale same-unit advance", async (t) => {
|
|
@@ -671,12 +673,12 @@ test("resume() clears idempotent lock and allows re-advance", async (t) => {
|
|
|
671
673
|
t.after(() => f.cleanup());
|
|
672
674
|
|
|
673
675
|
const first = await f.orchestrator.advance();
|
|
674
|
-
const
|
|
676
|
+
const idempotent = await f.orchestrator.advance();
|
|
675
677
|
const resumed = await f.orchestrator.resume();
|
|
676
678
|
const next = await f.orchestrator.advance();
|
|
677
679
|
|
|
678
680
|
assert.equal(first.kind, "advanced");
|
|
679
|
-
assert.equal(
|
|
681
|
+
assert.equal(idempotent.kind, "skipped");
|
|
680
682
|
assert.equal(resumed.kind, "resumed");
|
|
681
683
|
assert.equal(next.kind, "advanced");
|
|
682
684
|
});
|
|
@@ -686,11 +688,11 @@ test("start() clears prior idempotent lock", async (t) => {
|
|
|
686
688
|
t.after(() => f.cleanup());
|
|
687
689
|
|
|
688
690
|
await f.orchestrator.advance();
|
|
689
|
-
const
|
|
691
|
+
const idempotent = await f.orchestrator.advance();
|
|
690
692
|
const restarted = await f.orchestrator.start(SESSION_CONTEXT);
|
|
691
693
|
const next = await f.orchestrator.advance();
|
|
692
694
|
|
|
693
|
-
assert.equal(
|
|
695
|
+
assert.equal(idempotent.kind, "skipped");
|
|
694
696
|
assert.equal(restarted.kind, "started");
|
|
695
697
|
assert.equal(next.kind, "advanced");
|
|
696
698
|
});
|
|
@@ -700,32 +702,28 @@ test("stop() clears idempotent unit lock so advance can run again", async (t) =>
|
|
|
700
702
|
t.after(() => f.cleanup());
|
|
701
703
|
|
|
702
704
|
const first = await f.orchestrator.advance();
|
|
703
|
-
const
|
|
705
|
+
const idempotent = await f.orchestrator.advance();
|
|
704
706
|
const stopped = await f.orchestrator.stop("reset");
|
|
705
707
|
const second = await f.orchestrator.advance();
|
|
706
708
|
|
|
707
709
|
assert.equal(first.kind, "advanced");
|
|
708
|
-
assert.equal(
|
|
710
|
+
assert.equal(idempotent.kind, "skipped");
|
|
709
711
|
assert.equal(stopped.kind, "stopped");
|
|
710
712
|
assert.equal(second.kind, "advanced");
|
|
711
713
|
});
|
|
712
714
|
|
|
713
|
-
test("
|
|
715
|
+
test("idempotent path journals advance-skipped and records a health snapshot", async (t) => {
|
|
714
716
|
const f = makeFixture();
|
|
715
717
|
t.after(() => f.cleanup());
|
|
716
718
|
|
|
717
719
|
await f.orchestrator.advance();
|
|
718
720
|
await f.orchestrator.advance();
|
|
719
721
|
|
|
720
|
-
assert.ok(f.journalNames().includes("advance-
|
|
722
|
+
assert.ok(f.journalNames().includes("advance-skipped"));
|
|
721
723
|
});
|
|
722
724
|
|
|
723
725
|
// ─── Stuck-loop ring buffer (issue #5787) ──────────────────────────────────
|
|
724
726
|
|
|
725
|
-
test("STUCK_WINDOW_SIZE matches the legacy auto/phases.ts constant", () => {
|
|
726
|
-
assert.equal(STUCK_WINDOW_SIZE, 6);
|
|
727
|
-
});
|
|
728
|
-
|
|
729
727
|
test("stuck-loop: empty ring on a freshly constructed orchestrator advances normally", async (t) => {
|
|
730
728
|
const f = makeFixture();
|
|
731
729
|
t.after(() => f.cleanup());
|
|
@@ -761,21 +759,24 @@ test("stuck-loop: ring saturated with same unit blocks with action 'stop' and st
|
|
|
761
759
|
// First call advances.
|
|
762
760
|
assert.equal(results[0].kind, "advanced");
|
|
763
761
|
|
|
764
|
-
// Intermediate calls are
|
|
762
|
+
// Intermediate calls are skipped by idempotency (not stuck-loop yet).
|
|
765
763
|
for (let i = 1; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
766
764
|
const r = results[i];
|
|
767
|
-
assert.equal(r.kind, "
|
|
768
|
-
if (r.kind !== "
|
|
765
|
+
assert.equal(r.kind, "skipped", `round ${i} should be skipped`);
|
|
766
|
+
if (r.kind !== "skipped") return;
|
|
769
767
|
assert.equal(r.reason, "idempotent advance: unit already active");
|
|
770
|
-
assert.equal(r.action, "pause");
|
|
771
768
|
}
|
|
772
769
|
|
|
773
|
-
// The final call (ring now holds STUCK_WINDOW_SIZE copies) returns stuck-loop
|
|
770
|
+
// The final call (ring now holds STUCK_WINDOW_SIZE copies) returns stuck-loop
|
|
771
|
+
// with the detect-stuck rule verdict in the reason.
|
|
774
772
|
const last = results[STUCK_WINDOW_SIZE - 1];
|
|
775
773
|
assert.equal(last.kind, "blocked");
|
|
776
774
|
if (last.kind !== "blocked") return;
|
|
777
775
|
assert.equal(last.action, "stop");
|
|
778
|
-
assert.
|
|
776
|
+
assert.ok(
|
|
777
|
+
last.reason.startsWith("stuck-loop: execute-task:M001/S01/T01 derived"),
|
|
778
|
+
`expected detect-stuck verdict reason, got: ${last.reason}`,
|
|
779
|
+
);
|
|
779
780
|
});
|
|
780
781
|
|
|
781
782
|
test("stuck-loop: start() resets the ring so a fresh saturation cycle is required", async (t) => {
|
|
@@ -855,6 +856,80 @@ test("stuck-loop: stop('user-request') resets the ring (hard stop)", async (t) =
|
|
|
855
856
|
assert.equal(next.kind, "advanced");
|
|
856
857
|
});
|
|
857
858
|
|
|
859
|
+
test("stuck-loop #482 regression: start() rehydrates the window from the dispatch ledger so cross-session re-dispatch loops are detected", async (t) => {
|
|
860
|
+
const f = makeFixture();
|
|
861
|
+
t.after(() => f.cleanup());
|
|
862
|
+
|
|
863
|
+
// Simulate a PRIOR session: the dispatch ledger recorded the same unit
|
|
864
|
+
// being re-dispatched repeatedly without progress. The orchestrator under test is a
|
|
865
|
+
// fresh instance (as it would be after a session restart) — before the
|
|
866
|
+
// Dispatch History module, start() reset the window to [] and the loop
|
|
867
|
+
// would silently re-dispatch the unit forever (#482: 146 re-dispatches).
|
|
868
|
+
const worker = registerAutoWorker({ projectRootRealpath: normalizeRealPath(f.base) });
|
|
869
|
+
const lease = claimMilestoneLease(worker, "M001");
|
|
870
|
+
assert.equal(lease.ok, true);
|
|
871
|
+
if (!lease.ok) return;
|
|
872
|
+
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
873
|
+
const claim = recordDispatchClaim({
|
|
874
|
+
traceId: `prior-session-${i}`,
|
|
875
|
+
workerId: worker,
|
|
876
|
+
milestoneLeaseToken: lease.token,
|
|
877
|
+
milestoneId: "M001",
|
|
878
|
+
unitType: "execute-task",
|
|
879
|
+
unitId: "M001/S01/T01",
|
|
880
|
+
});
|
|
881
|
+
assert.equal(claim.ok, true);
|
|
882
|
+
if (!claim.ok) return;
|
|
883
|
+
markFailed(claim.dispatchId, { errorSummary: "" });
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const started = await f.orchestrator.start(SESSION_CONTEXT);
|
|
887
|
+
assert.equal(started.kind, "started");
|
|
888
|
+
|
|
889
|
+
// The very next decision for the same unit must trip the stuck verdict
|
|
890
|
+
// instead of advancing.
|
|
891
|
+
const result = await f.orchestrator.advance();
|
|
892
|
+
assert.equal(result.kind, "blocked");
|
|
893
|
+
if (result.kind !== "blocked") return;
|
|
894
|
+
assert.equal(result.action, "stop");
|
|
895
|
+
assert.ok(result.reason.startsWith("stuck-loop:"), `expected stuck-loop reason, got: ${result.reason}`);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("stuck-loop #482: resume() with an empty window rehydrates from the dispatch ledger", async (t) => {
|
|
899
|
+
const f = makeFixture();
|
|
900
|
+
t.after(() => f.cleanup());
|
|
901
|
+
|
|
902
|
+
const worker = registerAutoWorker({ projectRootRealpath: normalizeRealPath(f.base) });
|
|
903
|
+
const lease = claimMilestoneLease(worker, "M001");
|
|
904
|
+
assert.equal(lease.ok, true);
|
|
905
|
+
if (!lease.ok) return;
|
|
906
|
+
for (let i = 0; i < STUCK_WINDOW_SIZE - 1; i++) {
|
|
907
|
+
const claim = recordDispatchClaim({
|
|
908
|
+
traceId: `prior-session-resume-${i}`,
|
|
909
|
+
workerId: worker,
|
|
910
|
+
milestoneLeaseToken: lease.token,
|
|
911
|
+
milestoneId: "M001",
|
|
912
|
+
unitType: "execute-task",
|
|
913
|
+
unitId: "M001/S01/T01",
|
|
914
|
+
});
|
|
915
|
+
assert.equal(claim.ok, true);
|
|
916
|
+
if (!claim.ok) return;
|
|
917
|
+
markFailed(claim.dispatchId, { errorSummary: "" });
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Fresh orchestrator resuming a prior session: window starts empty, so
|
|
921
|
+
// resume() must rehydrate (while in-process resume keeps the live window —
|
|
922
|
+
// see the #572 preservation tests above).
|
|
923
|
+
const resumed = await f.orchestrator.resume();
|
|
924
|
+
assert.equal(resumed.kind, "resumed");
|
|
925
|
+
|
|
926
|
+
const result = await f.orchestrator.advance();
|
|
927
|
+
assert.equal(result.kind, "blocked");
|
|
928
|
+
if (result.kind !== "blocked") return;
|
|
929
|
+
assert.equal(result.action, "stop");
|
|
930
|
+
assert.ok(result.reason.startsWith("stuck-loop:"), `expected stuck-loop reason, got: ${result.reason}`);
|
|
931
|
+
});
|
|
932
|
+
|
|
858
933
|
test("stuck-loop: journal records the stuck-loop reason on advance-blocked", async (t) => {
|
|
859
934
|
const f = makeFixture();
|
|
860
935
|
t.after(() => f.cleanup());
|
|
@@ -1148,6 +1223,103 @@ test("decideOrchestratorDispatch forwards constructor session when advance input
|
|
|
1148
1223
|
}
|
|
1149
1224
|
});
|
|
1150
1225
|
|
|
1226
|
+
test("decideOrchestratorDispatch evaluates deep pre-planning rules without an active milestone", async (t) => {
|
|
1227
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-no-active-"));
|
|
1228
|
+
t.after(() => {
|
|
1229
|
+
resetRegistry();
|
|
1230
|
+
rmSync(base, { recursive: true, force: true });
|
|
1231
|
+
});
|
|
1232
|
+
resetRegistry();
|
|
1233
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
1234
|
+
writeFileSync(
|
|
1235
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
1236
|
+
[
|
|
1237
|
+
"---",
|
|
1238
|
+
"planning_depth: deep",
|
|
1239
|
+
"workflow_prefs_captured: true",
|
|
1240
|
+
"---",
|
|
1241
|
+
"",
|
|
1242
|
+
].join("\n"),
|
|
1243
|
+
);
|
|
1244
|
+
|
|
1245
|
+
const stateSnapshot: GSDState = {
|
|
1246
|
+
...makeState(),
|
|
1247
|
+
activeMilestone: null,
|
|
1248
|
+
phase: "pre-planning",
|
|
1249
|
+
nextAction: "All remaining milestones are parked (M027). Run /gsd unpark M027 or create a new milestone.",
|
|
1250
|
+
registry: [{ id: "M027", title: "Parked", status: "parked" }],
|
|
1251
|
+
};
|
|
1252
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1253
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1254
|
+
const session = {
|
|
1255
|
+
basePath: base,
|
|
1256
|
+
originalBasePath: base,
|
|
1257
|
+
currentMilestoneId: "M027",
|
|
1258
|
+
} as never;
|
|
1259
|
+
|
|
1260
|
+
const result = await decideOrchestratorDispatch(ctx, pi, base, session, { stateSnapshot });
|
|
1261
|
+
|
|
1262
|
+
assert.ok(result && "unitType" in result, `expected project-level dispatch, got ${JSON.stringify(result)}`);
|
|
1263
|
+
assert.equal(result.unitType, "discuss-project");
|
|
1264
|
+
assert.equal(result.unitId, "PROJECT");
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
test("decideOrchestratorDispatch does not replay milestone-scoped verification retry when no milestone is active", async (t) => {
|
|
1268
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-no-active-retry-"));
|
|
1269
|
+
t.after(() => {
|
|
1270
|
+
resetRegistry();
|
|
1271
|
+
rmSync(base, { recursive: true, force: true });
|
|
1272
|
+
});
|
|
1273
|
+
resetRegistry();
|
|
1274
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
1275
|
+
writeFileSync(
|
|
1276
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
1277
|
+
[
|
|
1278
|
+
"---",
|
|
1279
|
+
"planning_depth: deep",
|
|
1280
|
+
"workflow_prefs_captured: true",
|
|
1281
|
+
"---",
|
|
1282
|
+
"",
|
|
1283
|
+
].join("\n"),
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
const stateSnapshot: GSDState = {
|
|
1287
|
+
...makeState(),
|
|
1288
|
+
activeMilestone: null,
|
|
1289
|
+
phase: "pre-planning",
|
|
1290
|
+
nextAction: "All remaining milestones are parked (M027). Run /gsd unpark M027 or create a new milestone.",
|
|
1291
|
+
registry: [{ id: "M027", title: "Parked", status: "parked" }],
|
|
1292
|
+
};
|
|
1293
|
+
const ctx = { model: {}, modelRegistry: { getAll: () => [] } } as never;
|
|
1294
|
+
const pi = { getActiveTools: () => [] } as never;
|
|
1295
|
+
const stalePendingRetry = {
|
|
1296
|
+
unitType: "execute-task",
|
|
1297
|
+
unitId: "M027.S1.T1",
|
|
1298
|
+
prompt: "stale retry prompt",
|
|
1299
|
+
pauseAfterUatDispatch: false,
|
|
1300
|
+
state: stateSnapshot,
|
|
1301
|
+
mid: "M027",
|
|
1302
|
+
midTitle: "Parked",
|
|
1303
|
+
};
|
|
1304
|
+
const session = {
|
|
1305
|
+
basePath: base,
|
|
1306
|
+
originalBasePath: base,
|
|
1307
|
+
currentMilestoneId: "M027",
|
|
1308
|
+
pendingVerificationRetryDispatch: stalePendingRetry,
|
|
1309
|
+
} as never;
|
|
1310
|
+
|
|
1311
|
+
const result = await decideOrchestratorDispatch(ctx, pi, base, session, { stateSnapshot });
|
|
1312
|
+
|
|
1313
|
+
assert.ok(result && "unitType" in result, `expected project-level dispatch, got ${JSON.stringify(result)}`);
|
|
1314
|
+
assert.equal(result.unitType, "discuss-project");
|
|
1315
|
+
assert.equal(result.unitId, "PROJECT");
|
|
1316
|
+
// The stale retry must be preserved for a future tick, not consumed by this
|
|
1317
|
+
// no-active-milestone path (mirrors pre-#712-fix behavior where !active
|
|
1318
|
+
// returned null before touching the retry).
|
|
1319
|
+
const sess = session as unknown as { pendingVerificationRetryDispatch: unknown };
|
|
1320
|
+
assert.equal(sess.pendingVerificationRetryDispatch, stalePendingRetry);
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1151
1323
|
test("decideOrchestratorDispatch adopts next active milestone after the session milestone is closed", async (t) => {
|
|
1152
1324
|
const base = mkdtempSync(join(tmpdir(), "gsd-orchestrator-milestone-adopt-"));
|
|
1153
1325
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|