gsd-pi 2.42.0 → 2.43.0-dev.5717b75
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 +23 -0
- package/dist/cli.js +18 -3
- package/dist/loader.js +3 -1
- package/dist/resource-loader.js +39 -6
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
- package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
- package/dist/resources/extensions/async-jobs/index.js +2 -0
- package/dist/resources/extensions/gsd/auto/phases.js +1 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
- package/dist/resources/extensions/gsd/auto-start.js +8 -11
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
- package/dist/resources/extensions/gsd/detection.js +19 -0
- package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
- package/dist/resources/extensions/gsd/forensics.js +84 -0
- package/dist/resources/extensions/gsd/git-constants.js +1 -0
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +59 -8
- package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/dist/resources/extensions/gsd/repo-identity.js +46 -5
- package/dist/resources/extensions/gsd/service-tier.js +13 -4
- package/dist/resources/extensions/gsd/session-lock.js +2 -2
- package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
- package/dist/resources/extensions/gsd/worktree.js +2 -2
- package/dist/resources/extensions/mcp-client/index.js +2 -1
- package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +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 +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +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/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
- 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/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +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 +11 -11
- package/dist/web/standalone/.next/server/chunks/229.js +2 -2
- package/dist/web/standalone/.next/server/chunks/471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
- 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/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +40 -4
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +6 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +53 -0
- package/packages/pi-agent-core/src/agent.ts +3 -0
- package/packages/pi-agent-core/src/types.ts +6 -0
- package/packages/pi-agent-core/tsconfig.json +1 -1
- package/packages/pi-ai/dist/models.d.ts +5 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +1135 -1588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.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 +60 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +1543 -0
- package/packages/pi-ai/src/models.generated.ts +1140 -1593
- package/packages/pi-ai/src/models.ts +7 -4
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
- package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
- package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
- package/src/resources/extensions/async-jobs/index.ts +1 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
- package/src/resources/extensions/gsd/auto/phases.ts +1 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
- package/src/resources/extensions/gsd/auto-start.ts +7 -10
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
- package/src/resources/extensions/gsd/detection.ts +19 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
- package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -0
- package/src/resources/extensions/gsd/forensics.ts +92 -0
- package/src/resources/extensions/gsd/git-constants.ts +1 -0
- package/src/resources/extensions/gsd/git-service.ts +0 -1
- package/src/resources/extensions/gsd/gitignore.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +62 -6
- package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/src/resources/extensions/gsd/repo-identity.ts +48 -5
- package/src/resources/extensions/gsd/service-tier.ts +17 -4
- package/src/resources/extensions/gsd/session-lock.ts +2 -2
- package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
- package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
- package/src/resources/extensions/gsd/worktree-resolver.ts +2 -3
- package/src/resources/extensions/gsd/worktree.ts +2 -2
- package/src/resources/extensions/mcp-client/index.ts +5 -1
- package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
- /package/dist/web/standalone/.next/static/{QSuuaY2tP8h_zzhHcVxNj → 5ULZcR9XhHFzlAZFiSKRl}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{QSuuaY2tP8h_zzhHcVxNj → 5ULZcR9XhHFzlAZFiSKRl}/_ssgManifest.js +0 -0
|
@@ -30,6 +30,9 @@ import { loadPrompt } from "./prompt-loader.js";
|
|
|
30
30
|
import { gsdRoot } from "./paths.js";
|
|
31
31
|
import { formatDuration } from "../shared/format-utils.js";
|
|
32
32
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
33
|
+
import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
|
|
34
|
+
import { showNextAction } from "../shared/tui.js";
|
|
35
|
+
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
|
33
36
|
|
|
34
37
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
35
38
|
|
|
@@ -67,6 +70,71 @@ interface ForensicReport {
|
|
|
67
70
|
recentUnits: { type: string; id: string; cost: number; duration: number; model: string; finishedAt: number }[];
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
// ─── Duplicate Detection ──────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
const DEDUP_PROMPT_SECTION = `
|
|
76
|
+
## Duplicate Detection (REQUIRED before issue creation)
|
|
77
|
+
|
|
78
|
+
Before offering to create a GitHub issue, you MUST search for existing issues and PRs that may already address this bug. This step uses the user's AI tokens for analysis.
|
|
79
|
+
|
|
80
|
+
### Search Steps
|
|
81
|
+
|
|
82
|
+
1. **Search closed issues** for similar keywords from your diagnosis:
|
|
83
|
+
\`\`\`
|
|
84
|
+
gh issue list --repo gsd-build/gsd-2 --state closed --search "<keywords from root cause>" --limit 20
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
2. **Search open PRs** that might contain the fix:
|
|
88
|
+
\`\`\`
|
|
89
|
+
gh pr list --repo gsd-build/gsd-2 --state open --search "<keywords>" --limit 10
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
3. **Search merged PRs** that may have already fixed this:
|
|
93
|
+
\`\`\`
|
|
94
|
+
gh pr list --repo gsd-build/gsd-2 --state merged --search "<keywords>" --limit 10
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
### Analysis
|
|
98
|
+
|
|
99
|
+
For each result, compare it against your root-cause diagnosis:
|
|
100
|
+
- Does the issue describe the same code path or file?
|
|
101
|
+
- Does the PR modify the same file:line you identified?
|
|
102
|
+
- Is the symptom description semantically similar even if keywords differ?
|
|
103
|
+
|
|
104
|
+
### Present Findings
|
|
105
|
+
|
|
106
|
+
If you find potential matches, present them to the user:
|
|
107
|
+
|
|
108
|
+
1. **"Already fixed by PR #X — skip issue creation"** — when a merged PR or closed issue clearly addresses the same root cause. Explain why you believe it matches.
|
|
109
|
+
2. **"Add my findings to existing issue #Y"** — when an open issue exists for the same bug. Use \`gh issue comment #Y --repo gsd-build/gsd-2\` to add forensic evidence.
|
|
110
|
+
3. **"Create new issue anyway"** — when existing results do not cover this specific failure.
|
|
111
|
+
|
|
112
|
+
Only proceed to issue creation if no matches were found OR the user explicitly chooses "Create new issue anyway".
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
async function writeForensicsDedupPref(ctx: ExtensionCommandContext, enabled: boolean): Promise<void> {
|
|
116
|
+
const prefsPath = getGlobalGSDPreferencesPath();
|
|
117
|
+
await ensurePreferencesFile(prefsPath, ctx, "global");
|
|
118
|
+
const existing = loadGlobalGSDPreferences();
|
|
119
|
+
const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : {};
|
|
120
|
+
prefs.version = prefs.version || 1;
|
|
121
|
+
prefs.forensics_dedup = enabled;
|
|
122
|
+
|
|
123
|
+
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
124
|
+
const raw = existsSync(prefsPath) ? readFileSync(prefsPath, "utf-8") : "";
|
|
125
|
+
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
|
126
|
+
const start = raw.startsWith("---\n") ? 4 : raw.startsWith("---\r\n") ? 5 : -1;
|
|
127
|
+
if (start !== -1) {
|
|
128
|
+
const closingIdx = raw.indexOf("\n---", start);
|
|
129
|
+
if (closingIdx !== -1) {
|
|
130
|
+
const after = raw.slice(closingIdx + 4);
|
|
131
|
+
if (after.trim()) body = after;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
writeFileSync(prefsPath, `---\n${frontmatter}---${body}`, "utf-8");
|
|
136
|
+
}
|
|
137
|
+
|
|
70
138
|
// ─── Entry Point ──────────────────────────────────────────────────────────────
|
|
71
139
|
|
|
72
140
|
export async function handleForensics(
|
|
@@ -98,6 +166,29 @@ export async function handleForensics(
|
|
|
98
166
|
return;
|
|
99
167
|
}
|
|
100
168
|
|
|
169
|
+
// ─── Duplicate detection opt-in ─────────────────────────────────────────────
|
|
170
|
+
const effectivePrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
171
|
+
let dedupEnabled = effectivePrefs?.forensics_dedup === true;
|
|
172
|
+
|
|
173
|
+
if (effectivePrefs?.forensics_dedup === undefined) {
|
|
174
|
+
const choice = await showNextAction(ctx, {
|
|
175
|
+
title: "Duplicate detection available",
|
|
176
|
+
summary: ["Before filing a GitHub issue, forensics can search existing issues and PRs to avoid duplicates.", "This uses additional AI tokens for analysis."],
|
|
177
|
+
actions: [
|
|
178
|
+
{ id: "enable", label: "Enable duplicate detection", description: "Search issues/PRs before filing (recommended)", recommended: true },
|
|
179
|
+
{ id: "skip", label: "Skip for now", description: "File without checking for duplicates" },
|
|
180
|
+
],
|
|
181
|
+
notYetMessage: "You can enable this later via preferences (forensics_dedup: true).",
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (choice === "enable") {
|
|
185
|
+
await writeForensicsDedupPref(ctx, true);
|
|
186
|
+
dedupEnabled = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const dedupSection = dedupEnabled ? DEDUP_PROMPT_SECTION : "";
|
|
191
|
+
|
|
101
192
|
ctx.ui.notify("Building forensic report...", "info");
|
|
102
193
|
|
|
103
194
|
const report = await buildForensicReport(basePath);
|
|
@@ -117,6 +208,7 @@ export async function handleForensics(
|
|
|
117
208
|
problemDescription,
|
|
118
209
|
forensicData,
|
|
119
210
|
gsdSourceDir,
|
|
211
|
+
dedupSection,
|
|
120
212
|
});
|
|
121
213
|
|
|
122
214
|
ctx.ui.notify(`Forensic report saved: ${relative(basePath, savedPath)}`, "info");
|
|
@@ -245,7 +245,6 @@ export function writeIntegrationBranch(
|
|
|
245
245
|
basePath: string,
|
|
246
246
|
milestoneId: string,
|
|
247
247
|
branch: string,
|
|
248
|
-
_options?: { commitDocs?: boolean },
|
|
249
248
|
): void {
|
|
250
249
|
// Don't record slice branches as the integration target
|
|
251
250
|
if (SLICE_BRANCH_RE.test(branch)) return;
|
|
@@ -137,7 +137,7 @@ export function hasGitTrackedGsdFiles(basePath: string): boolean {
|
|
|
137
137
|
*/
|
|
138
138
|
export function ensureGitignore(
|
|
139
139
|
basePath: string,
|
|
140
|
-
options?: { manageGitignore?: boolean
|
|
140
|
+
options?: { manageGitignore?: boolean },
|
|
141
141
|
): boolean {
|
|
142
142
|
// If manage_gitignore is explicitly false, do not touch .gitignore at all
|
|
143
143
|
if (options?.manageGitignore === false) return false;
|
|
@@ -847,6 +847,7 @@ export function nativeMergeSquash(basePath: string, branch: string): GitMergeRes
|
|
|
847
847
|
cwd: basePath,
|
|
848
848
|
stdio: ["ignore", "pipe", "pipe"],
|
|
849
849
|
encoding: "utf-8",
|
|
850
|
+
env: GIT_NO_PROMPT_ENV,
|
|
850
851
|
});
|
|
851
852
|
return { success: true, conflicts: [] };
|
|
852
853
|
} catch (err: unknown) {
|
|
@@ -89,6 +89,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
89
89
|
"reactive_execution",
|
|
90
90
|
"github",
|
|
91
91
|
"service_tier",
|
|
92
|
+
"forensics_dedup",
|
|
92
93
|
]);
|
|
93
94
|
|
|
94
95
|
/** Canonical list of all dispatch unit types. */
|
|
@@ -223,6 +224,8 @@ export interface GSDPreferences {
|
|
|
223
224
|
github?: GitHubSyncConfig;
|
|
224
225
|
/** OpenAI service tier preference. "priority" = 2x cost, faster. "flex" = 0.5x cost, slower. Only affects gpt-5.4 models. */
|
|
225
226
|
service_tier?: "priority" | "flex";
|
|
227
|
+
/** Opt-in: search existing issues and PRs before filing from /gsd forensics. Uses additional AI tokens. */
|
|
228
|
+
forensics_dedup?: boolean;
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
export interface LoadedGSDPreferences {
|
|
@@ -200,12 +200,22 @@ function loadPreferencesFile(path: string, scope: "global" | "project"): LoadedG
|
|
|
200
200
|
export function parsePreferencesMarkdown(content: string): GSDPreferences | null {
|
|
201
201
|
// Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
|
|
202
202
|
const startMarker = content.startsWith('---\r\n') ? '---\r\n' : '---\n';
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
if (content.startsWith(startMarker)) {
|
|
204
|
+
const searchStart = startMarker.length;
|
|
205
|
+
const endIdx = content.indexOf('\n---', searchStart);
|
|
206
|
+
if (endIdx === -1) return null;
|
|
207
|
+
const block = content.slice(searchStart, endIdx);
|
|
208
|
+
return parseFrontmatterBlock(block.replace(/\r/g, ''));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Fallback: heading+list format (e.g. "## Git\n- isolation: none") (#2036)
|
|
212
|
+
// GSD agents may write preferences files without frontmatter delimiters.
|
|
213
|
+
if (/^##\s+\w/m.test(content)) {
|
|
214
|
+
return parseHeadingListFormat(content);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
|
|
218
|
+
return null;
|
|
209
219
|
}
|
|
210
220
|
|
|
211
221
|
function parseFrontmatterBlock(frontmatter: string): GSDPreferences {
|
|
@@ -221,6 +231,51 @@ function parseFrontmatterBlock(frontmatter: string): GSDPreferences {
|
|
|
221
231
|
}
|
|
222
232
|
}
|
|
223
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Parse heading+list format into a nested object, then cast to GSDPreferences.
|
|
236
|
+
* Handles markdown like:
|
|
237
|
+
* ## Git
|
|
238
|
+
* - isolation: none
|
|
239
|
+
* - commit_docs: true
|
|
240
|
+
* ## Models
|
|
241
|
+
* - planner: sonnet
|
|
242
|
+
*/
|
|
243
|
+
function parseHeadingListFormat(content: string): GSDPreferences {
|
|
244
|
+
const result: Record<string, Record<string, string>> = {};
|
|
245
|
+
let currentSection: string | null = null;
|
|
246
|
+
|
|
247
|
+
for (const rawLine of content.split('\n')) {
|
|
248
|
+
const line = rawLine.replace(/\r$/, '');
|
|
249
|
+
const headingMatch = line.match(/^##\s+(.+)$/);
|
|
250
|
+
if (headingMatch) {
|
|
251
|
+
currentSection = headingMatch[1].trim().toLowerCase().replace(/\s+/g, '_');
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (currentSection) {
|
|
255
|
+
const itemMatch = line.match(/^-\s+([^:]+):\s*(.*)$/);
|
|
256
|
+
if (itemMatch) {
|
|
257
|
+
if (!result[currentSection]) result[currentSection] = {};
|
|
258
|
+
const value = itemMatch[2].trim();
|
|
259
|
+
// Coerce "true"/"false" strings and numbers
|
|
260
|
+
result[currentSection][itemMatch[1].trim()] = value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Convert string values to appropriate types via YAML parser for each section
|
|
266
|
+
const typed: Record<string, unknown> = {};
|
|
267
|
+
for (const [section, entries] of Object.entries(result)) {
|
|
268
|
+
const yamlLines = Object.entries(entries).map(([k, v]) => `${k}: ${v}`).join('\n');
|
|
269
|
+
try {
|
|
270
|
+
typed[section] = parseYaml(yamlLines);
|
|
271
|
+
} catch {
|
|
272
|
+
typed[section] = entries;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return typed as GSDPreferences;
|
|
277
|
+
}
|
|
278
|
+
|
|
224
279
|
// ─── Merging ────────────────────────────────────────────────────────────────
|
|
225
280
|
|
|
226
281
|
/**
|
|
@@ -286,6 +341,7 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
286
341
|
? { ...(base.github ?? {}), ...(override.github ?? {}) } as import("../github-sync/types.js").GitHubSyncConfig
|
|
287
342
|
: undefined,
|
|
288
343
|
service_tier: override.service_tier ?? base.service_tier,
|
|
344
|
+
forensics_dedup: override.forensics_dedup ?? base.forensics_dedup,
|
|
289
345
|
};
|
|
290
346
|
}
|
|
291
347
|
|
|
@@ -101,11 +101,19 @@ Explain your findings:
|
|
|
101
101
|
- **Code snippet** — the problematic code and what it should do instead
|
|
102
102
|
- **Recovery** — what the user can do right now to get unstuck
|
|
103
103
|
|
|
104
|
+
{{dedupSection}}
|
|
105
|
+
|
|
104
106
|
Then **offer GitHub issue creation**: "Would you like me to create a GitHub issue for this on gsd-build/gsd-2?"
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
**CRITICAL: The `github_issues` tool ONLY targets the current user's repository — it has no `repo` parameter. You MUST use `gh issue create --repo gsd-build/gsd-2` via the `bash` tool to file on the correct repo. Do NOT use the `github_issues` tool for this.**
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
If yes, create using the `bash` tool:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
gh issue create --repo gsd-build/gsd-2 \
|
|
114
|
+
--title "..." \
|
|
115
|
+
--label "bug" --label "auto-generated" \
|
|
116
|
+
--body "$(cat <<'EOF'
|
|
109
117
|
## Problem
|
|
110
118
|
[1-2 sentence summary]
|
|
111
119
|
|
|
@@ -128,11 +136,10 @@ If yes, create using `gh issue create` with this format:
|
|
|
128
136
|
|
|
129
137
|
---
|
|
130
138
|
*Auto-generated by `/gsd forensics`*
|
|
139
|
+
EOF
|
|
140
|
+
)"
|
|
131
141
|
```
|
|
132
142
|
|
|
133
|
-
**Repository:** gsd-build/gsd-2
|
|
134
|
-
**Labels:** bug, auto-generated
|
|
135
|
-
|
|
136
143
|
### Redaction Rules (CRITICAL)
|
|
137
144
|
|
|
138
145
|
Before creating the issue, you MUST:
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
10
|
import { execFileSync } from "node:child_process";
|
|
11
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { basename, dirname, join, resolve } from "node:path";
|
|
14
14
|
|
|
@@ -271,15 +271,54 @@ export function externalProjectsRoot(): string {
|
|
|
271
271
|
return join(base, "projects");
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
// ─── Numbered Variant Cleanup ────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* macOS collision pattern: `.gsd 2`, `.gsd 3`, `.gsd 4`, etc.
|
|
278
|
+
*
|
|
279
|
+
* When `symlinkSync` (or Finder) tries to create `.gsd` but a real directory
|
|
280
|
+
* already exists at that path, macOS APFS silently renames the new entry to
|
|
281
|
+
* `.gsd 2`, then `.gsd 3`, and so on. These numbered variants confuse GSD
|
|
282
|
+
* because the canonical `.gsd` path no longer resolves to the external state
|
|
283
|
+
* directory, making tracked planning files appear deleted.
|
|
284
|
+
*
|
|
285
|
+
* This helper scans the project root for entries matching `.gsd <digits>` and
|
|
286
|
+
* removes them. It is called early in `ensureGsdSymlink()` so that the
|
|
287
|
+
* canonical `.gsd` path is always the one in use.
|
|
288
|
+
*/
|
|
289
|
+
const GSD_NUMBERED_VARIANT_RE = /^\.gsd \d+$/;
|
|
290
|
+
|
|
291
|
+
export function cleanNumberedGsdVariants(projectPath: string): string[] {
|
|
292
|
+
const removed: string[] = [];
|
|
293
|
+
try {
|
|
294
|
+
const entries = readdirSync(projectPath);
|
|
295
|
+
for (const entry of entries) {
|
|
296
|
+
if (GSD_NUMBERED_VARIANT_RE.test(entry)) {
|
|
297
|
+
const fullPath = join(projectPath, entry);
|
|
298
|
+
try {
|
|
299
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
300
|
+
removed.push(entry);
|
|
301
|
+
} catch {
|
|
302
|
+
// Best-effort: if removal fails (e.g. permissions), continue with next
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
// Non-fatal: readdir failure should not block symlink creation
|
|
308
|
+
}
|
|
309
|
+
return removed;
|
|
310
|
+
}
|
|
311
|
+
|
|
274
312
|
// ─── Symlink Management ─────────────────────────────────────────────────────
|
|
275
313
|
|
|
276
314
|
/**
|
|
277
315
|
* Ensure the `<project>/.gsd` symlink points to the external state directory.
|
|
278
316
|
*
|
|
279
|
-
* 1.
|
|
280
|
-
* 2.
|
|
281
|
-
* 3. If `<project>/.gsd`
|
|
282
|
-
* 4. If `<project>/.gsd` is
|
|
317
|
+
* 1. Clean up any macOS numbered collision variants (`.gsd 2`, `.gsd 3`, etc.)
|
|
318
|
+
* 2. mkdir -p the external dir
|
|
319
|
+
* 3. If `<project>/.gsd` doesn't exist → create symlink
|
|
320
|
+
* 4. If `<project>/.gsd` is already the correct symlink → no-op
|
|
321
|
+
* 5. If `<project>/.gsd` is a real directory → return as-is (migration handles later)
|
|
283
322
|
*
|
|
284
323
|
* Returns the resolved external path.
|
|
285
324
|
*/
|
|
@@ -297,6 +336,10 @@ export function ensureGsdSymlink(projectPath: string): string {
|
|
|
297
336
|
return localGsd;
|
|
298
337
|
}
|
|
299
338
|
|
|
339
|
+
// Clean up macOS numbered collision variants (.gsd 2, .gsd 3, etc.) before
|
|
340
|
+
// any existence checks — otherwise they accumulate and confuse state (#2205).
|
|
341
|
+
cleanNumberedGsdVariants(projectPath);
|
|
342
|
+
|
|
300
343
|
// Ensure external directory exists
|
|
301
344
|
mkdirSync(externalPath, { recursive: true });
|
|
302
345
|
|
|
@@ -23,6 +23,8 @@ import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./comm
|
|
|
23
23
|
|
|
24
24
|
export type ServiceTierSetting = "priority" | "flex" | undefined;
|
|
25
25
|
|
|
26
|
+
const SERVICE_TIER_SCOPE_NOTE = "Only affects gpt-5.4 models, regardless of provider.";
|
|
27
|
+
|
|
26
28
|
// ─── Gating ──────────────────────────────────────────────────────────────────
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -51,7 +53,7 @@ export function formatServiceTierStatus(tier: ServiceTierSetting): string {
|
|
|
51
53
|
" /gsd fast flex Set to flex (0.5x cost, slower)",
|
|
52
54
|
" /gsd fast off Disable service tier",
|
|
53
55
|
"",
|
|
54
|
-
|
|
56
|
+
SERVICE_TIER_SCOPE_NOTE,
|
|
55
57
|
].join("\n");
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -64,10 +66,18 @@ export function formatServiceTierStatus(tier: ServiceTierSetting): string {
|
|
|
64
66
|
" /gsd fast flex Set to flex (0.5x cost, slower)",
|
|
65
67
|
" /gsd fast off Disable service tier",
|
|
66
68
|
"",
|
|
67
|
-
|
|
69
|
+
SERVICE_TIER_SCOPE_NOTE,
|
|
68
70
|
].join("\n");
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
export function formatServiceTierFooterStatus(
|
|
74
|
+
tier: ServiceTierSetting,
|
|
75
|
+
modelId: string | undefined,
|
|
76
|
+
): string | undefined {
|
|
77
|
+
if (!tier || !modelId || !supportsServiceTier(modelId)) return undefined;
|
|
78
|
+
return tier === "priority" ? "fast: ⚡ priority" : "fast: 💰 flex";
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
// ─── Icon Resolution ─────────────────────────────────────────────────────────
|
|
72
82
|
|
|
73
83
|
/**
|
|
@@ -148,19 +158,22 @@ export async function handleFast(args: string, ctx: ExtensionCommandContext): Pr
|
|
|
148
158
|
|
|
149
159
|
if (trimmed === "on") {
|
|
150
160
|
await writeGlobalServiceTier(ctx, "priority");
|
|
151
|
-
ctx.ui.
|
|
161
|
+
ctx.ui.setStatus("gsd-fast", formatServiceTierFooterStatus("priority", ctx.model?.id));
|
|
162
|
+
ctx.ui.notify("Service tier set to priority (2x cost, faster responses). Only affects gpt-5.4 models, regardless of provider.", "info");
|
|
152
163
|
return;
|
|
153
164
|
}
|
|
154
165
|
|
|
155
166
|
if (trimmed === "off") {
|
|
156
167
|
await writeGlobalServiceTier(ctx, undefined);
|
|
168
|
+
ctx.ui.setStatus("gsd-fast", undefined);
|
|
157
169
|
ctx.ui.notify("Service tier disabled.", "info");
|
|
158
170
|
return;
|
|
159
171
|
}
|
|
160
172
|
|
|
161
173
|
if (trimmed === "flex") {
|
|
162
174
|
await writeGlobalServiceTier(ctx, "flex");
|
|
163
|
-
ctx.ui.
|
|
175
|
+
ctx.ui.setStatus("gsd-fast", formatServiceTierFooterStatus("flex", ctx.model?.id));
|
|
176
|
+
ctx.ui.notify("Service tier set to flex (0.5x cost, slower responses). Only affects gpt-5.4 models, regardless of provider.", "info");
|
|
164
177
|
return;
|
|
165
178
|
}
|
|
166
179
|
|
|
@@ -239,7 +239,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
239
239
|
const elapsed = Date.now() - _lockAcquiredAt;
|
|
240
240
|
if (elapsed < 1_800_000) {
|
|
241
241
|
process.stderr.write(
|
|
242
|
-
`[gsd] Lock heartbeat
|
|
242
|
+
`[gsd] Lock heartbeat caught up after ${Math.round(elapsed / 1000)}s — long LLM call, no action needed.\n`,
|
|
243
243
|
);
|
|
244
244
|
return; // Suppress false positive
|
|
245
245
|
}
|
|
@@ -299,7 +299,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
299
299
|
const elapsed = Date.now() - _lockAcquiredAt;
|
|
300
300
|
if (elapsed < 1_800_000) {
|
|
301
301
|
process.stderr.write(
|
|
302
|
-
`[gsd] Lock heartbeat
|
|
302
|
+
`[gsd] Lock heartbeat caught up after ${Math.round(elapsed / 1000)}s — long LLM call, no action needed.\n`,
|
|
303
303
|
);
|
|
304
304
|
return;
|
|
305
305
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - activity-log-save.test.ts (caching, dedup, collision recovery)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import test from "node:test";
|
|
7
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
9
|
import { existsSync, mkdtempSync, mkdirSync, readdirSync, realpathSync, rmSync, utimesSync, writeFileSync, readFileSync } from "node:fs";
|
|
10
10
|
import { join, dirname } from "node:path";
|
|
@@ -48,9 +48,12 @@ function createCtx(entries: unknown[]) {
|
|
|
48
48
|
|
|
49
49
|
// ── Pruning ──────────────────────────────────────────────────────────────────
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
describe("pruneActivityLogs", () => {
|
|
52
|
+
let dir: string;
|
|
53
|
+
beforeEach(() => { dir = createTmpDir(); });
|
|
54
|
+
afterEach(() => { rmSync(dir, { recursive: true, force: true }); });
|
|
55
|
+
|
|
56
|
+
test("deletes old files, keeps recent and highest-seq", () => {
|
|
54
57
|
const f001 = writeActivityFile(dir, "001", "execute-task-M001-S01-T01");
|
|
55
58
|
writeActivityFile(dir, "002", "execute-task-M001-S01-T02");
|
|
56
59
|
writeActivityFile(dir, "003", "execute-task-M001-S01-T03");
|
|
@@ -61,14 +64,9 @@ test("pruneActivityLogs deletes old files, keeps recent and highest-seq", () =>
|
|
|
61
64
|
assert.ok(!remaining.includes("001-execute-task-M001-S01-T01.jsonl"));
|
|
62
65
|
assert.ok(remaining.includes("002-execute-task-M001-S01-T02.jsonl"));
|
|
63
66
|
assert.ok(remaining.includes("003-execute-task-M001-S01-T03.jsonl"));
|
|
64
|
-
}
|
|
65
|
-
rmSync(dir, { recursive: true, force: true });
|
|
66
|
-
}
|
|
67
|
-
});
|
|
67
|
+
});
|
|
68
68
|
|
|
69
|
-
test("
|
|
70
|
-
const dir = createTmpDir();
|
|
71
|
-
try {
|
|
69
|
+
test("preserves highest-seq even when all files are old", () => {
|
|
72
70
|
const f001 = writeActivityFile(dir, "001", "t1");
|
|
73
71
|
const f002 = writeActivityFile(dir, "002", "t2");
|
|
74
72
|
const f003 = writeActivityFile(dir, "003", "t3");
|
|
@@ -78,14 +76,9 @@ test("pruneActivityLogs preserves highest-seq even when all files are old", () =
|
|
|
78
76
|
const remaining = listFiles(dir);
|
|
79
77
|
assert.equal(remaining.length, 1);
|
|
80
78
|
assert.ok(remaining[0].startsWith("003-"));
|
|
81
|
-
}
|
|
82
|
-
rmSync(dir, { recursive: true, force: true });
|
|
83
|
-
}
|
|
84
|
-
});
|
|
79
|
+
});
|
|
85
80
|
|
|
86
|
-
test("
|
|
87
|
-
const dir = createTmpDir();
|
|
88
|
-
try {
|
|
81
|
+
test("with retentionDays=0 keeps only highest-seq", () => {
|
|
89
82
|
writeActivityFile(dir, "001", "t1");
|
|
90
83
|
writeActivityFile(dir, "002", "t2");
|
|
91
84
|
writeActivityFile(dir, "003", "t3");
|
|
@@ -94,51 +87,31 @@ test("pruneActivityLogs with retentionDays=0 keeps only highest-seq", () => {
|
|
|
94
87
|
const remaining = listFiles(dir);
|
|
95
88
|
assert.equal(remaining.length, 1);
|
|
96
89
|
assert.ok(remaining[0].startsWith("003-"));
|
|
97
|
-
}
|
|
98
|
-
rmSync(dir, { recursive: true, force: true });
|
|
99
|
-
}
|
|
100
|
-
});
|
|
90
|
+
});
|
|
101
91
|
|
|
102
|
-
test("
|
|
103
|
-
const dir = createTmpDir();
|
|
104
|
-
try {
|
|
92
|
+
test("no-op when all files are recent", () => {
|
|
105
93
|
writeActivityFile(dir, "001", "t1");
|
|
106
94
|
writeActivityFile(dir, "002", "t2");
|
|
107
95
|
writeActivityFile(dir, "003", "t3");
|
|
108
96
|
|
|
109
97
|
pruneActivityLogs(dir, 30);
|
|
110
98
|
assert.equal(listFiles(dir).length, 3);
|
|
111
|
-
}
|
|
112
|
-
rmSync(dir, { recursive: true, force: true });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
99
|
+
});
|
|
115
100
|
|
|
116
|
-
test("
|
|
117
|
-
const dir = createTmpDir();
|
|
118
|
-
try {
|
|
101
|
+
test("handles empty directory", () => {
|
|
119
102
|
assert.doesNotThrow(() => pruneActivityLogs(dir, 30));
|
|
120
103
|
assert.equal(readdirSync(dir).length, 0);
|
|
121
|
-
}
|
|
122
|
-
rmSync(dir, { recursive: true, force: true });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
104
|
+
});
|
|
125
105
|
|
|
126
|
-
test("
|
|
127
|
-
const dir = createTmpDir();
|
|
128
|
-
try {
|
|
106
|
+
test("preserves single old file (it is highest-seq)", () => {
|
|
129
107
|
const f = writeActivityFile(dir, "001", "t1");
|
|
130
108
|
backdateFile(f, 100);
|
|
131
109
|
|
|
132
110
|
pruneActivityLogs(dir, 30);
|
|
133
111
|
assert.equal(listFiles(dir).length, 1);
|
|
134
|
-
}
|
|
135
|
-
rmSync(dir, { recursive: true, force: true });
|
|
136
|
-
}
|
|
137
|
-
});
|
|
112
|
+
});
|
|
138
113
|
|
|
139
|
-
test("
|
|
140
|
-
const dir = createTmpDir();
|
|
141
|
-
try {
|
|
114
|
+
test("ignores non-matching filenames", () => {
|
|
142
115
|
const f001 = writeActivityFile(dir, "001", "t1");
|
|
143
116
|
writeFileSync(join(dir, "notes.txt"), "some notes\n", "utf-8");
|
|
144
117
|
backdateFile(f001, 40);
|
|
@@ -148,16 +121,17 @@ test("pruneActivityLogs ignores non-matching filenames", () => {
|
|
|
148
121
|
assert.ok(remaining.includes("notes.txt"));
|
|
149
122
|
// 001 is the only seq file, so it's highest-seq and survives
|
|
150
123
|
assert.ok(remaining.includes("001-t1.jsonl"));
|
|
151
|
-
}
|
|
152
|
-
rmSync(dir, { recursive: true, force: true });
|
|
153
|
-
}
|
|
124
|
+
});
|
|
154
125
|
});
|
|
155
126
|
|
|
156
127
|
// ── Save: caching, dedup, collision recovery ─────────────────────────────────
|
|
157
128
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
129
|
+
describe("saveActivityLog", () => {
|
|
130
|
+
let baseDir: string;
|
|
131
|
+
beforeEach(() => { baseDir = createTmpDir(); });
|
|
132
|
+
afterEach(() => { rmSync(baseDir, { recursive: true, force: true }); });
|
|
133
|
+
|
|
134
|
+
test("caches sequence instead of rescanning", () => {
|
|
161
135
|
saveActivityLog(createCtx([{ kind: "first", n: 1 }]) as any, baseDir, "execute-task", "M001/S01/T01");
|
|
162
136
|
writeFileSync(join(activityDir(baseDir), "999-external.jsonl"), '{"x":1}\n', "utf-8");
|
|
163
137
|
saveActivityLog(createCtx([{ kind: "second", n: 2 }]) as any, baseDir, "execute-task", "M001/S01/T02");
|
|
@@ -166,14 +140,9 @@ test("saveActivityLog caches sequence instead of rescanning", () => {
|
|
|
166
140
|
assert.ok(files.includes("001-execute-task-M001-S01-T01.jsonl"));
|
|
167
141
|
assert.ok(files.includes("002-execute-task-M001-S01-T02.jsonl"));
|
|
168
142
|
assert.ok(!files.some(f => f.startsWith("1000-")));
|
|
169
|
-
}
|
|
170
|
-
rmSync(baseDir, { recursive: true, force: true });
|
|
171
|
-
}
|
|
172
|
-
});
|
|
143
|
+
});
|
|
173
144
|
|
|
174
|
-
test("
|
|
175
|
-
const baseDir = createTmpDir();
|
|
176
|
-
try {
|
|
145
|
+
test("deduplicates identical snapshots for same unit", () => {
|
|
177
146
|
const ctx = createCtx([{ role: "assistant", content: "same" }]);
|
|
178
147
|
saveActivityLog(ctx as any, baseDir, "plan-slice", "M002/S01");
|
|
179
148
|
saveActivityLog(ctx as any, baseDir, "plan-slice", "M002/S01");
|
|
@@ -184,14 +153,9 @@ test("saveActivityLog deduplicates identical snapshots for same unit", () => {
|
|
|
184
153
|
saveActivityLog(createCtx([{ role: "assistant", content: "changed" }]) as any, baseDir, "plan-slice", "M002/S01");
|
|
185
154
|
files = listFiles(activityDir(baseDir));
|
|
186
155
|
assert.equal(files.length, 2);
|
|
187
|
-
}
|
|
188
|
-
rmSync(baseDir, { recursive: true, force: true });
|
|
189
|
-
}
|
|
190
|
-
});
|
|
156
|
+
});
|
|
191
157
|
|
|
192
|
-
test("
|
|
193
|
-
const baseDir = createTmpDir();
|
|
194
|
-
try {
|
|
158
|
+
test("recovers on sequence collision", () => {
|
|
195
159
|
saveActivityLog(createCtx([{ turn: 1 }]) as any, baseDir, "execute-task", "M003/S02/T01");
|
|
196
160
|
writeFileSync(join(activityDir(baseDir), "002-execute-task-M003-S02-T02.jsonl"), '{"collision":true}\n', "utf-8");
|
|
197
161
|
saveActivityLog(createCtx([{ turn: 2 }]) as any, baseDir, "execute-task", "M003/S02/T02");
|
|
@@ -199,9 +163,7 @@ test("saveActivityLog recovers on sequence collision", () => {
|
|
|
199
163
|
const files = listFiles(activityDir(baseDir));
|
|
200
164
|
assert.ok(files.includes("002-execute-task-M003-S02-T02.jsonl"));
|
|
201
165
|
assert.ok(files.includes("003-execute-task-M003-S02-T02.jsonl"));
|
|
202
|
-
}
|
|
203
|
-
rmSync(baseDir, { recursive: true, force: true });
|
|
204
|
-
}
|
|
166
|
+
});
|
|
205
167
|
});
|
|
206
168
|
|
|
207
169
|
// ── Prompt text assertion ────────────────────────────────────────────────────
|