gsd-pi 2.72.0 → 2.73.0-dev.27730dc
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 +12 -2
- package/dist/cli.js +59 -50
- package/dist/help-text.js +1 -1
- package/dist/onboarding.js +10 -0
- package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
- package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +48 -23
- package/dist/resources/extensions/gsd/auto/loop.js +84 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +5 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
- package/dist/resources/extensions/gsd/auto.js +30 -20
- package/dist/resources/extensions/gsd/bootstrap/crash-log.js +31 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -7
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
- package/dist/resources/extensions/gsd/context-injector.js +1 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +51 -0
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
- package/dist/resources/extensions/gsd/definition-io.js +15 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
- package/dist/resources/extensions/gsd/git-service.js +11 -8
- package/dist/resources/extensions/gsd/gitignore.js +12 -6
- package/dist/resources/extensions/gsd/gsd-db.js +85 -8
- package/dist/resources/extensions/gsd/key-manager.js +2 -0
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -1
- package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
- package/dist/resources/extensions/gsd/preferences-types.js +15 -0
- package/dist/resources/extensions/gsd/preferences.js +16 -3
- package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/state.js +21 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
- package/dist/resources/extensions/gsd/write-intercept.js +10 -1
- package/dist/resources/extensions/ollama/index.js +4 -5
- package/dist/resources/extensions/ollama/ollama-client.js +35 -6
- package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
- package/dist/startup-model-validation.js +8 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- 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/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 +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/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 +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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/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/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/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 +3 -3
- 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 +4 -4
- 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 +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +10 -10
- package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
- package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
- package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
- package/dist/web/standalone/.next/server/chunks/63.js +8 -8
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
- package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
- package/dist/web/standalone/.next/server/middleware.js +4 -12
- 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 +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
- 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-f1e30ab6bb269149.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/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/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +1 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.custom.d.ts +105 -0
- package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.custom.js +97 -0
- package/packages/pi-ai/dist/models.custom.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +648 -140
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +867 -370
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
- package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/models.generated.test.js +334 -0
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
- package/packages/pi-ai/dist/models.test.js +105 -0
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- 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/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +1 -0
- package/packages/pi-ai/src/models.custom.ts +98 -0
- package/packages/pi-ai/src/models.generated.test.ts +373 -0
- package/packages/pi-ai/src/models.generated.ts +867 -370
- package/packages/pi-ai/src/models.test.ts +135 -0
- package/packages/pi-ai/src/types.ts +1 -0
- package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +25 -67
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- 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 +22 -9
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +63 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +38 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +26 -69
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +71 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +23 -9
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/keys.d.ts.map +1 -1
- package/packages/pi-tui/dist/keys.js +27 -0
- package/packages/pi-tui/dist/keys.js.map +1 -1
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
- package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
- package/packages/pi-tui/src/keys.ts +32 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
- package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
- package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +49 -24
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +112 -0
- package/src/resources/extensions/gsd/auto/loop.ts +89 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
- package/src/resources/extensions/gsd/auto.ts +30 -20
- package/src/resources/extensions/gsd/bootstrap/crash-log.ts +32 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
- package/src/resources/extensions/gsd/context-injector.ts +1 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +59 -0
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
- package/src/resources/extensions/gsd/definition-io.ts +18 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
- package/src/resources/extensions/gsd/git-service.ts +11 -8
- package/src/resources/extensions/gsd/gitignore.ts +12 -6
- package/src/resources/extensions/gsd/gsd-db.ts +106 -8
- package/src/resources/extensions/gsd/key-manager.ts +2 -0
- package/src/resources/extensions/gsd/milestone-actions.ts +19 -1
- package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +19 -6
- package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/state.ts +20 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +235 -0
- package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +165 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
- package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +267 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
- package/src/resources/extensions/gsd/write-intercept.ts +10 -1
- package/src/resources/extensions/ollama/index.ts +4 -5
- package/src/resources/extensions/ollama/ollama-client.ts +35 -6
- package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/auto-observability.js +0 -54
- package/dist/resources/extensions/gsd/file-watcher.js +0 -80
- package/dist/resources/extensions/gsd/rtk-status.js +0 -43
- package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.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/src/resources/extensions/gsd/auto-observability.ts +0 -72
- package/src/resources/extensions/gsd/file-watcher.ts +0 -100
- package/src/resources/extensions/gsd/rtk-status.ts +0 -53
- /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → jNiH700EcljeLnbQ2_RCv}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → jNiH700EcljeLnbQ2_RCv}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #3674 — block direct writes to gsd.db
|
|
3
|
+
*
|
|
4
|
+
* When gsd_complete_task was unavailable, agents fell back to shell-based
|
|
5
|
+
* sqlite3 writes, corrupting the WAL-backed database. The fix extends
|
|
6
|
+
* write-intercept to block file writes and bash commands targeting gsd.db.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, test } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { isBlockedStateFile, isBashWriteToStateFile } from '../write-intercept.ts';
|
|
12
|
+
|
|
13
|
+
describe('isBlockedStateFile blocks gsd.db paths (#3674)', () => {
|
|
14
|
+
test('blocks .gsd/gsd.db', () => {
|
|
15
|
+
assert.ok(isBlockedStateFile('/project/.gsd/gsd.db'));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('blocks .gsd/gsd.db-wal', () => {
|
|
19
|
+
assert.ok(isBlockedStateFile('/project/.gsd/gsd.db-wal'));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('blocks .gsd/gsd.db-shm', () => {
|
|
23
|
+
assert.ok(isBlockedStateFile('/project/.gsd/gsd.db-shm'));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('blocks resolved symlink path under .gsd/projects/', () => {
|
|
27
|
+
assert.ok(isBlockedStateFile('/home/user/.gsd/projects/myproj/gsd.db'));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('still blocks STATE.md', () => {
|
|
31
|
+
assert.ok(isBlockedStateFile('/project/.gsd/STATE.md'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('does not block other .gsd files', () => {
|
|
35
|
+
assert.ok(!isBlockedStateFile('/project/.gsd/DECISIONS.md'));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('isBashWriteToStateFile blocks DB shell commands (#3674)', () => {
|
|
40
|
+
test('blocks sqlite3 targeting gsd.db', () => {
|
|
41
|
+
assert.ok(isBashWriteToStateFile('sqlite3 .gsd/gsd.db "INSERT INTO ..."'));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('blocks better-sqlite3 targeting gsd.db', () => {
|
|
45
|
+
assert.ok(isBashWriteToStateFile('node -e "require(\'better-sqlite3\')(\'.gsd/gsd.db\')"'));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('blocks shell redirect to gsd.db', () => {
|
|
49
|
+
assert.ok(isBashWriteToStateFile('echo data > .gsd/gsd.db'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('blocks cp to gsd.db', () => {
|
|
53
|
+
assert.ok(isBashWriteToStateFile('cp backup.db .gsd/gsd.db'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('blocks mv to gsd.db', () => {
|
|
57
|
+
assert.ok(isBashWriteToStateFile('mv temp.db .gsd/gsd.db'));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('does not block reading gsd.db with cat', () => {
|
|
61
|
+
assert.ok(!isBashWriteToStateFile('cat .gsd/gsd.db'));
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for #3348 secondary issues — crash handler gaps surfaced after #3696
|
|
3
|
+
*
|
|
4
|
+
* 1. register-extension.ts: writeCrashLog writes to ~/.gsd/crash/ directory
|
|
5
|
+
* 2. register-extension.ts: _gsdRejectionGuard registered for unhandledRejection
|
|
6
|
+
* 3. register-extension.ts: _gsdEpipeGuard exits with code 1 for unrecoverable errors (no log-and-continue)
|
|
7
|
+
* 4. crash-recovery.ts: emitCrashRecoveredUnitEnd closes open unit-start journal entries
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, test } from 'node:test';
|
|
11
|
+
import assert from 'node:assert/strict';
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
|
|
22
|
+
function makeTmpBase(): string {
|
|
23
|
+
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
|
24
|
+
mkdirSync(join(base, '.gsd'), { recursive: true });
|
|
25
|
+
return base;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── register-extension source assertions ────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const registerExtSrc = readFileSync(
|
|
31
|
+
join(__dirname, '..', 'bootstrap', 'register-extension.ts'),
|
|
32
|
+
'utf-8',
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
describe('register-extension crash handler secondary fixes (#3348)', () => {
|
|
36
|
+
test('writeCrashLog is exported and writes a file to the crash directory', async () => {
|
|
37
|
+
// Dynamic import so GSD_HOME can be pointed at a temp dir without polluting ~/.gsd
|
|
38
|
+
const tmpHome = join(tmpdir(), `gsd-crash-test-${randomUUID()}`);
|
|
39
|
+
const origHome = process.env.GSD_HOME;
|
|
40
|
+
process.env.GSD_HOME = tmpHome;
|
|
41
|
+
try {
|
|
42
|
+
const { writeCrashLog } = await import('../bootstrap/crash-log.ts');
|
|
43
|
+
const err = new Error('test crash from secondary regression test');
|
|
44
|
+
writeCrashLog(err, 'uncaughtException');
|
|
45
|
+
|
|
46
|
+
const crashDir = join(tmpHome, 'crash');
|
|
47
|
+
assert.ok(existsSync(crashDir), 'crash directory should be created');
|
|
48
|
+
|
|
49
|
+
const logs = readdirSync(crashDir).filter((f) => f.endsWith('.log'));
|
|
50
|
+
assert.equal(logs.length, 1, 'exactly one crash log should be written');
|
|
51
|
+
|
|
52
|
+
const content = readFileSync(join(crashDir, logs[0]), 'utf-8');
|
|
53
|
+
assert.ok(content.includes('test crash from secondary regression test'), 'log should contain error message');
|
|
54
|
+
assert.ok(content.includes('uncaughtException'), 'log should identify the source');
|
|
55
|
+
assert.ok(content.includes('pid:'), 'log should include process pid');
|
|
56
|
+
} finally {
|
|
57
|
+
process.env.GSD_HOME = origHome;
|
|
58
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('_gsdRejectionGuard is registered for unhandledRejection', () => {
|
|
63
|
+
assert.match(
|
|
64
|
+
registerExtSrc,
|
|
65
|
+
/_gsdRejectionGuard/,
|
|
66
|
+
'_gsdRejectionGuard handler should be defined',
|
|
67
|
+
);
|
|
68
|
+
assert.match(
|
|
69
|
+
registerExtSrc,
|
|
70
|
+
/unhandledRejection/,
|
|
71
|
+
'installEpipeGuard should register an unhandledRejection handler',
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('_gsdEpipeGuard calls process.exit(1) for unrecoverable errors, not log-and-continue', () => {
|
|
76
|
+
// The original #3696 fix replaced "throw err" with a log-and-continue.
|
|
77
|
+
// The secondary fix replaces that with writeCrashLog + process.exit(1).
|
|
78
|
+
assert.ok(
|
|
79
|
+
!registerExtSrc.includes('process.stderr.write(`[gsd] uncaught extension error (non-fatal)'),
|
|
80
|
+
'_gsdEpipeGuard should NOT log errors as non-fatal and continue',
|
|
81
|
+
);
|
|
82
|
+
assert.match(
|
|
83
|
+
registerExtSrc,
|
|
84
|
+
/process\.exit\(1\)/,
|
|
85
|
+
'_gsdEpipeGuard should call process.exit(1) for unrecoverable errors',
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('writeCrashLog never throws even when directory is unwritable', async () => {
|
|
90
|
+
const { writeCrashLog } = await import('../bootstrap/crash-log.ts');
|
|
91
|
+
const origHome = process.env.GSD_HOME;
|
|
92
|
+
// Point at a path that will fail to mkdir (e.g. a file that exists as non-dir)
|
|
93
|
+
const tmpFile = join(tmpdir(), `gsd-not-a-dir-${randomUUID()}`);
|
|
94
|
+
// Don't create it — mkdirSync with bad path should be caught internally
|
|
95
|
+
process.env.GSD_HOME = join(tmpFile, 'nested', 'deeply');
|
|
96
|
+
try {
|
|
97
|
+
// Should not throw
|
|
98
|
+
assert.doesNotThrow(() => {
|
|
99
|
+
writeCrashLog(new Error('should not throw'), 'test');
|
|
100
|
+
});
|
|
101
|
+
} finally {
|
|
102
|
+
process.env.GSD_HOME = origHome;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ─── emitCrashRecoveredUnitEnd ────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
describe('emitCrashRecoveredUnitEnd (#3348)', () => {
|
|
110
|
+
test('emits synthetic unit-end when unit-start has no matching unit-end', async () => {
|
|
111
|
+
const base = makeTmpBase();
|
|
112
|
+
try {
|
|
113
|
+
const { emitJournalEvent, queryJournal } = await import('../journal.ts');
|
|
114
|
+
const { emitCrashRecoveredUnitEnd } = await import('../crash-recovery.ts');
|
|
115
|
+
|
|
116
|
+
const flowId = randomUUID();
|
|
117
|
+
const unitStartSeq = 5;
|
|
118
|
+
|
|
119
|
+
// Emit a unit-start with no corresponding unit-end (simulating a crash)
|
|
120
|
+
emitJournalEvent(base, {
|
|
121
|
+
ts: new Date().toISOString(),
|
|
122
|
+
flowId,
|
|
123
|
+
seq: unitStartSeq,
|
|
124
|
+
eventType: 'unit-start',
|
|
125
|
+
data: { unitType: 'execute-task', unitId: 'M001/S01/T01' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const lock = {
|
|
129
|
+
pid: 99999,
|
|
130
|
+
startedAt: new Date().toISOString(),
|
|
131
|
+
unitType: 'execute-task',
|
|
132
|
+
unitId: 'M001/S01/T01',
|
|
133
|
+
unitStartedAt: new Date().toISOString(),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
emitCrashRecoveredUnitEnd(base, lock);
|
|
137
|
+
|
|
138
|
+
const events = queryJournal(base);
|
|
139
|
+
const ends = events.filter((e) => e.eventType === 'unit-end');
|
|
140
|
+
assert.equal(ends.length, 1, 'should emit exactly one unit-end');
|
|
141
|
+
assert.equal(ends[0].data?.unitId, 'M001/S01/T01');
|
|
142
|
+
assert.equal(ends[0].data?.status, 'crash-recovered');
|
|
143
|
+
assert.equal(ends[0].causedBy?.flowId, flowId);
|
|
144
|
+
assert.equal(ends[0].causedBy?.seq, unitStartSeq);
|
|
145
|
+
assert.ok(ends[0].seq > unitStartSeq, 'unit-end seq must be higher than unit-start seq');
|
|
146
|
+
} finally {
|
|
147
|
+
rmSync(base, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('is a no-op when unit-end was already emitted (e.g. hard timeout fired)', async () => {
|
|
152
|
+
const base = makeTmpBase();
|
|
153
|
+
try {
|
|
154
|
+
const { emitJournalEvent, queryJournal } = await import('../journal.ts');
|
|
155
|
+
const { emitCrashRecoveredUnitEnd } = await import('../crash-recovery.ts');
|
|
156
|
+
|
|
157
|
+
const flowId = randomUUID();
|
|
158
|
+
emitJournalEvent(base, {
|
|
159
|
+
ts: new Date().toISOString(),
|
|
160
|
+
flowId,
|
|
161
|
+
seq: 3,
|
|
162
|
+
eventType: 'unit-start',
|
|
163
|
+
data: { unitType: 'plan-slice', unitId: 'M001/S02' },
|
|
164
|
+
});
|
|
165
|
+
// Hard timeout already emitted a unit-end
|
|
166
|
+
emitJournalEvent(base, {
|
|
167
|
+
ts: new Date().toISOString(),
|
|
168
|
+
flowId,
|
|
169
|
+
seq: 4,
|
|
170
|
+
eventType: 'unit-end',
|
|
171
|
+
data: { unitType: 'plan-slice', unitId: 'M001/S02', status: 'cancelled' },
|
|
172
|
+
causedBy: { flowId, seq: 3 },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const lock = {
|
|
176
|
+
pid: 99999,
|
|
177
|
+
startedAt: new Date().toISOString(),
|
|
178
|
+
unitType: 'plan-slice',
|
|
179
|
+
unitId: 'M001/S02',
|
|
180
|
+
unitStartedAt: new Date().toISOString(),
|
|
181
|
+
};
|
|
182
|
+
emitCrashRecoveredUnitEnd(base, lock);
|
|
183
|
+
|
|
184
|
+
const ends = queryJournal(base).filter((e) => e.eventType === 'unit-end');
|
|
185
|
+
assert.equal(ends.length, 1, 'should not emit a duplicate unit-end');
|
|
186
|
+
assert.equal(ends[0].data?.status, 'cancelled', 'original unit-end should be preserved');
|
|
187
|
+
} finally {
|
|
188
|
+
rmSync(base, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('is a no-op for "starting" pseudo-units (bootstrap crash)', async () => {
|
|
193
|
+
const base = makeTmpBase();
|
|
194
|
+
try {
|
|
195
|
+
const { queryJournal } = await import('../journal.ts');
|
|
196
|
+
const { emitCrashRecoveredUnitEnd } = await import('../crash-recovery.ts');
|
|
197
|
+
|
|
198
|
+
const lock = {
|
|
199
|
+
pid: 99999,
|
|
200
|
+
startedAt: new Date().toISOString(),
|
|
201
|
+
unitType: 'starting',
|
|
202
|
+
unitId: 'bootstrap',
|
|
203
|
+
unitStartedAt: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
emitCrashRecoveredUnitEnd(base, lock);
|
|
206
|
+
|
|
207
|
+
const events = queryJournal(base);
|
|
208
|
+
assert.equal(events.length, 0, 'should emit nothing for starting/bootstrap pseudo-units');
|
|
209
|
+
} finally {
|
|
210
|
+
rmSync(base, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('is a no-op when no unit-start exists in the journal', async () => {
|
|
215
|
+
const base = makeTmpBase();
|
|
216
|
+
try {
|
|
217
|
+
const { queryJournal } = await import('../journal.ts');
|
|
218
|
+
const { emitCrashRecoveredUnitEnd } = await import('../crash-recovery.ts');
|
|
219
|
+
|
|
220
|
+
const lock = {
|
|
221
|
+
pid: 99999,
|
|
222
|
+
startedAt: new Date().toISOString(),
|
|
223
|
+
unitType: 'execute-task',
|
|
224
|
+
unitId: 'M002/S01/T03',
|
|
225
|
+
unitStartedAt: new Date().toISOString(),
|
|
226
|
+
};
|
|
227
|
+
emitCrashRecoveredUnitEnd(base, lock);
|
|
228
|
+
|
|
229
|
+
const events = queryJournal(base);
|
|
230
|
+
assert.equal(events.length, 0, 'should emit nothing when there is no journal entry to close');
|
|
231
|
+
} finally {
|
|
232
|
+
rmSync(base, { recursive: true, force: true });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* definition-io.ts — unit tests for readFrozenDefinition.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
|
|
11
|
+
import { readFrozenDefinition } from "../definition-io.ts";
|
|
12
|
+
|
|
13
|
+
function createTmpDir(): string {
|
|
14
|
+
return realpathSync(mkdtempSync(join(tmpdir(), "gsd-defio-test-")));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("readFrozenDefinition", () => {
|
|
18
|
+
let runDir: string;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
runDir = createTmpDir();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
rmSync(runDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("parses a valid DEFINITION.yaml", () => {
|
|
29
|
+
const yaml = [
|
|
30
|
+
"version: 1",
|
|
31
|
+
"name: test-workflow",
|
|
32
|
+
"description: A test workflow",
|
|
33
|
+
"steps:",
|
|
34
|
+
" - id: step-1",
|
|
35
|
+
" prompt: do the thing",
|
|
36
|
+
].join("\n");
|
|
37
|
+
writeFileSync(join(runDir, "DEFINITION.yaml"), yaml, "utf-8");
|
|
38
|
+
|
|
39
|
+
const def = readFrozenDefinition(runDir);
|
|
40
|
+
assert.equal(def.version, 1);
|
|
41
|
+
assert.equal(def.name, "test-workflow");
|
|
42
|
+
assert.equal(def.description, "A test workflow");
|
|
43
|
+
assert.equal(def.steps.length, 1);
|
|
44
|
+
assert.equal(def.steps[0].id, "step-1");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("throws when DEFINITION.yaml is missing", () => {
|
|
48
|
+
assert.throws(() => readFrozenDefinition(runDir), {
|
|
49
|
+
code: "ENOENT",
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("throws on malformed YAML", () => {
|
|
54
|
+
writeFileSync(join(runDir, "DEFINITION.yaml"), ": : : not valid yaml [", "utf-8");
|
|
55
|
+
assert.throws(() => readFrozenDefinition(runDir));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -172,6 +172,32 @@ test("dispatch guard ignores positionally-earlier reverse dependents for zero-de
|
|
|
172
172
|
);
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
+
test("dispatch guard treats zero-dependency slices as independent when a milestone uses explicit deps (#3998)", (t) => {
|
|
176
|
+
const repo = setupRepo();
|
|
177
|
+
t.after(() => teardownRepo(repo));
|
|
178
|
+
|
|
179
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M022"), { recursive: true });
|
|
180
|
+
|
|
181
|
+
insertMilestone({ id: "M022", title: "Mixed dependency milestone" });
|
|
182
|
+
insertSlice({ id: "S02", milestoneId: "M022", title: "Core A", status: "complete", depends: [], sequence: 2 });
|
|
183
|
+
insertSlice({ id: "S03", milestoneId: "M022", title: "Core B", status: "complete", depends: [], sequence: 3 });
|
|
184
|
+
insertSlice({ id: "S05", milestoneId: "M022", title: "Blocked integration", status: "pending", depends: ["S02", "S03", "S07"], sequence: 5 });
|
|
185
|
+
insertSlice({ id: "S06", milestoneId: "M022", title: "Independent zero-dep slice", status: "pending", depends: [], sequence: 6 });
|
|
186
|
+
insertSlice({ id: "S07", milestoneId: "M022", title: "Late prerequisite", status: "pending", depends: ["S02"], sequence: 7 });
|
|
187
|
+
|
|
188
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M022", "M022-ROADMAP.md"), "# M022\n");
|
|
189
|
+
|
|
190
|
+
assert.equal(
|
|
191
|
+
getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M022/S06/T02"),
|
|
192
|
+
null,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
assert.equal(
|
|
196
|
+
getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M022/S05/T01"),
|
|
197
|
+
"Cannot dispatch execute-task M022/S05/T01: dependency slice M022/S07 is not complete.",
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
175
201
|
test("dispatch guard allows slice with all declared dependencies complete", (t) => {
|
|
176
202
|
const repo = setupRepo();
|
|
177
203
|
t.after(() => teardownRepo(repo));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { isDoctorHealActionable } from "../commands-handlers.js";
|
|
4
|
+
|
|
5
|
+
test("doctor heal actionable filter keeps fixable warnings and errors", () => {
|
|
6
|
+
assert.equal(isDoctorHealActionable({ fixable: true, severity: "warning" }), true);
|
|
7
|
+
assert.equal(isDoctorHealActionable({ fixable: true, severity: "error" }), true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("doctor heal actionable filter excludes info and non-fixable issues", () => {
|
|
11
|
+
assert.equal(isDoctorHealActionable({ fixable: true, severity: "info" }), false);
|
|
12
|
+
assert.equal(isDoctorHealActionable({ fixable: false, severity: "warning" }), false);
|
|
13
|
+
assert.equal(isDoctorHealActionable({ fixable: false, severity: "error" }), false);
|
|
14
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* false-degraded-mode-warning.test.ts — Regression tests for #3922.
|
|
3
|
+
*
|
|
4
|
+
* Before this fix, deriveState() logged a "DB unavailable — degraded mode"
|
|
5
|
+
* warning even when the DB simply hadn't been opened yet (e.g. during
|
|
6
|
+
* before_agent_start context injection). The fix introduces wasDbOpenAttempted()
|
|
7
|
+
* to distinguish "not yet initialized" from "genuinely unavailable."
|
|
8
|
+
*
|
|
9
|
+
* Two aspects:
|
|
10
|
+
* 1. gsd-db: wasDbOpenAttempted() tracks whether openDatabase() was ever called.
|
|
11
|
+
* 2. state: the degraded-mode warning is gated behind wasDbOpenAttempted().
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, test } from "node:test";
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import {
|
|
20
|
+
openDatabase,
|
|
21
|
+
closeDatabase,
|
|
22
|
+
isDbAvailable,
|
|
23
|
+
wasDbOpenAttempted,
|
|
24
|
+
} from "../gsd-db.ts";
|
|
25
|
+
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const stateSource = readFileSync(join(__dirname, "..", "state.ts"), "utf-8");
|
|
28
|
+
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
30
|
+
// 1. gsd-db: wasDbOpenAttempted flag
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
32
|
+
|
|
33
|
+
describe("wasDbOpenAttempted (#3922)", () => {
|
|
34
|
+
|
|
35
|
+
test("wasDbOpenAttempted returns true after openDatabase is called", () => {
|
|
36
|
+
// By this point in the test suite, openDatabase may or may not have been
|
|
37
|
+
// called by other tests. So we call it explicitly and verify it returns true.
|
|
38
|
+
openDatabase(":memory:");
|
|
39
|
+
assert.strictEqual(wasDbOpenAttempted(), true,
|
|
40
|
+
"wasDbOpenAttempted should be true after openDatabase call");
|
|
41
|
+
closeDatabase();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("openDatabase sets the flag even if it fails on invalid path", () => {
|
|
45
|
+
// openDatabase with an unreachable path may fail, but the flag should
|
|
46
|
+
// still be set because the attempt was made.
|
|
47
|
+
try { openDatabase("/nonexistent/path/that/will/fail.db"); } catch { /* expected */ }
|
|
48
|
+
assert.strictEqual(wasDbOpenAttempted(), true,
|
|
49
|
+
"wasDbOpenAttempted should be true even after a failed open attempt");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
54
|
+
// 2. state.ts: degraded-mode warning is gated behind wasDbOpenAttempted
|
|
55
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
56
|
+
|
|
57
|
+
describe("degraded-mode warning guard (#3922)", () => {
|
|
58
|
+
|
|
59
|
+
test("state.ts imports wasDbOpenAttempted from gsd-db", () => {
|
|
60
|
+
assert.ok(
|
|
61
|
+
stateSource.includes("wasDbOpenAttempted"),
|
|
62
|
+
"state.ts must import wasDbOpenAttempted to gate the degraded-mode warning",
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("degraded-mode warning is inside a wasDbOpenAttempted() guard", () => {
|
|
67
|
+
// Find the degraded-mode warning string
|
|
68
|
+
const warningStr = 'DB unavailable — using filesystem state derivation (degraded mode)';
|
|
69
|
+
const warningIdx = stateSource.indexOf(warningStr);
|
|
70
|
+
assert.ok(warningIdx > 0, "degraded-mode warning string must exist in state.ts");
|
|
71
|
+
|
|
72
|
+
// The wasDbOpenAttempted() check must appear BEFORE the warning,
|
|
73
|
+
// within the same else-branch (i.e. within a reasonable distance).
|
|
74
|
+
// Look backwards from the warning for the guard.
|
|
75
|
+
const searchWindow = stateSource.slice(Math.max(0, warningIdx - 300), warningIdx);
|
|
76
|
+
assert.ok(
|
|
77
|
+
searchWindow.includes("wasDbOpenAttempted()"),
|
|
78
|
+
"wasDbOpenAttempted() guard must appear shortly before the degraded-mode warning " +
|
|
79
|
+
"to prevent false warnings when DB has not been initialized yet",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("warning is NOT emitted unconditionally in the else branch", () => {
|
|
84
|
+
// The old code had `logWarning(...)` directly in the else branch.
|
|
85
|
+
// The fix wraps it in `if (wasDbOpenAttempted())`.
|
|
86
|
+
// Verify the logWarning call is inside a conditional, not bare.
|
|
87
|
+
const lines = stateSource.split("\n");
|
|
88
|
+
for (let i = 0; i < lines.length; i++) {
|
|
89
|
+
if (lines[i]!.includes("DB unavailable") && lines[i]!.includes("degraded mode")) {
|
|
90
|
+
// This line has the warning. Check that the preceding non-empty line
|
|
91
|
+
// contains an if-condition (wasDbOpenAttempted), not a bare else.
|
|
92
|
+
let prev = i - 1;
|
|
93
|
+
while (prev >= 0 && lines[prev]!.trim() === "") prev--;
|
|
94
|
+
const prevLine = lines[prev]!.trim();
|
|
95
|
+
assert.ok(
|
|
96
|
+
prevLine.includes("wasDbOpenAttempted"),
|
|
97
|
+
`Line ${i + 1} emits degraded-mode warning — preceding line ${prev + 1} must ` +
|
|
98
|
+
`contain wasDbOpenAttempted guard, but found: "${prevLine}"`,
|
|
99
|
+
);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|