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
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
import test from "node:test";
|
|
12
12
|
import assert from "node:assert/strict";
|
|
13
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
13
16
|
import {
|
|
14
17
|
validatePreferences,
|
|
15
18
|
applyModeDefaults,
|
|
16
19
|
getIsolationMode,
|
|
20
|
+
loadEffectiveGSDPreferences,
|
|
17
21
|
parsePreferencesMarkdown,
|
|
18
22
|
_resetParseWarningFlag,
|
|
19
23
|
} from "../preferences.ts";
|
|
@@ -501,6 +505,55 @@ test("experimental.rtk parses correctly from preferences markdown", () => {
|
|
|
501
505
|
assert.equal(prefs!.experimental?.rtk, true);
|
|
502
506
|
});
|
|
503
507
|
|
|
508
|
+
test("loadEffectiveGSDPreferences preserves experimental prefs across global+project merge", () => {
|
|
509
|
+
const originalCwd = process.cwd();
|
|
510
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
511
|
+
const tempProject = mkdtempSync(join(tmpdir(), "gsd-prefs-project-"));
|
|
512
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-prefs-home-"));
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
516
|
+
|
|
517
|
+
writeFileSync(
|
|
518
|
+
join(tempGsdHome, "preferences.md"),
|
|
519
|
+
[
|
|
520
|
+
"---",
|
|
521
|
+
"version: 1",
|
|
522
|
+
"experimental:",
|
|
523
|
+
" rtk: true",
|
|
524
|
+
"---",
|
|
525
|
+
].join("\n"),
|
|
526
|
+
"utf-8",
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
writeFileSync(
|
|
530
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
531
|
+
[
|
|
532
|
+
"---",
|
|
533
|
+
"version: 1",
|
|
534
|
+
"git:",
|
|
535
|
+
" isolation: none",
|
|
536
|
+
"---",
|
|
537
|
+
].join("\n"),
|
|
538
|
+
"utf-8",
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
542
|
+
process.chdir(tempProject);
|
|
543
|
+
|
|
544
|
+
const loaded = loadEffectiveGSDPreferences();
|
|
545
|
+
assert.notEqual(loaded, null);
|
|
546
|
+
assert.equal(loaded!.preferences.experimental?.rtk, true);
|
|
547
|
+
assert.equal(loaded!.preferences.git?.isolation, "none");
|
|
548
|
+
} finally {
|
|
549
|
+
process.chdir(originalCwd);
|
|
550
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
551
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
552
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
553
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
504
557
|
test("experimental.rtk defaults to off in new project preferences", () => {
|
|
505
558
|
// No experimental key → feature is disabled
|
|
506
559
|
const content = "---\nversion: 1\n---\n";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import test from 'node:test';
|
|
6
6
|
import assert from 'node:assert/strict';
|
|
7
7
|
|
|
8
|
-
import { renderPlanContent, renderRoadmapContent } from '../workflow-projections.ts';
|
|
8
|
+
import { renderPlanContent, renderRoadmapContent, renderSummaryContent } from '../workflow-projections.ts';
|
|
9
9
|
import type { SliceRow, TaskRow } from '../gsd-db.ts';
|
|
10
10
|
|
|
11
11
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
@@ -172,3 +172,98 @@ test('renderRoadmapContent: slice with status "pending" shows ⬜', () => {
|
|
|
172
172
|
|
|
173
173
|
assert.ok(content.includes('⬜'), 'pending slice should show ⬜');
|
|
174
174
|
});
|
|
175
|
+
|
|
176
|
+
// ─── renderSummaryContent: double-frontmatter regression ─────────────────
|
|
177
|
+
|
|
178
|
+
test('renderSummaryContent: uses full_summary_md as-is when it contains frontmatter', () => {
|
|
179
|
+
const existingSummary = [
|
|
180
|
+
'---',
|
|
181
|
+
'id: T01',
|
|
182
|
+
'parent: S01',
|
|
183
|
+
'milestone: M001',
|
|
184
|
+
'key_files:',
|
|
185
|
+
' - src/thing.ts',
|
|
186
|
+
'verification_result: passed',
|
|
187
|
+
'completed_at: 2026-01-01T00:00:00Z',
|
|
188
|
+
'blocker_discovered: false',
|
|
189
|
+
'---',
|
|
190
|
+
'',
|
|
191
|
+
'# T01: Did the thing',
|
|
192
|
+
'',
|
|
193
|
+
'**One-liner summary**',
|
|
194
|
+
'',
|
|
195
|
+
'## What Happened',
|
|
196
|
+
'',
|
|
197
|
+
'Narrative content here.',
|
|
198
|
+
'',
|
|
199
|
+
'## Deviations',
|
|
200
|
+
'',
|
|
201
|
+
'None.',
|
|
202
|
+
'',
|
|
203
|
+
].join('\n');
|
|
204
|
+
|
|
205
|
+
const task = makeTaskRow({
|
|
206
|
+
id: 'T01',
|
|
207
|
+
status: 'complete',
|
|
208
|
+
title: 'Did the thing',
|
|
209
|
+
one_liner: 'One-liner summary',
|
|
210
|
+
narrative: 'Narrative content here.',
|
|
211
|
+
full_summary_md: existingSummary,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const result = renderSummaryContent(task, 'S01', 'M001');
|
|
215
|
+
|
|
216
|
+
// Must NOT produce double frontmatter
|
|
217
|
+
const frontmatterCount = (result.match(/^---$/gm) || []).length;
|
|
218
|
+
assert.equal(frontmatterCount, 2, `Expected exactly 2 frontmatter delimiters (one block), got ${frontmatterCount}`);
|
|
219
|
+
|
|
220
|
+
// Must NOT produce double H1 heading
|
|
221
|
+
const h1Count = (result.match(/^# T01:/gm) || []).length;
|
|
222
|
+
assert.equal(h1Count, 1, `Expected exactly 1 H1 heading, got ${h1Count}`);
|
|
223
|
+
|
|
224
|
+
// Content should match the full_summary_md exactly
|
|
225
|
+
assert.equal(result, existingSummary);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('renderSummaryContent: synthesizes from DB columns when full_summary_md is empty', () => {
|
|
229
|
+
const task = makeTaskRow({
|
|
230
|
+
id: 'T01',
|
|
231
|
+
status: 'complete',
|
|
232
|
+
title: 'Did the thing',
|
|
233
|
+
one_liner: 'One-liner summary',
|
|
234
|
+
narrative: 'Built the feature.',
|
|
235
|
+
full_summary_md: '',
|
|
236
|
+
deviations: 'Deviated slightly.',
|
|
237
|
+
known_issues: 'None.',
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const result = renderSummaryContent(task, 'S01', 'M001');
|
|
241
|
+
|
|
242
|
+
// Should have exactly one frontmatter block
|
|
243
|
+
const frontmatterCount = (result.match(/^---$/gm) || []).length;
|
|
244
|
+
assert.equal(frontmatterCount, 2, 'Should have one frontmatter block (2 delimiters)');
|
|
245
|
+
|
|
246
|
+
// Should contain synthesized sections
|
|
247
|
+
assert.ok(result.includes('## What Happened'), 'Should have What Happened section');
|
|
248
|
+
assert.ok(result.includes('Built the feature.'), 'Should use narrative for content');
|
|
249
|
+
assert.ok(result.includes('## Deviations'), 'Should have Deviations section');
|
|
250
|
+
assert.ok(result.includes('Deviated slightly.'), 'Should include deviation text');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('renderSummaryContent: synthesizes when full_summary_md has no frontmatter', () => {
|
|
254
|
+
const task = makeTaskRow({
|
|
255
|
+
id: 'T02',
|
|
256
|
+
status: 'complete',
|
|
257
|
+
title: 'Partial summary',
|
|
258
|
+
narrative: 'Did some work.',
|
|
259
|
+
full_summary_md: 'Just a plain text summary with no frontmatter.',
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const result = renderSummaryContent(task, 'S01', 'M001');
|
|
263
|
+
|
|
264
|
+
// Should synthesize with proper frontmatter since the stored md lacks it
|
|
265
|
+
assert.ok(result.startsWith('---'), 'Should start with frontmatter');
|
|
266
|
+
assert.ok(result.includes('id: T02'), 'Should have task ID in frontmatter');
|
|
267
|
+
assert.ok(result.includes('## What Happened'), 'Should have What Happened section');
|
|
268
|
+
assert.ok(result.includes('Did some work.'), 'Should use narrative');
|
|
269
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { loadPrompt } from "../prompt-loader.ts";
|
|
5
|
+
|
|
6
|
+
test("loadPrompt normalizes workingDirectory backslashes for bash-friendly prompts (#4048)", () => {
|
|
7
|
+
const prompt = loadPrompt("research-milestone", {
|
|
8
|
+
milestoneId: "M001",
|
|
9
|
+
milestoneTitle: "Windows path fix",
|
|
10
|
+
workingDirectory: "C:\\Dev\\NB\\TR",
|
|
11
|
+
inlinedContext: "context",
|
|
12
|
+
skillActivation: "skill activation",
|
|
13
|
+
skillDiscoveryMode: "off",
|
|
14
|
+
skillDiscoveryInstructions: " disabled",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
assert.match(prompt, /Your working directory is `C:\/Dev\/NB\/TR`/);
|
|
18
|
+
assert.doesNotMatch(prompt, /C:\\Dev\\NB\\TR/);
|
|
19
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { registerHooks } from "../bootstrap/register-hooks.ts";
|
|
8
|
+
import {
|
|
9
|
+
getPendingGate,
|
|
10
|
+
resetWriteGateState,
|
|
11
|
+
shouldBlockContextArtifactSave,
|
|
12
|
+
} from "../bootstrap/write-gate.ts";
|
|
13
|
+
|
|
14
|
+
function makeTempDir(prefix: string): string {
|
|
15
|
+
const dir = join(
|
|
16
|
+
tmpdir(),
|
|
17
|
+
`gsd-depth-gate-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
18
|
+
);
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test("register-hooks unlocks milestone depth verification from question id without guided-flow state (#4047)", async (t) => {
|
|
24
|
+
const dir = makeTempDir("manual");
|
|
25
|
+
const originalCwd = process.cwd();
|
|
26
|
+
process.chdir(dir);
|
|
27
|
+
resetWriteGateState();
|
|
28
|
+
|
|
29
|
+
t.after(() => {
|
|
30
|
+
resetWriteGateState();
|
|
31
|
+
process.chdir(originalCwd);
|
|
32
|
+
rmSync(dir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<void> | void>>();
|
|
36
|
+
const pi = {
|
|
37
|
+
on(event: string, handler: (event: any, ctx?: any) => Promise<void> | void) {
|
|
38
|
+
const existing = handlers.get(event) ?? [];
|
|
39
|
+
existing.push(handler);
|
|
40
|
+
handlers.set(event, existing);
|
|
41
|
+
},
|
|
42
|
+
} as any;
|
|
43
|
+
|
|
44
|
+
registerHooks(pi);
|
|
45
|
+
|
|
46
|
+
const questionId = "depth_verification_M001_confirm";
|
|
47
|
+
const questions = [
|
|
48
|
+
{
|
|
49
|
+
id: questionId,
|
|
50
|
+
question: "Do you agree?",
|
|
51
|
+
options: [
|
|
52
|
+
{ label: "Yes, you got it (Recommended)" },
|
|
53
|
+
{ label: "Needs adjustment" },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const toolCallHandlers = handlers.get("tool_call");
|
|
59
|
+
const toolResultHandlers = handlers.get("tool_result");
|
|
60
|
+
assert.ok(toolCallHandlers?.length, "tool_call handler should be registered");
|
|
61
|
+
assert.ok(toolResultHandlers?.length, "tool_result handler should be registered");
|
|
62
|
+
|
|
63
|
+
for (const handler of toolCallHandlers ?? []) {
|
|
64
|
+
await handler({
|
|
65
|
+
toolName: "ask_user_questions",
|
|
66
|
+
input: { questions },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
assert.equal(getPendingGate(), questionId, "gate should be set even without guided-flow state");
|
|
71
|
+
assert.equal(
|
|
72
|
+
shouldBlockContextArtifactSave("CONTEXT", "M001").block,
|
|
73
|
+
true,
|
|
74
|
+
"milestone context should still be blocked before confirmation",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
for (const handler of toolResultHandlers ?? []) {
|
|
78
|
+
await handler({
|
|
79
|
+
toolName: "ask_user_questions",
|
|
80
|
+
input: { questions },
|
|
81
|
+
details: {
|
|
82
|
+
response: {
|
|
83
|
+
answers: {
|
|
84
|
+
[questionId]: { selected: "Yes, you got it (Recommended)" },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
assert.equal(getPendingGate(), null, "confirming the depth question should clear the pending gate");
|
|
92
|
+
assert.equal(
|
|
93
|
+
shouldBlockContextArtifactSave("CONTEXT", "M001").block,
|
|
94
|
+
false,
|
|
95
|
+
"question-id milestone inference should unlock the matching milestone context write",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stale-slice-rows.test.ts — #3658
|
|
3
|
+
*
|
|
4
|
+
* Verify that state.ts contains slice-level status reconciliation that
|
|
5
|
+
* updates stale DB rows (status "pending") when disk artifacts (SUMMARY)
|
|
6
|
+
* prove the slice is complete. Without this, the dependency resolver builds
|
|
7
|
+
* doneSliceIds from stale DB rows and downstream slices stay blocked.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, test } from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const sourceFile = join(__dirname, "..", "state.ts");
|
|
18
|
+
|
|
19
|
+
describe("stale slice row reconciliation (#3658)", () => {
|
|
20
|
+
const source = readFileSync(sourceFile, "utf-8");
|
|
21
|
+
|
|
22
|
+
test("imports updateSliceStatus from gsd-db", () => {
|
|
23
|
+
assert.match(source, /import\s*\{[^}]*updateSliceStatus[^}]*\}\s*from/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("checks isStatusDone before reconciling slice rows", () => {
|
|
27
|
+
assert.match(source, /isStatusDone\(dbSlice\.status\)/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("resolves SUMMARY file to detect completed slices on disk", () => {
|
|
31
|
+
assert.match(source, /resolveSliceFile\(basePath,\s*mid,\s*dbSlice\.id,\s*["']SUMMARY["']\)/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("calls updateSliceStatus to reconcile stale rows", () => {
|
|
35
|
+
assert.match(source, /updateSliceStatus\(mid,\s*dbSlice\.id,\s*["']complete["']\)/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("references issue #3599 in reconciliation comment", () => {
|
|
39
|
+
assert.match(source, /#3599/);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for subagent model preference wiring.
|
|
3
|
+
*
|
|
4
|
+
* Fixes: subagent_model config in reactive_execution was validated and stored
|
|
5
|
+
* but never passed through to subagent dispatch instruction strings, so the
|
|
6
|
+
* executing agent autonomously chose "sonnet" instead of the configured model.
|
|
7
|
+
*
|
|
8
|
+
* Issue: gsd-build/gsd-2#4078
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { validatePreferences } from "../preferences-validation.ts";
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const promptsSrc = readFileSync(join(__dirname, "..", "auto-prompts.ts"), "utf-8");
|
|
21
|
+
const dispatchSrc = readFileSync(join(__dirname, "..", "auto-dispatch.ts"), "utf-8");
|
|
22
|
+
|
|
23
|
+
// ─── Preference Validation ────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
test("reactive_execution: subagent_model is preserved in validated preferences", () => {
|
|
26
|
+
const result = validatePreferences({
|
|
27
|
+
reactive_execution: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
max_parallel: 2,
|
|
30
|
+
isolation_mode: "same-tree",
|
|
31
|
+
subagent_model: "claude-opus-4-6",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
assert.equal(result.errors.length, 0);
|
|
35
|
+
assert.equal(
|
|
36
|
+
result.preferences.reactive_execution?.subagent_model,
|
|
37
|
+
"claude-opus-4-6",
|
|
38
|
+
"subagent_model should be preserved through validation",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("reactive_execution: subagent_model rejects empty string", () => {
|
|
43
|
+
const result = validatePreferences({
|
|
44
|
+
reactive_execution: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
max_parallel: 2,
|
|
47
|
+
isolation_mode: "same-tree",
|
|
48
|
+
subagent_model: "",
|
|
49
|
+
} as any,
|
|
50
|
+
});
|
|
51
|
+
assert.ok(
|
|
52
|
+
result.errors.some((e) => e.includes("subagent_model")),
|
|
53
|
+
"empty subagent_model should produce a validation error",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ─── Structural: Prompt Builders Accept subagentModel ────────────────────
|
|
58
|
+
|
|
59
|
+
test("buildReactiveExecutePrompt: accepts subagentModel parameter", () => {
|
|
60
|
+
const fnStart = promptsSrc.indexOf("export async function buildReactiveExecutePrompt");
|
|
61
|
+
assert.ok(fnStart !== -1, "buildReactiveExecutePrompt should be exported");
|
|
62
|
+
const signature = promptsSrc.slice(fnStart, fnStart + 300);
|
|
63
|
+
assert.ok(
|
|
64
|
+
signature.includes("subagentModel"),
|
|
65
|
+
"buildReactiveExecutePrompt should accept a subagentModel parameter",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("buildParallelResearchSlicesPrompt: accepts subagentModel parameter", () => {
|
|
70
|
+
const fnStart = promptsSrc.indexOf("export async function buildParallelResearchSlicesPrompt");
|
|
71
|
+
assert.ok(fnStart !== -1, "buildParallelResearchSlicesPrompt should be exported");
|
|
72
|
+
const signature = promptsSrc.slice(fnStart, fnStart + 300);
|
|
73
|
+
assert.ok(
|
|
74
|
+
signature.includes("subagentModel"),
|
|
75
|
+
"buildParallelResearchSlicesPrompt should accept a subagentModel parameter",
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("buildGateEvaluatePrompt: accepts subagentModel parameter", () => {
|
|
80
|
+
const fnStart = promptsSrc.indexOf("export async function buildGateEvaluatePrompt");
|
|
81
|
+
assert.ok(fnStart !== -1, "buildGateEvaluatePrompt should be exported");
|
|
82
|
+
const signature = promptsSrc.slice(fnStart, fnStart + 300);
|
|
83
|
+
assert.ok(
|
|
84
|
+
signature.includes("subagentModel"),
|
|
85
|
+
"buildGateEvaluatePrompt should accept a subagentModel parameter",
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ─── Structural: Instruction Strings Inject Model ────────────────────────
|
|
90
|
+
|
|
91
|
+
test("buildReactiveExecutePrompt: instruction string uses subagentModel when set", () => {
|
|
92
|
+
const fnStart = promptsSrc.indexOf("export async function buildReactiveExecutePrompt");
|
|
93
|
+
const fnEnd = promptsSrc.indexOf("\nexport async function", fnStart + 1);
|
|
94
|
+
const fnBody = promptsSrc.slice(fnStart, fnEnd);
|
|
95
|
+
assert.ok(
|
|
96
|
+
fnBody.includes("subagentModel"),
|
|
97
|
+
"buildReactiveExecutePrompt body should reference subagentModel",
|
|
98
|
+
);
|
|
99
|
+
// The instruction line must be dynamic (not a plain string literal)
|
|
100
|
+
assert.ok(
|
|
101
|
+
!fnBody.includes('"Use this as the prompt for a `subagent` call:"'),
|
|
102
|
+
"instruction should not be a plain static string — model must be injectable",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("buildParallelResearchSlicesPrompt: instruction string uses subagentModel when set", () => {
|
|
107
|
+
const fnStart = promptsSrc.indexOf("export async function buildParallelResearchSlicesPrompt");
|
|
108
|
+
const fnEnd = promptsSrc.indexOf("\nexport async function", fnStart + 1);
|
|
109
|
+
const fnBody = promptsSrc.slice(fnStart, fnEnd);
|
|
110
|
+
assert.ok(
|
|
111
|
+
fnBody.includes("subagentModel"),
|
|
112
|
+
"buildParallelResearchSlicesPrompt body should reference subagentModel",
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("buildGateEvaluatePrompt: instruction string uses subagentModel when set", () => {
|
|
117
|
+
const fnStart = promptsSrc.indexOf("export async function buildGateEvaluatePrompt");
|
|
118
|
+
const fnEnd = promptsSrc.indexOf("\nexport async function", fnStart + 1);
|
|
119
|
+
const fnBody = promptsSrc.slice(fnStart, fnEnd);
|
|
120
|
+
assert.ok(
|
|
121
|
+
fnBody.includes("subagentModel"),
|
|
122
|
+
"buildGateEvaluatePrompt body should reference subagentModel",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ─── Structural: Dispatch Wires Model to Prompt Builders ─────────────────
|
|
127
|
+
|
|
128
|
+
test("auto-dispatch: passes model to buildReactiveExecutePrompt", () => {
|
|
129
|
+
// Find the reactive-execute dispatch rule
|
|
130
|
+
const ruleStart = dispatchSrc.indexOf("reactive-execute (parallel dispatch)");
|
|
131
|
+
assert.ok(ruleStart !== -1, "reactive-execute dispatch rule should exist");
|
|
132
|
+
const ruleBlock = dispatchSrc.slice(ruleStart, ruleStart + 1000);
|
|
133
|
+
assert.ok(
|
|
134
|
+
ruleBlock.includes("subagent_model") || ruleBlock.includes("subagentModel"),
|
|
135
|
+
"reactive-execute rule should resolve and pass the subagent model",
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("auto-dispatch: passes model to buildParallelResearchSlicesPrompt", () => {
|
|
140
|
+
const callIdx = dispatchSrc.indexOf("buildParallelResearchSlicesPrompt(");
|
|
141
|
+
assert.ok(callIdx !== -1, "buildParallelResearchSlicesPrompt call should exist");
|
|
142
|
+
// The call site should pass a model argument (not just 4 args)
|
|
143
|
+
const callSite = dispatchSrc.slice(callIdx, callIdx + 300);
|
|
144
|
+
assert.ok(
|
|
145
|
+
callSite.includes("subagentModel") || callSite.includes("resolveModelWithFallbacksForUnit"),
|
|
146
|
+
"buildParallelResearchSlicesPrompt call should include model argument",
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("auto-dispatch: passes model to buildGateEvaluatePrompt", () => {
|
|
151
|
+
const callIdx = dispatchSrc.indexOf("buildGateEvaluatePrompt(");
|
|
152
|
+
assert.ok(callIdx !== -1, "buildGateEvaluatePrompt call should exist");
|
|
153
|
+
const callSite = dispatchSrc.slice(callIdx, callIdx + 300);
|
|
154
|
+
assert.ok(
|
|
155
|
+
callSite.includes("subagentModel") || callSite.includes("resolveModelWithFallbacksForUnit"),
|
|
156
|
+
"buildGateEvaluatePrompt call should include model argument",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ─── Integration: Prompt Output Contains Model String ────────────────────
|
|
161
|
+
|
|
162
|
+
test("buildReactiveExecutePrompt: output contains model string when subagentModel provided", async (t) => {
|
|
163
|
+
const { buildReactiveExecutePrompt } = await import("../auto-prompts.ts");
|
|
164
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-subagent-model-reactive-"));
|
|
165
|
+
t.after(() => rmSync(repo, { recursive: true, force: true }));
|
|
166
|
+
|
|
167
|
+
const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
|
|
168
|
+
mkdirSync(join(gsd, "tasks"), { recursive: true });
|
|
169
|
+
|
|
170
|
+
writeFileSync(
|
|
171
|
+
join(gsd, "S01-PLAN.md"),
|
|
172
|
+
[
|
|
173
|
+
"# S01: Test Slice",
|
|
174
|
+
"",
|
|
175
|
+
"**Goal:** Verify model injection",
|
|
176
|
+
"**Demo:** Model appears in subagent prompt",
|
|
177
|
+
"",
|
|
178
|
+
"## Tasks",
|
|
179
|
+
"",
|
|
180
|
+
"- [ ] **T01: Task One** `est:15m`",
|
|
181
|
+
" Do something.",
|
|
182
|
+
"",
|
|
183
|
+
].join("\n"),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
writeFileSync(
|
|
187
|
+
join(gsd, "tasks", "T01-PLAN.md"),
|
|
188
|
+
[
|
|
189
|
+
"# T01: Task One",
|
|
190
|
+
"",
|
|
191
|
+
"## Description",
|
|
192
|
+
"Do something.",
|
|
193
|
+
"",
|
|
194
|
+
"## Inputs",
|
|
195
|
+
"",
|
|
196
|
+
"- `src/config.json` — Config",
|
|
197
|
+
"",
|
|
198
|
+
"## Expected Output",
|
|
199
|
+
"",
|
|
200
|
+
"- `src/out.ts` — Result",
|
|
201
|
+
].join("\n"),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const prompt = await buildReactiveExecutePrompt(
|
|
205
|
+
"M001", "Test Milestone", "S01", "Test Slice",
|
|
206
|
+
["T01"], repo, "claude-opus-4-6",
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
assert.ok(
|
|
210
|
+
prompt.includes('model: "claude-opus-4-6"'),
|
|
211
|
+
`Prompt should contain model instruction. Got:\n${prompt.slice(0, 500)}`,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("buildReactiveExecutePrompt: no model instruction when subagentModel omitted", async (t) => {
|
|
216
|
+
const { buildReactiveExecutePrompt } = await import("../auto-prompts.ts");
|
|
217
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-subagent-model-none-"));
|
|
218
|
+
t.after(() => rmSync(repo, { recursive: true, force: true }));
|
|
219
|
+
|
|
220
|
+
const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
|
|
221
|
+
mkdirSync(join(gsd, "tasks"), { recursive: true });
|
|
222
|
+
|
|
223
|
+
writeFileSync(
|
|
224
|
+
join(gsd, "S01-PLAN.md"),
|
|
225
|
+
[
|
|
226
|
+
"# S01: Test Slice",
|
|
227
|
+
"",
|
|
228
|
+
"**Goal:** Verify no model when omitted",
|
|
229
|
+
"**Demo:** No model string",
|
|
230
|
+
"",
|
|
231
|
+
"## Tasks",
|
|
232
|
+
"",
|
|
233
|
+
"- [ ] **T01: Task One** `est:15m`",
|
|
234
|
+
" Do something.",
|
|
235
|
+
"",
|
|
236
|
+
].join("\n"),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
writeFileSync(
|
|
240
|
+
join(gsd, "tasks", "T01-PLAN.md"),
|
|
241
|
+
[
|
|
242
|
+
"# T01: Task One",
|
|
243
|
+
"",
|
|
244
|
+
"## Description",
|
|
245
|
+
"Do something.",
|
|
246
|
+
"",
|
|
247
|
+
"## Inputs",
|
|
248
|
+
"",
|
|
249
|
+
"- `src/config.json` — Config",
|
|
250
|
+
"",
|
|
251
|
+
"## Expected Output",
|
|
252
|
+
"",
|
|
253
|
+
"- `src/out.ts` — Result",
|
|
254
|
+
].join("\n"),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const prompt = await buildReactiveExecutePrompt(
|
|
258
|
+
"M001", "Test Milestone", "S01", "Test Slice",
|
|
259
|
+
["T01"], repo,
|
|
260
|
+
// no subagentModel
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
assert.ok(
|
|
264
|
+
!prompt.includes('with model:'),
|
|
265
|
+
"Prompt should not contain model instruction when subagentModel is omitted",
|
|
266
|
+
);
|
|
267
|
+
});
|
|
@@ -180,6 +180,14 @@ export function renderSummaryContent(
|
|
|
180
180
|
milestoneId: string,
|
|
181
181
|
evidence?: Array<{ command: string; exitCode?: number; exit_code?: number; verdict: string; durationMs?: number; duration_ms?: number }>,
|
|
182
182
|
): string {
|
|
183
|
+
// If the task already has a fully rendered summary (written by handleCompleteTask's
|
|
184
|
+
// renderSummaryMarkdown), use it as-is. That content already includes frontmatter,
|
|
185
|
+
// heading, and all sections. Re-wrapping it inside a second frontmatter/heading
|
|
186
|
+
// envelope produces double frontmatter and duplicate sections.
|
|
187
|
+
if (taskRow.full_summary_md && taskRow.full_summary_md.trimStart().startsWith("---")) {
|
|
188
|
+
return taskRow.full_summary_md;
|
|
189
|
+
}
|
|
190
|
+
|
|
183
191
|
// ── Frontmatter (YAML list format, matches parseSummary() expectations) ──
|
|
184
192
|
const keyFilesYaml = taskRow.key_files && taskRow.key_files.length > 0
|
|
185
193
|
? taskRow.key_files.map(f => ` - ${f}`).join("\n")
|
|
@@ -548,13 +548,39 @@ export function removeWorktree(
|
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
/**
|
|
552
|
-
|
|
553
|
-
|
|
551
|
+
/**
|
|
552
|
+
* Paths to skip in all worktree diffs (internal/runtime artifacts).
|
|
553
|
+
*
|
|
554
|
+
* NOTE: These arrays must stay synchronized with GSD_RUNTIME_PATTERNS in gitignore.ts.
|
|
555
|
+
* That file is the canonical source of truth for runtime ignore patterns.
|
|
556
|
+
* This module uses a split representation (paths/exact/prefixes) for efficient matching.
|
|
557
|
+
*/
|
|
558
|
+
const SKIP_PATHS = [
|
|
559
|
+
".gsd/worktrees/",
|
|
560
|
+
".gsd/runtime/",
|
|
561
|
+
".gsd/activity/",
|
|
562
|
+
".gsd/forensics/",
|
|
563
|
+
".gsd/parallel/",
|
|
564
|
+
".gsd/journal/",
|
|
565
|
+
];
|
|
566
|
+
const SKIP_EXACT = [
|
|
567
|
+
".gsd/STATE.md",
|
|
568
|
+
".gsd/auto.lock",
|
|
569
|
+
".gsd/metrics.json",
|
|
570
|
+
".gsd/state-manifest.json",
|
|
571
|
+
".gsd/doctor-history.jsonl",
|
|
572
|
+
".gsd/event-log.jsonl",
|
|
573
|
+
];
|
|
574
|
+
/** File prefixes to skip (for wildcard patterns like completed-units*.json, gsd.db*). */
|
|
575
|
+
const SKIP_PREFIXES = [
|
|
576
|
+
".gsd/completed-units",
|
|
577
|
+
".gsd/gsd.db",
|
|
578
|
+
];
|
|
554
579
|
|
|
555
580
|
function shouldSkipPath(filePath: string): boolean {
|
|
556
581
|
if (SKIP_PATHS.some(p => filePath.startsWith(p))) return true;
|
|
557
582
|
if (SKIP_EXACT.includes(filePath)) return true;
|
|
583
|
+
if (SKIP_PREFIXES.some(p => filePath.startsWith(p))) return true;
|
|
558
584
|
return false;
|
|
559
585
|
}
|
|
560
586
|
|