gsd-pi 2.51.0 → 2.52.0-dev.655ad8a
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 +59 -36
- package/dist/headless-events.d.ts +18 -0
- package/dist/headless-events.js +36 -0
- package/dist/headless-query.js +1 -1
- package/dist/headless-types.d.ts +28 -0
- package/dist/headless-types.js +7 -0
- package/dist/headless.d.ts +8 -3
- package/dist/headless.js +47 -16
- package/dist/help-text.js +16 -5
- package/dist/onboarding.js +5 -4
- package/dist/remote-questions-config.js +1 -1
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
- package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
- package/dist/resources/extensions/get-secrets-from-user.js +7 -0
- package/dist/resources/extensions/gsd/auto/phases.js +34 -8
- package/dist/resources/extensions/gsd/auto-dispatch.js +23 -1
- package/dist/resources/extensions/gsd/auto-start.js +2 -0
- package/dist/resources/extensions/gsd/auto-timers.js +24 -2
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +91 -14
- package/dist/resources/extensions/gsd/auto.js +30 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +99 -70
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
- package/dist/resources/extensions/gsd/claude-import.js +60 -9
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
- package/dist/resources/extensions/gsd/commands-config.js +10 -5
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
- package/dist/resources/extensions/gsd/detection.js +6 -6
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/dist/resources/extensions/gsd/error-classifier.js +105 -0
- package/dist/resources/extensions/gsd/git-service.js +4 -3
- package/dist/resources/extensions/gsd/gitignore.js +7 -7
- package/dist/resources/extensions/gsd/gsd-db.js +298 -45
- package/dist/resources/extensions/gsd/guided-flow.js +4 -3
- package/dist/resources/extensions/gsd/init-wizard.js +2 -2
- package/dist/resources/extensions/gsd/key-manager.js +7 -16
- package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
- package/dist/resources/extensions/gsd/memory-store.js +28 -13
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
- package/dist/resources/extensions/gsd/preferences-models.js +1 -13
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences.js +13 -13
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
- package/dist/resources/extensions/gsd/rule-registry.js +1 -1
- package/dist/resources/extensions/gsd/service-tier.js +13 -2
- package/dist/resources/extensions/gsd/state.js +38 -30
- package/dist/resources/extensions/gsd/status-guards.js +12 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
- package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
- package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
- package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
- package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
- package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
- package/dist/resources/extensions/gsd/validation.js +21 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
- package/dist/resources/extensions/remote-questions/config.js +1 -1
- package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
- package/dist/resources/extensions/search-the-web/native-search.js +1 -1
- package/dist/resources/extensions/search-the-web/provider.js +1 -1
- package/dist/resources/extensions/shared/rtk.js +14 -4
- package/dist/rtk.js +3 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- 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/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- 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 +4 -4
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +5 -5
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
- package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- 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/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-bca0e732db0dcec3.js} +1 -1
- package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/wizard.js +4 -1
- package/package.json +2 -2
- package/packages/mcp-server/README.md +202 -0
- package/packages/mcp-server/package.json +36 -0
- package/packages/mcp-server/src/cli.ts +68 -0
- package/packages/mcp-server/src/index.ts +14 -0
- package/packages/mcp-server/src/mcp-server.test.ts +628 -0
- package/packages/mcp-server/src/server.ts +278 -0
- package/packages/mcp-server/src/session-manager.ts +328 -0
- package/packages/mcp-server/src/types.ts +107 -0
- package/packages/mcp-server/tsconfig.json +24 -0
- package/packages/pi-ai/dist/models.d.ts +14 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.js +53 -10
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +102 -1
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +30 -0
- 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/src/models.test.ts +114 -1
- package/packages/pi-ai/src/models.ts +70 -13
- package/packages/pi-ai/src/types.ts +31 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +3 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -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 +5 -1
- 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.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +5 -3
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +4 -0
- package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
- package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
- package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
- package/packages/pi-coding-agent/src/index.ts +3 -0
- package/packages/pi-coding-agent/src/main.ts +5 -3
- package/packages/pi-coding-agent/src/modes/index.ts +8 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
- package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
- package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
- package/packages/rpc-client/package.json +20 -0
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +36 -8
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
- package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
- package/src/resources/extensions/get-secrets-from-user.ts +8 -0
- package/src/resources/extensions/gsd/auto/phases.ts +44 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +25 -1
- package/src/resources/extensions/gsd/auto-start.ts +2 -0
- package/src/resources/extensions/gsd/auto-timers.ts +25 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +94 -14
- package/src/resources/extensions/gsd/auto.ts +31 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +118 -73
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
- package/src/resources/extensions/gsd/claude-import.ts +58 -9
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
- package/src/resources/extensions/gsd/commands-config.ts +11 -5
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
- package/src/resources/extensions/gsd/detection.ts +6 -6
- package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/src/resources/extensions/gsd/error-classifier.ts +139 -0
- package/src/resources/extensions/gsd/git-service.ts +4 -3
- package/src/resources/extensions/gsd/gitignore.ts +7 -7
- package/src/resources/extensions/gsd/gsd-db.ts +355 -63
- package/src/resources/extensions/gsd/guided-flow.ts +4 -3
- package/src/resources/extensions/gsd/init-wizard.ts +2 -2
- package/src/resources/extensions/gsd/key-manager.ts +7 -16
- package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
- package/src/resources/extensions/gsd/memory-store.ts +29 -18
- package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
- package/src/resources/extensions/gsd/preferences-models.ts +1 -13
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/preferences.ts +12 -13
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
- package/src/resources/extensions/gsd/rule-registry.ts +1 -1
- package/src/resources/extensions/gsd/service-tier.ts +14 -2
- package/src/resources/extensions/gsd/state.ts +39 -30
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
- package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +65 -31
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
- package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
- package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
- package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
- package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
- package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
- package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
- package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
- package/src/resources/extensions/gsd/validation.ts +23 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
- package/src/resources/extensions/remote-questions/config.ts +1 -1
- package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/src/resources/extensions/search-the-web/native-search.ts +1 -1
- package/src/resources/extensions/search-the-web/provider.ts +1 -1
- package/src/resources/extensions/shared/rtk.ts +22 -4
- package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
- /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_ssgManifest.js +0 -0
- /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider error handling tests — consolidated from:
|
|
3
|
-
* - provider-error-classify.test.ts (
|
|
3
|
+
* - provider-error-classify.test.ts (classifyError)
|
|
4
4
|
* - network-error-fallback.test.ts (isTransientNetworkError, getNextFallbackModel)
|
|
5
5
|
* - agent-end-provider-error.test.ts (pauseAutoForProviderError)
|
|
6
6
|
*/
|
|
@@ -10,102 +10,111 @@ import assert from "node:assert/strict";
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { join, dirname } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { classifyError, isTransient, isTransientNetworkError } from "../error-classifier.ts";
|
|
14
|
+
import { pauseAutoForProviderError } from "../provider-error-pause.ts";
|
|
15
|
+
import { getNextFallbackModel } from "../preferences.ts";
|
|
15
16
|
|
|
16
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
|
|
18
|
-
// ──
|
|
19
|
+
// ── classifyError ────────────────────────────────────────────────────────────
|
|
19
20
|
|
|
20
|
-
test("
|
|
21
|
-
const result =
|
|
22
|
-
assert.ok(result
|
|
23
|
-
assert.
|
|
24
|
-
assert.ok(result.
|
|
21
|
+
test("classifyError detects rate limit from 429", () => {
|
|
22
|
+
const result = classifyError("HTTP 429 Too Many Requests");
|
|
23
|
+
assert.ok(isTransient(result));
|
|
24
|
+
assert.equal(result.kind, "rate-limit");
|
|
25
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs > 0);
|
|
25
26
|
});
|
|
26
27
|
|
|
27
|
-
test("
|
|
28
|
-
const result =
|
|
29
|
-
assert.ok(result
|
|
30
|
-
assert.
|
|
28
|
+
test("classifyError detects rate limit from message", () => {
|
|
29
|
+
const result = classifyError("rate limit exceeded");
|
|
30
|
+
assert.ok(isTransient(result));
|
|
31
|
+
assert.equal(result.kind, "rate-limit");
|
|
31
32
|
});
|
|
32
33
|
|
|
33
|
-
test("
|
|
34
|
-
const result =
|
|
35
|
-
assert.
|
|
36
|
-
assert.
|
|
34
|
+
test("classifyError extracts reset delay from message", () => {
|
|
35
|
+
const result = classifyError("rate limit exceeded, reset in 45s");
|
|
36
|
+
assert.equal(result.kind, "rate-limit");
|
|
37
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs === 45000);
|
|
37
38
|
});
|
|
38
39
|
|
|
39
|
-
test("
|
|
40
|
-
const result =
|
|
41
|
-
assert.
|
|
42
|
-
assert.
|
|
40
|
+
test("classifyError defaults to 60s for rate limit without reset", () => {
|
|
41
|
+
const result = classifyError("429 too many requests");
|
|
42
|
+
assert.equal(result.kind, "rate-limit");
|
|
43
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs === 60_000);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
test("
|
|
46
|
+
test("classifyError treats stream_exhausted_without_result as transient connection failure", () => {
|
|
47
|
+
const result = classifyError("stream_exhausted_without_result");
|
|
48
|
+
assert.ok(isTransient(result));
|
|
49
|
+
assert.equal(result.kind, "connection");
|
|
50
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs === 15_000);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("classifyError detects Anthropic internal server error", () => {
|
|
46
54
|
const msg = '{"type":"error","error":{"details":null,"type":"api_error","message":"Internal server error"}}';
|
|
47
|
-
const result =
|
|
48
|
-
assert.ok(result
|
|
49
|
-
assert.
|
|
50
|
-
assert.
|
|
55
|
+
const result = classifyError(msg);
|
|
56
|
+
assert.ok(isTransient(result));
|
|
57
|
+
assert.equal(result.kind, "server");
|
|
58
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
|
|
51
59
|
});
|
|
52
60
|
|
|
53
|
-
test("
|
|
61
|
+
test("classifyError detects Codex server_error from extracted message", () => {
|
|
54
62
|
// After fix, mapCodexEvents extracts the nested error type and produces
|
|
55
63
|
// "Codex server_error: <message>" instead of raw JSON.
|
|
56
64
|
const msg = "Codex server_error: An error occurred while processing your request.";
|
|
57
|
-
const result =
|
|
58
|
-
assert.ok(result
|
|
59
|
-
assert.
|
|
60
|
-
assert.
|
|
65
|
+
const result = classifyError(msg);
|
|
66
|
+
assert.ok(isTransient(result));
|
|
67
|
+
assert.equal(result.kind, "server");
|
|
68
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
|
|
61
69
|
});
|
|
62
70
|
|
|
63
|
-
test("
|
|
64
|
-
const result =
|
|
65
|
-
assert.ok(result
|
|
66
|
-
assert.
|
|
71
|
+
test("classifyError detects overloaded error", () => {
|
|
72
|
+
const result = classifyError("overloaded_error: Overloaded");
|
|
73
|
+
assert.ok(isTransient(result));
|
|
74
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs === 30_000);
|
|
67
75
|
});
|
|
68
76
|
|
|
69
|
-
test("
|
|
70
|
-
const result =
|
|
71
|
-
assert.ok(result
|
|
77
|
+
test("classifyError detects 503 service unavailable", () => {
|
|
78
|
+
const result = classifyError("HTTP 503 Service Unavailable");
|
|
79
|
+
assert.ok(isTransient(result));
|
|
72
80
|
});
|
|
73
81
|
|
|
74
|
-
test("
|
|
75
|
-
const result =
|
|
76
|
-
assert.ok(result
|
|
82
|
+
test("classifyError detects 502 bad gateway", () => {
|
|
83
|
+
const result = classifyError("HTTP 502 Bad Gateway");
|
|
84
|
+
assert.ok(isTransient(result));
|
|
77
85
|
});
|
|
78
86
|
|
|
79
|
-
test("
|
|
80
|
-
const result =
|
|
81
|
-
assert.ok(!result
|
|
82
|
-
assert.
|
|
87
|
+
test("classifyError detects auth error as permanent", () => {
|
|
88
|
+
const result = classifyError("unauthorized: invalid API key");
|
|
89
|
+
assert.ok(!isTransient(result));
|
|
90
|
+
assert.equal(result.kind, "permanent");
|
|
83
91
|
});
|
|
84
92
|
|
|
85
|
-
test("
|
|
86
|
-
const result =
|
|
87
|
-
assert.ok(!result
|
|
93
|
+
test("classifyError detects billing error as permanent", () => {
|
|
94
|
+
const result = classifyError("billing issue: payment required");
|
|
95
|
+
assert.ok(!isTransient(result));
|
|
88
96
|
});
|
|
89
97
|
|
|
90
|
-
test("
|
|
91
|
-
const result =
|
|
92
|
-
assert.ok(!result
|
|
98
|
+
test("classifyError detects quota exceeded as permanent", () => {
|
|
99
|
+
const result = classifyError("quota exceeded for this month");
|
|
100
|
+
assert.ok(!isTransient(result));
|
|
93
101
|
});
|
|
94
102
|
|
|
95
|
-
test("
|
|
96
|
-
const result =
|
|
97
|
-
assert.ok(!result
|
|
103
|
+
test("classifyError treats unknown error as not transient", () => {
|
|
104
|
+
const result = classifyError("something went wrong");
|
|
105
|
+
assert.ok(!isTransient(result));
|
|
106
|
+
assert.equal(result.kind, "unknown");
|
|
98
107
|
});
|
|
99
108
|
|
|
100
|
-
test("
|
|
101
|
-
const result =
|
|
102
|
-
assert.ok(!result
|
|
109
|
+
test("classifyError treats empty string as not transient", () => {
|
|
110
|
+
const result = classifyError("");
|
|
111
|
+
assert.ok(!isTransient(result));
|
|
103
112
|
});
|
|
104
113
|
|
|
105
|
-
test("
|
|
106
|
-
const result =
|
|
107
|
-
assert.
|
|
108
|
-
assert.ok(result
|
|
114
|
+
test("classifyError: rate limit takes precedence over auth keywords", () => {
|
|
115
|
+
const result = classifyError("429 unauthorized rate limit");
|
|
116
|
+
assert.equal(result.kind, "rate-limit");
|
|
117
|
+
assert.ok(isTransient(result));
|
|
109
118
|
});
|
|
110
119
|
|
|
111
120
|
// ── isTransientNetworkError ──────────────────────────────────────────────────
|
|
@@ -265,8 +274,8 @@ test("agent-end-recovery.ts tracks consecutive transient errors for escalating b
|
|
|
265
274
|
const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
|
|
266
275
|
|
|
267
276
|
assert.ok(
|
|
268
|
-
src.includes("
|
|
269
|
-
"agent-end-recovery.ts must track
|
|
277
|
+
src.includes("consecutiveTransientCount"),
|
|
278
|
+
"agent-end-recovery.ts must track consecutiveTransientCount for escalating backoff (#1166)",
|
|
270
279
|
);
|
|
271
280
|
assert.ok(
|
|
272
281
|
src.includes("MAX_TRANSIENT_AUTO_RESUMES"),
|
|
@@ -274,15 +283,13 @@ test("agent-end-recovery.ts tracks consecutive transient errors for escalating b
|
|
|
274
283
|
);
|
|
275
284
|
});
|
|
276
285
|
|
|
277
|
-
test("agent-end-recovery.ts resets
|
|
286
|
+
test("agent-end-recovery.ts resets retry state before resolveAgentEnd on success", () => {
|
|
278
287
|
const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
|
|
279
288
|
|
|
280
|
-
// After successful agent_end
|
|
281
|
-
// Use a regex across the success block so CRLF checkouts on Windows do not
|
|
282
|
-
// push the reset line outside a fixed substring window.
|
|
289
|
+
// After successful agent_end, resetRetryState must be called before resolveAgentEnd.
|
|
283
290
|
assert.ok(
|
|
284
|
-
/
|
|
285
|
-
"
|
|
291
|
+
/resetRetryState[\s\S]{0,250}resolveAgentEnd/.test(src),
|
|
292
|
+
"resetRetryState must be called before resolveAgentEnd on the success path (#1166)",
|
|
286
293
|
);
|
|
287
294
|
});
|
|
288
295
|
|
|
@@ -291,7 +298,7 @@ test("agent-end-recovery.ts applies escalating delay for repeated transient erro
|
|
|
291
298
|
|
|
292
299
|
// Must contain the exponential backoff formula (may span multiple lines)
|
|
293
300
|
assert.ok(
|
|
294
|
-
src.includes("2 ** Math.max(0,
|
|
301
|
+
src.includes("2 ** Math.max(0, retryState.consecutiveTransientCount"),
|
|
295
302
|
"agent-end-recovery.ts must escalate retryAfterMs exponentially for consecutive transient errors (#1166)",
|
|
296
303
|
);
|
|
297
304
|
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rate-limit-model-fallback.test.ts — Regression test for #2770.
|
|
3
|
+
*
|
|
4
|
+
* Rate-limit errors enter the model fallback path before falling through
|
|
5
|
+
* to pause. This verifies the structural contract in agent-end-recovery.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { join, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const RECOVERY_PATH = join(__dirname, "..", "bootstrap", "agent-end-recovery.ts");
|
|
16
|
+
|
|
17
|
+
function getRecoverySource(): string {
|
|
18
|
+
return readFileSync(RECOVERY_PATH, "utf-8");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── Rate-limit errors attempt model fallback (#2770) ─────────────────────────
|
|
22
|
+
|
|
23
|
+
test("rate-limit errors enter the model fallback branch alongside other transient errors", () => {
|
|
24
|
+
const src = getRecoverySource();
|
|
25
|
+
|
|
26
|
+
// The condition that gates model fallback must include rate-limit.
|
|
27
|
+
// Match the if-condition that contains both "rate-limit" and fallback-related kinds.
|
|
28
|
+
const fallbackConditionRe = /if\s*\([^)]*cls\.kind\s*===\s*"rate-limit"[^)]*cls\.kind\s*===\s*"network"/;
|
|
29
|
+
const fallbackConditionReAlt = /if\s*\([^)]*cls\.kind\s*===\s*"network"[^)]*cls\.kind\s*===\s*"rate-limit"/;
|
|
30
|
+
|
|
31
|
+
assert.ok(
|
|
32
|
+
fallbackConditionRe.test(src) || fallbackConditionReAlt.test(src),
|
|
33
|
+
'rate-limit must appear in the same if-condition as network/server for model fallback (#2770)',
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("rate-limit errors are NOT short-circuited to pause before model fallback", () => {
|
|
38
|
+
const src = getRecoverySource();
|
|
39
|
+
|
|
40
|
+
// The old code had a dedicated rate-limit early-return block before the fallback block.
|
|
41
|
+
// Verify it no longer exists.
|
|
42
|
+
const earlyRateLimitPause = /if\s*\(\s*cls\.kind\s*===\s*"rate-limit"\s*\)\s*\{[^}]*pauseTransientWithBackoff/;
|
|
43
|
+
assert.ok(
|
|
44
|
+
!earlyRateLimitPause.test(src),
|
|
45
|
+
'rate-limit must NOT have a dedicated early pause before the model fallback path (#2770)',
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("rate-limit errors fall through to pause if no fallback model is available", () => {
|
|
50
|
+
const src = getRecoverySource();
|
|
51
|
+
|
|
52
|
+
// After the fallback block, the transient fallback pause must still fire for rate-limit.
|
|
53
|
+
// The isTransient check covers rate-limit (verified by error-classifier tests).
|
|
54
|
+
// Verify pauseTransientWithBackoff is called with isRateLimit derived from cls.kind.
|
|
55
|
+
assert.ok(
|
|
56
|
+
src.includes('cls.kind === "rate-limit"'),
|
|
57
|
+
'agent-end-recovery.ts must reference cls.kind === "rate-limit" for fallback and pause paths (#2770)',
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// The transient fallback pause must pass the isRateLimit flag correctly.
|
|
61
|
+
const pauseCallRe = /pauseTransientWithBackoff\([^)]*cls\.kind\s*===\s*"rate-limit"/;
|
|
62
|
+
assert.ok(
|
|
63
|
+
pauseCallRe.test(src),
|
|
64
|
+
'pauseTransientWithBackoff must receive isRateLimit based on cls.kind === "rate-limit" (#2770)',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("other transient errors (server, connection, stream) still attempt model fallback", () => {
|
|
69
|
+
const src = getRecoverySource();
|
|
70
|
+
|
|
71
|
+
// All transient kinds must appear in the fallback condition.
|
|
72
|
+
for (const kind of ["server", "connection", "stream"]) {
|
|
73
|
+
assert.ok(
|
|
74
|
+
src.includes(`cls.kind === "${kind}"`),
|
|
75
|
+
`model fallback condition must include cls.kind === "${kind}"`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("permanent errors still bypass model fallback and pause indefinitely", () => {
|
|
81
|
+
const src = getRecoverySource();
|
|
82
|
+
|
|
83
|
+
// The permanent/unknown error handler must exist and call pauseAutoForProviderError
|
|
84
|
+
// with isTransient: false.
|
|
85
|
+
const permanentPauseRe = /pauseAutoForProviderError[\s\S]{0,300}isTransient:\s*false/;
|
|
86
|
+
assert.ok(
|
|
87
|
+
permanentPauseRe.test(src),
|
|
88
|
+
'permanent errors must pause with isTransient: false (no auto-resume)',
|
|
89
|
+
);
|
|
90
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #2675: completing-milestone dispatch rule must
|
|
3
|
+
* block completion when VALIDATION verdict is "needs-remediation".
|
|
4
|
+
*
|
|
5
|
+
* Without this guard, needs-remediation + allSlicesDone causes a loop:
|
|
6
|
+
* complete-milestone dispatched → agent refuses (correct) → no SUMMARY
|
|
7
|
+
* → re-dispatch → repeat until stuck detection fires.
|
|
8
|
+
*/
|
|
9
|
+
import { test } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { DISPATCH_RULES } from "../auto-dispatch.ts";
|
|
16
|
+
|
|
17
|
+
/** Find the completing-milestone dispatch rule */
|
|
18
|
+
const completingRule = DISPATCH_RULES.find(r => r.name === "completing-milestone → complete-milestone");
|
|
19
|
+
|
|
20
|
+
test("completing-milestone dispatch rule exists", () => {
|
|
21
|
+
assert.ok(completingRule, "rule should exist in DISPATCH_RULES");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("completing-milestone blocks when VALIDATION verdict is needs-remediation (#2675)", async () => {
|
|
25
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-remediation-"));
|
|
26
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Write a VALIDATION file with needs-remediation verdict
|
|
30
|
+
writeFileSync(
|
|
31
|
+
join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
|
|
32
|
+
[
|
|
33
|
+
"---",
|
|
34
|
+
"verdict: needs-remediation",
|
|
35
|
+
"remediation_round: 0",
|
|
36
|
+
"---",
|
|
37
|
+
"",
|
|
38
|
+
"# Validation Report",
|
|
39
|
+
"",
|
|
40
|
+
"3 success criteria failed. Remediation required.",
|
|
41
|
+
].join("\n"),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const ctx = {
|
|
45
|
+
mid: "M001",
|
|
46
|
+
midTitle: "Test Milestone",
|
|
47
|
+
basePath: base,
|
|
48
|
+
state: { phase: "completing-milestone" } as any,
|
|
49
|
+
prefs: {} as any,
|
|
50
|
+
session: undefined,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = await completingRule!.match(ctx);
|
|
54
|
+
|
|
55
|
+
assert.ok(result !== null, "rule should match");
|
|
56
|
+
assert.equal(result!.action, "stop", "should return stop action");
|
|
57
|
+
if (result!.action === "stop") {
|
|
58
|
+
assert.equal(result!.level, "warning", "should be warning level (pausable)");
|
|
59
|
+
assert.ok(
|
|
60
|
+
result!.reason.includes("needs-remediation"),
|
|
61
|
+
"reason should mention needs-remediation",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(base, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("completing-milestone proceeds normally when VALIDATION verdict is pass (#2675 guard)", async () => {
|
|
70
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-remediation-"));
|
|
71
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Write a VALIDATION file with pass verdict
|
|
75
|
+
writeFileSync(
|
|
76
|
+
join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md"),
|
|
77
|
+
[
|
|
78
|
+
"---",
|
|
79
|
+
"verdict: pass",
|
|
80
|
+
"---",
|
|
81
|
+
"",
|
|
82
|
+
"# Validation Report",
|
|
83
|
+
"",
|
|
84
|
+
"All criteria met.",
|
|
85
|
+
].join("\n"),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const ctx = {
|
|
89
|
+
mid: "M001",
|
|
90
|
+
midTitle: "Test Milestone",
|
|
91
|
+
basePath: base,
|
|
92
|
+
state: { phase: "completing-milestone" } as any,
|
|
93
|
+
prefs: {} as any,
|
|
94
|
+
session: undefined,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const result = await completingRule!.match(ctx);
|
|
98
|
+
|
|
99
|
+
// Should NOT return a stop — should either dispatch or return stop for
|
|
100
|
+
// a different reason (e.g. missing SUMMARY files, no implementation)
|
|
101
|
+
if (result && result.action === "stop") {
|
|
102
|
+
assert.ok(
|
|
103
|
+
!result.reason.includes("needs-remediation"),
|
|
104
|
+
"pass verdict should NOT trigger the remediation guard",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
rmSync(base, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
@@ -724,3 +724,32 @@ test("resolveRemoteConfig returns null when preferences are absent (no env side-
|
|
|
724
724
|
if (savedTelegram !== undefined) process.env.TELEGRAM_BOT_TOKEN = savedTelegram;
|
|
725
725
|
}
|
|
726
726
|
});
|
|
727
|
+
|
|
728
|
+
test("config source-level: hydration skips api_key entries with empty keys", () => {
|
|
729
|
+
const configSrc = readFileSync(
|
|
730
|
+
join(__dirname, "..", "..", "remote-questions", "config.ts"),
|
|
731
|
+
"utf-8",
|
|
732
|
+
);
|
|
733
|
+
// The find() call in hydrateRemoteTokensFromAuth must filter for non-empty keys,
|
|
734
|
+
// not just match on type === "api_key". This prevents stale empty-key entries
|
|
735
|
+
// (left by removeProviderToken) from shadowing valid tokens.
|
|
736
|
+
assert.ok(
|
|
737
|
+
configSrc.includes('c.type === "api_key" && !!c.key'),
|
|
738
|
+
"hydrateRemoteTokensFromAuth find() should require a non-empty key",
|
|
739
|
+
);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test("config source-level: removeProviderToken uses auth.remove not auth.set with empty key", () => {
|
|
743
|
+
const commandSrc = readFileSync(
|
|
744
|
+
join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),
|
|
745
|
+
"utf-8",
|
|
746
|
+
);
|
|
747
|
+
// removeProviderToken should call auth.remove(provider), not auth.set(provider, { key: "" }).
|
|
748
|
+
// Setting an empty key pollutes the credentials array and shadows valid tokens.
|
|
749
|
+
const fnStart = commandSrc.indexOf("function removeProviderToken");
|
|
750
|
+
assert.ok(fnStart !== -1, "removeProviderToken should exist");
|
|
751
|
+
const fnEnd = commandSrc.indexOf("\n}", fnStart);
|
|
752
|
+
const fnBody = commandSrc.slice(fnStart, fnEnd);
|
|
753
|
+
assert.ok(fnBody.includes("auth.remove("), "removeProviderToken should call auth.remove()");
|
|
754
|
+
assert.ok(!fnBody.includes('key: ""'), "removeProviderToken should not set an empty key");
|
|
755
|
+
});
|
|
@@ -95,13 +95,13 @@ async function main(): Promise<void> {
|
|
|
95
95
|
writeFileSync(lockFile, JSON.stringify(lockData, null, 2));
|
|
96
96
|
|
|
97
97
|
// Simulate transient unavailability: move file away, spawn a child process
|
|
98
|
-
// to restore it after
|
|
99
|
-
// fires even during busy-wait retries.
|
|
98
|
+
// to restore it shortly after. The child runs outside our event loop so it
|
|
99
|
+
// fires even during busy-wait retries. Give the test extra retry budget so
|
|
100
|
+
// it stays stable under full-suite CPU contention.
|
|
100
101
|
renameSync(lockFile, tmpFile);
|
|
101
|
-
spawn('bash', ['-c', `sleep 0.
|
|
102
|
+
spawn('bash', ['-c', `sleep 0.05 && mv "${tmpFile}" "${lockFile}"`], { stdio: 'ignore', detached: true }).unref();
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
const result = readExistingLockDataWithRetry(lockFile, { maxAttempts: 3, delayMs: 200 });
|
|
104
|
+
const result = readExistingLockDataWithRetry(lockFile, { maxAttempts: 8, delayMs: 400 });
|
|
105
105
|
assertTrue(result !== null, 'data recovered after transient unavailability');
|
|
106
106
|
if (result) {
|
|
107
107
|
assertEq(result.pid, process.pid, 'correct PID after recovery');
|
|
@@ -131,11 +131,12 @@ async function main(): Promise<void> {
|
|
|
131
131
|
writeFileSync(lockFile, JSON.stringify(lockData, null, 2));
|
|
132
132
|
|
|
133
133
|
// Remove read permission to simulate NFS/CIFS latency, then spawn a child
|
|
134
|
-
// to restore permissions after
|
|
134
|
+
// to restore permissions shortly after (runs outside our event loop).
|
|
135
|
+
// Use the same wider retry window as the rename case for full-suite stability.
|
|
135
136
|
chmodSync(lockFile, 0o000);
|
|
136
|
-
spawn('bash', ['-c', `sleep 0.
|
|
137
|
+
spawn('bash', ['-c', `sleep 0.05 && chmod 644 "${lockFile}"`], { stdio: 'ignore', detached: true }).unref();
|
|
137
138
|
|
|
138
|
-
const result = readExistingLockDataWithRetry(lockFile, { maxAttempts:
|
|
139
|
+
const result = readExistingLockDataWithRetry(lockFile, { maxAttempts: 8, delayMs: 400 });
|
|
139
140
|
assertTrue(result !== null, 'data recovered after transient permission error');
|
|
140
141
|
if (result) {
|
|
141
142
|
assertEq(result.pid, process.pid, 'correct PID after permission recovery');
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stash-pop-gsd-conflict.test.ts — Regression test for #2766.
|
|
3
|
+
*
|
|
4
|
+
* When a squash merge stash-pops and hits conflicts on .gsd/ state files,
|
|
5
|
+
* the UU entries block every subsequent merge. This test verifies that
|
|
6
|
+
* mergeMilestoneToMain auto-resolves .gsd/ conflicts by accepting HEAD
|
|
7
|
+
* and drops the stash, leaving the repo in a clean state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
import { createAutoWorktree, mergeMilestoneToMain } from "../auto-worktree.ts";
|
|
18
|
+
|
|
19
|
+
function run(cmd: string, cwd: string): string {
|
|
20
|
+
return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createTempRepo(): string {
|
|
24
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-stashpop-test-")));
|
|
25
|
+
run("git init", dir);
|
|
26
|
+
run("git config user.email test@test.com", dir);
|
|
27
|
+
run("git config user.name Test", dir);
|
|
28
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
29
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
30
|
+
writeFileSync(join(dir, ".gsd", "STATE.md"), "version: 1\n");
|
|
31
|
+
run("git add .", dir);
|
|
32
|
+
run("git commit -m init", dir);
|
|
33
|
+
run("git branch -M main", dir);
|
|
34
|
+
return dir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeRoadmap(milestoneId: string, title: string, slices: Array<{ id: string; title: string }>): string {
|
|
38
|
+
const sliceLines = slices.map(s => `- [x] **${s.id}: ${s.title}**`).join("\n");
|
|
39
|
+
return `# ${milestoneId}: ${title}\n\n## Slices\n${sliceLines}\n`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test("#2766: stash pop conflict on .gsd/ files is auto-resolved", () => {
|
|
43
|
+
const repo = createTempRepo();
|
|
44
|
+
try {
|
|
45
|
+
const wtPath = createAutoWorktree(repo, "M300");
|
|
46
|
+
|
|
47
|
+
// Add a slice with real code on the milestone branch
|
|
48
|
+
const normalizedPath = wtPath.replaceAll("\\", "/");
|
|
49
|
+
const worktreeName = normalizedPath.split("/").pop() || "M300";
|
|
50
|
+
const sliceBranch = `slice/${worktreeName}/S01`;
|
|
51
|
+
run(`git checkout -b "${sliceBranch}"`, wtPath);
|
|
52
|
+
writeFileSync(join(wtPath, "feature.ts"), "export const feature = true;\n");
|
|
53
|
+
|
|
54
|
+
// Modify .gsd/STATE.md on the milestone branch (diverges from main)
|
|
55
|
+
writeFileSync(join(wtPath, ".gsd", "STATE.md"), "version: 2-milestone\n");
|
|
56
|
+
run("git add .", wtPath);
|
|
57
|
+
run('git commit -m "add feature and update state"', wtPath);
|
|
58
|
+
run("git checkout milestone/M300", wtPath);
|
|
59
|
+
run(`git merge --no-ff "${sliceBranch}" -m "merge S01: feature"`, wtPath);
|
|
60
|
+
|
|
61
|
+
// Dirty .gsd/STATE.md in the main repo (stash will conflict on pop)
|
|
62
|
+
writeFileSync(join(repo, ".gsd", "STATE.md"), "version: 2-main-dirty\n");
|
|
63
|
+
|
|
64
|
+
const roadmap = makeRoadmap("M300", "Stash pop conflict test", [
|
|
65
|
+
{ id: "S01", title: "Feature" },
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
// mergeMilestoneToMain should succeed — .gsd/ conflict auto-resolved
|
|
69
|
+
const result = mergeMilestoneToMain(repo, "M300", roadmap);
|
|
70
|
+
assert.ok(
|
|
71
|
+
result.commitMessage.includes("GSD-Milestone: M300"),
|
|
72
|
+
"merge succeeds despite stash pop conflict on .gsd/ file",
|
|
73
|
+
);
|
|
74
|
+
assert.ok(existsSync(join(repo, "feature.ts")), "milestone code merged to main");
|
|
75
|
+
|
|
76
|
+
// Verify repo is clean (no UU entries blocking future merges)
|
|
77
|
+
const status = run("git status --porcelain", repo);
|
|
78
|
+
assert.ok(
|
|
79
|
+
!status.includes("UU "),
|
|
80
|
+
"no unmerged (UU) entries remain after stash pop conflict resolution",
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Stash should be dropped (no remaining stash entries)
|
|
84
|
+
let stashList = "";
|
|
85
|
+
try { stashList = run("git stash list", repo); } catch { /* empty stash */ }
|
|
86
|
+
assert.strictEqual(stashList, "", "stash is empty after .gsd/ conflict auto-resolution");
|
|
87
|
+
} finally {
|
|
88
|
+
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* cleanup best-effort */ }
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("#2766: stash pop conflict on non-.gsd files preserves stash for manual resolution", () => {
|
|
93
|
+
const repo = createTempRepo();
|
|
94
|
+
try {
|
|
95
|
+
const wtPath = createAutoWorktree(repo, "M301");
|
|
96
|
+
|
|
97
|
+
// Add a slice that modifies a file also dirty on main
|
|
98
|
+
const normalizedPath = wtPath.replaceAll("\\", "/");
|
|
99
|
+
const worktreeName = normalizedPath.split("/").pop() || "M301";
|
|
100
|
+
const sliceBranch = `slice/${worktreeName}/S01`;
|
|
101
|
+
run(`git checkout -b "${sliceBranch}"`, wtPath);
|
|
102
|
+
writeFileSync(join(wtPath, "README.md"), "# milestone version\n");
|
|
103
|
+
run("git add .", wtPath);
|
|
104
|
+
run('git commit -m "update readme"', wtPath);
|
|
105
|
+
run("git checkout milestone/M301", wtPath);
|
|
106
|
+
run(`git merge --no-ff "${sliceBranch}" -m "merge S01: readme"`, wtPath);
|
|
107
|
+
|
|
108
|
+
// Dirty README.md in the main repo — this will conflict on stash pop
|
|
109
|
+
// and is NOT a .gsd/ file, so it should be left for manual resolution
|
|
110
|
+
writeFileSync(join(repo, "README.md"), "# locally modified\n");
|
|
111
|
+
|
|
112
|
+
const roadmap = makeRoadmap("M301", "Non-gsd stash conflict", [
|
|
113
|
+
{ id: "S01", title: "Readme update" },
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
// The merge itself should still succeed (stash pop conflict is non-fatal)
|
|
117
|
+
const result = mergeMilestoneToMain(repo, "M301", roadmap);
|
|
118
|
+
assert.ok(
|
|
119
|
+
result.commitMessage.includes("GSD-Milestone: M301"),
|
|
120
|
+
"merge succeeds even with non-.gsd stash pop conflict",
|
|
121
|
+
);
|
|
122
|
+
} finally {
|
|
123
|
+
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* cleanup best-effort */ }
|
|
124
|
+
}
|
|
125
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// GSD — status-guards unit tests
|
|
2
|
+
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
|
|
6
|
+
import { isClosedStatus } from '../status-guards.ts';
|
|
7
|
+
|
|
8
|
+
test('isClosedStatus: "complete" returns true', () => {
|
|
9
|
+
assert.equal(isClosedStatus('complete'), true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('isClosedStatus: "done" returns true', () => {
|
|
13
|
+
assert.equal(isClosedStatus('done'), true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('isClosedStatus: "pending" returns false', () => {
|
|
17
|
+
assert.equal(isClosedStatus('pending'), false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('isClosedStatus: "in_progress" returns false', () => {
|
|
21
|
+
assert.equal(isClosedStatus('in_progress'), false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('isClosedStatus: "active" returns false', () => {
|
|
25
|
+
assert.equal(isClosedStatus('active'), false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('isClosedStatus: "" (empty string) returns false', () => {
|
|
29
|
+
assert.equal(isClosedStatus(''), false);
|
|
30
|
+
});
|