@opengsd/gsd-pi 1.0.2-dev.867e002 → 1.0.2-dev.cce3612
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/onboarding.js +22 -3
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/context7/index.js +12 -2
- package/dist/resources/extensions/get-secrets-from-user.js +16 -16
- package/dist/resources/extensions/google-cli/index.js +30 -0
- package/dist/resources/extensions/google-cli/models.js +55 -0
- package/dist/resources/extensions/google-cli/package.json +11 -0
- package/dist/resources/extensions/google-cli/readiness.js +12 -0
- package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
- package/dist/resources/extensions/gsd/auto/loop.js +62 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +37 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +8 -0
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +17 -7
- package/dist/resources/extensions/gsd/auto-post-unit.js +18 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +5 -236
- package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
- package/dist/resources/extensions/gsd/auto-start.js +232 -49
- package/dist/resources/extensions/gsd/auto.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +39 -5
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -7
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -27
- package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
- package/dist/resources/extensions/gsd/commands-usage.js +105 -1
- package/dist/resources/extensions/gsd/config-overlay.js +20 -14
- package/dist/resources/extensions/gsd/context-overlay.js +22 -16
- package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +87 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
- package/dist/resources/extensions/gsd/doctor.js +6 -1
- package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
- package/dist/resources/extensions/gsd/guided-flow.js +5 -6
- package/dist/resources/extensions/gsd/key-manager.js +45 -13
- package/dist/resources/extensions/gsd/milestone-reopen-events.js +28 -0
- package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
- package/dist/resources/extensions/gsd/preferences-skills.js +11 -4
- package/dist/resources/extensions/gsd/preferences.js +14 -2
- package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +1 -3
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
- package/dist/resources/extensions/gsd/repository-registry.js +3 -1
- package/dist/resources/extensions/gsd/skill-activation.js +233 -0
- package/dist/resources/extensions/gsd/skill-catalog.data.js +820 -0
- package/dist/resources/extensions/gsd/skill-catalog.install.js +179 -0
- package/dist/resources/extensions/gsd/skill-catalog.js +5 -1028
- package/dist/resources/extensions/gsd/skill-discovery.js +121 -79
- package/dist/resources/extensions/gsd/skill-scope.js +52 -0
- package/dist/resources/extensions/gsd/skill-telemetry.js +6 -39
- package/dist/resources/extensions/gsd/skills.js +60 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +351 -0
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +41 -0
- package/dist/resources/extensions/gsd/state-reconciliation/registry.js +4 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +63 -2
- package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +35 -26
- package/dist/resources/extensions/gsd/user-input-boundary.js +1 -1
- package/dist/resources/extensions/gsd/vision-ask.js +22 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
- package/dist/resources/extensions/search-the-web/native-search.js +57 -8
- package/dist/resources/extensions/shared/confirm-ui.js +9 -6
- package/dist/resources/extensions/shared/dialog-frame.js +42 -0
- package/dist/resources/extensions/shared/interview-ui.js +42 -30
- package/dist/resources/extensions/shared/next-action-ui.js +6 -6
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts +1 -0
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +22 -8
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
- package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/skills.js +6 -0
- package/packages/pi-agent-core/dist/harness/skills.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/system-prompt.d.ts +7 -0
- package/packages/pi-agent-core/dist/harness/system-prompt.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/system-prompt.js +7 -0
- package/packages/pi-agent-core/dist/harness/system-prompt.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +8 -59
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +21 -72
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +50 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses-shared.js +28 -4
- package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/README.md +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +8 -2
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +3 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/context7/index.ts +15 -2
- package/src/resources/extensions/get-secrets-from-user.ts +17 -16
- package/src/resources/extensions/google-cli/index.ts +34 -0
- package/src/resources/extensions/google-cli/models.ts +57 -0
- package/src/resources/extensions/google-cli/package.json +11 -0
- package/src/resources/extensions/google-cli/readiness.ts +15 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
- package/src/resources/extensions/gsd/auto/loop.ts +74 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -2
- package/src/resources/extensions/gsd/auto/phases.ts +46 -0
- package/src/resources/extensions/gsd/auto/run-unit.ts +10 -0
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +31 -11
- package/src/resources/extensions/gsd/auto-post-unit.ts +37 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +4 -284
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -7
- package/src/resources/extensions/gsd/auto-start.ts +307 -56
- package/src/resources/extensions/gsd/auto.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +42 -5
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +18 -6
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -28
- package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
- package/src/resources/extensions/gsd/commands-usage.ts +110 -5
- package/src/resources/extensions/gsd/config-overlay.ts +19 -16
- package/src/resources/extensions/gsd/context-overlay.ts +24 -19
- package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +99 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/doctor.ts +6 -1
- package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
- package/src/resources/extensions/gsd/guided-flow.ts +5 -6
- package/src/resources/extensions/gsd/key-manager.ts +57 -14
- package/src/resources/extensions/gsd/milestone-reopen-events.ts +28 -0
- package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
- package/src/resources/extensions/gsd/preferences-skills.ts +11 -4
- package/src/resources/extensions/gsd/preferences.ts +17 -2
- package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +1 -3
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
- package/src/resources/extensions/gsd/repository-registry.ts +3 -1
- package/src/resources/extensions/gsd/skill-activation.ts +292 -0
- package/src/resources/extensions/gsd/skill-catalog.data.ts +858 -0
- package/src/resources/extensions/gsd/skill-catalog.install.ts +205 -0
- package/src/resources/extensions/gsd/skill-catalog.ts +16 -1087
- package/src/resources/extensions/gsd/skill-discovery.ts +134 -78
- package/src/resources/extensions/gsd/skill-scope.ts +63 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +6 -40
- package/src/resources/extensions/gsd/skills.ts +75 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +499 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +40 -0
- package/src/resources/extensions/gsd/state-reconciliation/registry.ts +8 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +30 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +328 -2
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +28 -2
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
- package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/discord-invite-links.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +116 -11
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +30 -1
- package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/skill-scope-auto.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/skills.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
- package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +82 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +82 -5
- package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +37 -26
- package/src/resources/extensions/gsd/user-input-boundary.ts +1 -1
- package/src/resources/extensions/gsd/vision-ask.ts +28 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
- package/src/resources/extensions/search-the-web/native-search.ts +60 -8
- package/src/resources/extensions/shared/confirm-ui.ts +8 -12
- package/src/resources/extensions/shared/dialog-frame.ts +71 -0
- package/src/resources/extensions/shared/interview-ui.ts +43 -42
- package/src/resources/extensions/shared/next-action-ui.ts +6 -6
- package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
- package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
- /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → orfEoZqDIo6Be_Z9ZFipD}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → orfEoZqDIo6Be_Z9ZFipD}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { mock } from "node:test";
|
|
7
|
+
|
|
8
|
+
import { postUnitPostVerification, type PostUnitContext } from "../auto-post-unit.ts";
|
|
9
|
+
import { AutoSession } from "../auto/session.ts";
|
|
10
|
+
import { checkPostUnitHooks, resetHookState, resolveHookArtifactPath } from "../post-unit-hooks.ts";
|
|
11
|
+
import { _clearGsdRootCache } from "../paths.ts";
|
|
12
|
+
import { invalidateAllCaches } from "../cache.ts";
|
|
13
|
+
|
|
14
|
+
function writePreferences(basePath: string): void {
|
|
15
|
+
const content = `---
|
|
16
|
+
post_unit_hooks:
|
|
17
|
+
- name: review-arbiter
|
|
18
|
+
after:
|
|
19
|
+
- execute-task
|
|
20
|
+
prompt: Review {taskId}
|
|
21
|
+
agent: arbiter
|
|
22
|
+
artifact: REVIEW-DEBATE.md
|
|
23
|
+
retry_on: NEEDS-REWORK.md
|
|
24
|
+
max_cycles: 3
|
|
25
|
+
enabled: true
|
|
26
|
+
---
|
|
27
|
+
`;
|
|
28
|
+
writeFileSync(join(basePath, ".gsd", "PREFERENCES.md"), content, "utf-8");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("post-unit retry_on marks trigger unit as retry in orchestrator before redispatch", async () => {
|
|
32
|
+
const originalCwd = process.cwd();
|
|
33
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-post-unit-retry-"));
|
|
34
|
+
const taskDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
35
|
+
mkdirSync(taskDir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
process.chdir(base);
|
|
39
|
+
_clearGsdRootCache();
|
|
40
|
+
invalidateAllCaches();
|
|
41
|
+
resetHookState();
|
|
42
|
+
writePreferences(base);
|
|
43
|
+
|
|
44
|
+
const hookDispatch = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
|
|
45
|
+
assert.ok(hookDispatch, "hook should dispatch for execute-task");
|
|
46
|
+
|
|
47
|
+
const retryPath = resolveHookArtifactPath(base, "M001/S01/T01", "NEEDS-REWORK.md");
|
|
48
|
+
writeFileSync(retryPath, "rework requested", "utf-8");
|
|
49
|
+
|
|
50
|
+
const retryActiveUnit = mock.fn(async (_unit: { unitType: string; unitId: string }) => {});
|
|
51
|
+
const s = new AutoSession();
|
|
52
|
+
s.basePath = base;
|
|
53
|
+
s.active = true;
|
|
54
|
+
s.currentUnit = { type: "hook/review-arbiter", id: "M001/S01/T01", startedAt: Date.now() };
|
|
55
|
+
s.orchestration = {
|
|
56
|
+
start: async () => ({ kind: "started" }),
|
|
57
|
+
advance: async () => ({ kind: "stopped", reason: "unused" }),
|
|
58
|
+
completeActiveUnit: async () => {},
|
|
59
|
+
retryActiveUnit,
|
|
60
|
+
resume: async () => ({ kind: "resumed" }),
|
|
61
|
+
stop: async (reason: string) => ({ kind: "stopped", reason }),
|
|
62
|
+
getStatus: () => ({ phase: "running", transitionCount: 0 }),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const pctx: PostUnitContext = {
|
|
66
|
+
s,
|
|
67
|
+
ctx: {
|
|
68
|
+
ui: { notify: () => {}, setStatus: () => {}, setWidget: () => {}, setFooter: () => {} },
|
|
69
|
+
model: { id: "test-model" },
|
|
70
|
+
} as any,
|
|
71
|
+
pi: { sendMessage: async () => {}, setModel: async () => true } as any,
|
|
72
|
+
buildSnapshotOpts: () => ({}),
|
|
73
|
+
lockBase: () => base,
|
|
74
|
+
stopAuto: async () => {},
|
|
75
|
+
pauseAuto: async () => {},
|
|
76
|
+
updateProgressWidget: () => {},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const result = await postUnitPostVerification(pctx);
|
|
80
|
+
assert.equal(result, "continue");
|
|
81
|
+
assert.equal(retryActiveUnit.mock.callCount(), 1);
|
|
82
|
+
assert.deepEqual(retryActiveUnit.mock.calls[0]?.arguments[0], {
|
|
83
|
+
unitType: "execute-task",
|
|
84
|
+
unitId: "M001/S01/T01",
|
|
85
|
+
});
|
|
86
|
+
} finally {
|
|
87
|
+
process.chdir(originalCwd);
|
|
88
|
+
resetHookState();
|
|
89
|
+
invalidateAllCaches();
|
|
90
|
+
_clearGsdRootCache();
|
|
91
|
+
rmSync(base, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
@@ -2,6 +2,7 @@ import { describe, test } from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
4
|
import { showQueueReorder } from "../queue-reorder-ui.ts";
|
|
5
|
+
import { assertFullOuterBorder } from "./tui-border-assertions.ts";
|
|
5
6
|
|
|
6
7
|
const fakeTheme = {
|
|
7
8
|
fg: (_color: string, text: string) => text,
|
|
@@ -43,6 +44,51 @@ describe("queue-reorder-ui", () => {
|
|
|
43
44
|
const joined = lastRender.join("\n");
|
|
44
45
|
assert.ok(joined.includes("M016"), "selected item should stay visible after scrolling");
|
|
45
46
|
assert.ok(lastRender.length <= 16, `overlay should fit terminal max-height, got ${lastRender.length}`);
|
|
47
|
+
assertFullOuterBorder(lastRender, 100);
|
|
48
|
+
assert.match(lastRender[0] ?? "", /^╭─ Queue Reorder /);
|
|
49
|
+
assert.match(lastRender.at(-1) ?? "", /^╰─+╯$/);
|
|
50
|
+
} finally {
|
|
51
|
+
if (originalRowsDescriptor) {
|
|
52
|
+
Object.defineProperty(process.stdout, "rows", originalRowsDescriptor);
|
|
53
|
+
} else {
|
|
54
|
+
delete (process.stdout as { rows?: number }).rows;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("draws queue scroll thumb beside queue rows when completed rows are shown", async () => {
|
|
60
|
+
const originalRowsDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "rows");
|
|
61
|
+
Object.defineProperty(process.stdout, "rows", { value: 12, configurable: true });
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const completed = [
|
|
65
|
+
{ id: "M000", title: "Already done" },
|
|
66
|
+
];
|
|
67
|
+
const pending = Array.from({ length: 8 }, (_, idx) => ({
|
|
68
|
+
id: `M${String(idx + 1).padStart(3, "0")}`,
|
|
69
|
+
title: `Milestone ${idx + 1}`,
|
|
70
|
+
}));
|
|
71
|
+
let rendered: string[] = [];
|
|
72
|
+
|
|
73
|
+
const ctx = {
|
|
74
|
+
hasUI: true,
|
|
75
|
+
ui: {
|
|
76
|
+
custom: async (factory: any) => {
|
|
77
|
+
const component = factory({ requestRender() {} }, fakeTheme, null, () => {});
|
|
78
|
+
rendered = component.render(80);
|
|
79
|
+
return null;
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
} as any;
|
|
83
|
+
|
|
84
|
+
await showQueueReorder(ctx, completed, pending);
|
|
85
|
+
|
|
86
|
+
const completedHeader = rendered.find(line => line.includes("Completed:"));
|
|
87
|
+
const firstQueueRow = rendered.find(line => line.includes("M001"));
|
|
88
|
+
assert.ok(completedHeader, "completed header should be rendered before queue rows");
|
|
89
|
+
assert.ok(firstQueueRow, "first queue row should be rendered");
|
|
90
|
+
assert.match(completedHeader, /│$/);
|
|
91
|
+
assert.match(firstQueueRow, /┃$/);
|
|
46
92
|
} finally {
|
|
47
93
|
if (originalRowsDescriptor) {
|
|
48
94
|
Object.defineProperty(process.stdout, "rows", originalRowsDescriptor);
|
|
@@ -134,6 +134,72 @@ test("handleRecoverableExtensionProcessError swallows EPIPE without writing a cr
|
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
test("handleRecoverableExtensionProcessError tolerates stderr.write throwing EPIPE (re-entry guard)", () => {
|
|
138
|
+
// process.stderr.write itself can EPIPE; safeStderr() must swallow it so the
|
|
139
|
+
// handler doesn't re-enter the EPIPE branch and loop forever.
|
|
140
|
+
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
141
|
+
process.stderr.write = (() => {
|
|
142
|
+
throw Object.assign(new Error("stderr broken pipe"), {
|
|
143
|
+
code: "EPIPE",
|
|
144
|
+
syscall: "write",
|
|
145
|
+
});
|
|
146
|
+
}) as typeof process.stderr.write;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const handled = handleRecoverableExtensionProcessError(
|
|
150
|
+
Object.assign(new Error("broken pipe"), {
|
|
151
|
+
code: "EPIPE",
|
|
152
|
+
syscall: "write",
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
assert.equal(handled, true);
|
|
156
|
+
} finally {
|
|
157
|
+
process.stderr.write = originalWrite;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// #181: Windows surfaces a closed pipe mid-write as `Error: write EOF` (or
|
|
162
|
+
// `read EOF`) with no `code` set. Both are the same logical condition as POSIX
|
|
163
|
+
// EPIPE and must be swallowed, else they escape to the uncaught-exception path
|
|
164
|
+
// and crash auto-mode workers. These non-storm cases run before the storm test
|
|
165
|
+
// below so the shared storm counter stays under threshold. ECONNRESET is
|
|
166
|
+
// intentionally NOT in this set — see the dedicated "leaves ECONNRESET network
|
|
167
|
+
// errors unhandled" test above.
|
|
168
|
+
test("handleRecoverableExtensionProcessError swallows Windows 'write EOF' (no code)", () => {
|
|
169
|
+
let stderr = "";
|
|
170
|
+
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
171
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
172
|
+
stderr += String(chunk);
|
|
173
|
+
return true;
|
|
174
|
+
}) as typeof process.stderr.write;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// Mirror the real Windows error: message "write EOF", no `code`, no `syscall`.
|
|
178
|
+
const handled = handleRecoverableExtensionProcessError(new Error("write EOF"));
|
|
179
|
+
assert.equal(handled, true);
|
|
180
|
+
assert.match(stderr, /swallowed write EOF/);
|
|
181
|
+
} finally {
|
|
182
|
+
process.stderr.write = originalWrite;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("handleRecoverableExtensionProcessError swallows 'read EOF' (no code)", () => {
|
|
187
|
+
let stderr = "";
|
|
188
|
+
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
189
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
190
|
+
stderr += String(chunk);
|
|
191
|
+
return true;
|
|
192
|
+
}) as typeof process.stderr.write;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const handled = handleRecoverableExtensionProcessError(new Error("read EOF"));
|
|
196
|
+
assert.equal(handled, true);
|
|
197
|
+
assert.match(stderr, /swallowed read EOF/);
|
|
198
|
+
} finally {
|
|
199
|
+
process.stderr.write = originalWrite;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
137
203
|
test("handleRecoverableExtensionProcessError swallows dead transport control write errors", () => {
|
|
138
204
|
let stderr = "";
|
|
139
205
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
@@ -153,7 +219,16 @@ test("handleRecoverableExtensionProcessError swallows dead transport control wri
|
|
|
153
219
|
}
|
|
154
220
|
});
|
|
155
221
|
|
|
156
|
-
test("handleRecoverableExtensionProcessError
|
|
222
|
+
test("handleRecoverableExtensionProcessError leaves a plain EOF-substring error unhandled", () => {
|
|
223
|
+
// Guard against over-matching: only the exact "write EOF"/"read EOF" messages
|
|
224
|
+
// are the Windows pipe-closed signature; an unrelated error must not be eaten.
|
|
225
|
+
const handled = handleRecoverableExtensionProcessError(new Error("could not write EOF marker to log"));
|
|
226
|
+
assert.equal(handled, false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("handleRecoverableExtensionProcessError exits on EPIPE storm (>100 within 10s)", () => {
|
|
230
|
+
// After the storm threshold, the pipe is gone for good — handler must exit
|
|
231
|
+
// cleanly instead of swallowing forever in a tight CPU loop.
|
|
157
232
|
let stderr = "";
|
|
158
233
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
159
234
|
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
@@ -161,18 +236,38 @@ test("handleRecoverableExtensionProcessError swallows write EOF without code", (
|
|
|
161
236
|
return true;
|
|
162
237
|
}) as typeof process.stderr.write;
|
|
163
238
|
|
|
239
|
+
const originalExit = process.exit;
|
|
240
|
+
const exitCalls: Array<number | undefined> = [];
|
|
241
|
+
process.exit = ((code?: number) => {
|
|
242
|
+
exitCalls.push(code);
|
|
243
|
+
// Don't actually exit; just record the call.
|
|
244
|
+
return undefined as never;
|
|
245
|
+
}) as typeof process.exit;
|
|
246
|
+
|
|
164
247
|
try {
|
|
165
|
-
const
|
|
166
|
-
|
|
248
|
+
const err = Object.assign(new Error("broken pipe"), {
|
|
249
|
+
code: "EPIPE",
|
|
250
|
+
syscall: "write",
|
|
251
|
+
});
|
|
252
|
+
// Fire well above the 100-event threshold inside the 10s window.
|
|
253
|
+
for (let i = 0; i < 150; i++) {
|
|
254
|
+
handleRecoverableExtensionProcessError(err);
|
|
255
|
+
}
|
|
256
|
+
assert.ok(
|
|
257
|
+
exitCalls.length > 0,
|
|
258
|
+
`expected process.exit to be called during EPIPE storm, got ${exitCalls.length} calls`,
|
|
167
259
|
);
|
|
168
|
-
assert.equal(
|
|
169
|
-
assert.match(stderr, /
|
|
260
|
+
assert.equal(exitCalls[0], 0);
|
|
261
|
+
assert.match(stderr, /EPIPE storm/);
|
|
170
262
|
} finally {
|
|
171
263
|
process.stderr.write = originalWrite;
|
|
264
|
+
process.exit = originalExit;
|
|
172
265
|
}
|
|
173
266
|
});
|
|
174
267
|
|
|
175
|
-
test("handleRecoverableExtensionProcessError
|
|
268
|
+
test("handleRecoverableExtensionProcessError exits on a Windows 'write EOF' storm too (#181)", () => {
|
|
269
|
+
// The storm counter is shared across all pipe-closed encodings, so a runaway
|
|
270
|
+
// Windows `write EOF` loop must trip the same clean-exit guard as EPIPE.
|
|
176
271
|
let stderr = "";
|
|
177
272
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
178
273
|
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
@@ -180,13 +275,23 @@ test("handleRecoverableExtensionProcessError swallows read EOF without code", ()
|
|
|
180
275
|
return true;
|
|
181
276
|
}) as typeof process.stderr.write;
|
|
182
277
|
|
|
278
|
+
const originalExit = process.exit;
|
|
279
|
+
const exitCalls: Array<number | undefined> = [];
|
|
280
|
+
process.exit = ((code?: number) => {
|
|
281
|
+
exitCalls.push(code);
|
|
282
|
+
return undefined as never;
|
|
283
|
+
}) as typeof process.exit;
|
|
284
|
+
|
|
183
285
|
try {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
assert.
|
|
286
|
+
const err = new Error("write EOF");
|
|
287
|
+
for (let i = 0; i < 150; i++) {
|
|
288
|
+
handleRecoverableExtensionProcessError(err);
|
|
289
|
+
}
|
|
290
|
+
assert.ok(exitCalls.length > 0, "expected process.exit during write EOF storm");
|
|
291
|
+
assert.equal(exitCalls[0], 0);
|
|
292
|
+
assert.match(stderr, /write EOF storm/);
|
|
189
293
|
} finally {
|
|
190
294
|
process.stderr.write = originalWrite;
|
|
295
|
+
process.exit = originalExit;
|
|
191
296
|
}
|
|
192
297
|
});
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
|
-
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
|
|
5
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
8
9
|
import { createRepositoryRegistryFromPreferences, defaultRepositoryTargets } from "../repository-registry.ts";
|
|
9
10
|
|
|
10
11
|
test("repository registry includes implicit project root and declared child repos", (t) => {
|
|
@@ -99,3 +100,31 @@ test("defaultRepositoryTargets returns [project] for a parent-mode registry", (t
|
|
|
99
100
|
|
|
100
101
|
assert.deepEqual(defaultRepositoryTargets(registry), ["project"]);
|
|
101
102
|
});
|
|
103
|
+
|
|
104
|
+
test("repository registry keeps project root anchored to .gsd project in monorepo subdirectory", (t) => {
|
|
105
|
+
const monorepo = mkdtempSync(join(tmpdir(), "gsd-repo-registry-mono-"));
|
|
106
|
+
t.after(() => rmSync(monorepo, { recursive: true, force: true }));
|
|
107
|
+
execFileSync("git", ["init"], { cwd: monorepo, stdio: "ignore" });
|
|
108
|
+
|
|
109
|
+
const subproject = join(monorepo, "fieldkit-tools");
|
|
110
|
+
mkdirSync(join(subproject, ".gsd"), { recursive: true });
|
|
111
|
+
writeFileSync(join(subproject, ".gsd", "PREFERENCES.md"), "---\nversion: 1\n---\n");
|
|
112
|
+
|
|
113
|
+
const registry = createRepositoryRegistryFromPreferences(subproject, undefined);
|
|
114
|
+
|
|
115
|
+
assert.equal(registry.projectRoot, subproject);
|
|
116
|
+
assert.equal(registry.byId.get("project")?.root, subproject);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("repository registry uses external-state worktree checkout as project root", (t) => {
|
|
120
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-repo-registry-external-"));
|
|
121
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
122
|
+
const worktree = join(base, ".gsd", "projects", "abc123", "worktrees", "M001");
|
|
123
|
+
mkdirSync(worktree, { recursive: true });
|
|
124
|
+
execFileSync("git", ["init"], { cwd: worktree, stdio: "ignore" });
|
|
125
|
+
|
|
126
|
+
const registry = createRepositoryRegistryFromPreferences(worktree, undefined);
|
|
127
|
+
|
|
128
|
+
assert.equal(registry.projectRoot, worktree);
|
|
129
|
+
assert.equal(registry.byId.get("project")?.root, worktree);
|
|
130
|
+
});
|
|
@@ -7,6 +7,7 @@ import assert from "node:assert/strict";
|
|
|
7
7
|
|
|
8
8
|
import { GSDConfigOverlay, formatConfigText } from "../config-overlay.ts";
|
|
9
9
|
import { handleCoreCommand } from "../commands/handlers/core.ts";
|
|
10
|
+
import { assertFullOuterBorder } from "./tui-border-assertions.ts";
|
|
10
11
|
|
|
11
12
|
const theme = {
|
|
12
13
|
bold: (s: string) => s,
|
|
@@ -24,6 +25,9 @@ test("GSDConfigOverlay renders and responds to input", () => {
|
|
|
24
25
|
|
|
25
26
|
const lines = overlay.render(60);
|
|
26
27
|
assert.ok(lines.some((line) => line.includes("GSD Configuration")));
|
|
28
|
+
assertFullOuterBorder(lines, 60);
|
|
29
|
+
assert.match(lines[0] ?? "", /^╭─ GSD Configuration /);
|
|
30
|
+
assert.match(lines.at(-1) ?? "", /^╰─+╯$/);
|
|
27
31
|
|
|
28
32
|
overlay.handleInput("j");
|
|
29
33
|
assert.equal(renderRequests, 1);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
appendDiscoveredSkillsFallback,
|
|
8
|
+
clearSkillSnapshot,
|
|
9
|
+
detectNewSkills,
|
|
10
|
+
refreshCatalogForNewSkills,
|
|
11
|
+
snapshotSkills,
|
|
12
|
+
} from "../skill-discovery.js";
|
|
13
|
+
|
|
14
|
+
function makeTempHome(): string {
|
|
15
|
+
return mkdtempSync(join(tmpdir(), "gsd-skill-discovery-"));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function withTempSkillHome<T>(fn: (home: string) => T | Promise<T>): Promise<T> {
|
|
19
|
+
const previousHome = process.env.HOME;
|
|
20
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
21
|
+
const home = makeTempHome();
|
|
22
|
+
process.env.HOME = home;
|
|
23
|
+
process.env.GSD_HOME = join(home, ".gsd");
|
|
24
|
+
try {
|
|
25
|
+
return await fn(home);
|
|
26
|
+
} finally {
|
|
27
|
+
clearSkillSnapshot();
|
|
28
|
+
if (previousHome === undefined) {
|
|
29
|
+
delete process.env.HOME;
|
|
30
|
+
} else {
|
|
31
|
+
process.env.HOME = previousHome;
|
|
32
|
+
}
|
|
33
|
+
if (previousGsdHome === undefined) {
|
|
34
|
+
delete process.env.GSD_HOME;
|
|
35
|
+
} else {
|
|
36
|
+
process.env.GSD_HOME = previousGsdHome;
|
|
37
|
+
}
|
|
38
|
+
rmSync(home, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeDiskSkill(root: string, name: string, description = `Use for ${name}.`): void {
|
|
43
|
+
const dir = join(root, name);
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
writeFileSync(join(dir, "SKILL.md"), `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test("detectNewSkills detects skills added on disk after the baseline snapshot", async () => {
|
|
49
|
+
await withTempSkillHome((home) => {
|
|
50
|
+
const skillsRoot = join(home, ".agents", "skills");
|
|
51
|
+
writeDiskSkill(skillsRoot, "existing-skill");
|
|
52
|
+
snapshotSkills();
|
|
53
|
+
|
|
54
|
+
writeDiskSkill(skillsRoot, "new-disk-skill", "New disk skill.");
|
|
55
|
+
const detected = detectNewSkills();
|
|
56
|
+
|
|
57
|
+
assert.deepEqual(detected.map(skill => skill.name), ["new-disk-skill"]);
|
|
58
|
+
assert.equal(detected[0].description, "New disk skill.");
|
|
59
|
+
assert.equal(detected[0].location, join(skillsRoot, "new-disk-skill", "SKILL.md"));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("refreshCatalogForNewSkills retries discovery after reload failure", async () => {
|
|
64
|
+
await withTempSkillHome(async (home) => {
|
|
65
|
+
const skillsRoot = join(home, ".agents", "skills");
|
|
66
|
+
snapshotSkills();
|
|
67
|
+
writeDiskSkill(skillsRoot, "reload-retry-skill");
|
|
68
|
+
|
|
69
|
+
const messages: Array<{ message: string; level: "info" | "warning" }> = [];
|
|
70
|
+
const failed = await refreshCatalogForNewSkills({
|
|
71
|
+
reload: async () => { throw new Error("reload failed"); },
|
|
72
|
+
notify: (message, level) => messages.push({ message, level }),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(failed.map(skill => skill.name), ["reload-retry-skill"]);
|
|
76
|
+
assert.deepEqual(detectNewSkills().map(skill => skill.name), ["reload-retry-skill"]);
|
|
77
|
+
assert.equal(messages[0]?.level, "warning");
|
|
78
|
+
|
|
79
|
+
const loaded = await refreshCatalogForNewSkills({
|
|
80
|
+
reload: async () => {},
|
|
81
|
+
notify: (message, level) => messages.push({ message, level }),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
assert.deepEqual(loaded.map(skill => skill.name), ["reload-retry-skill"]);
|
|
85
|
+
assert.deepEqual(detectNewSkills(), []);
|
|
86
|
+
assert.ok(messages.some(({ level, message }) => level === "info" && message.includes("reload-retry-skill")));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("appendDiscoveredSkillsFallback exposes newly detected skills missing from the prompt", () => {
|
|
91
|
+
const prompt = appendDiscoveredSkillsFallback("base system prompt", [{
|
|
92
|
+
name: "fallback-skill",
|
|
93
|
+
description: "Use when reload fails & skill is needed.",
|
|
94
|
+
location: "/tmp/fallback-skill/SKILL.md",
|
|
95
|
+
}]);
|
|
96
|
+
|
|
97
|
+
assert.match(prompt, /<newly_discovered_skills>/);
|
|
98
|
+
assert.match(prompt, /fallback-skill/);
|
|
99
|
+
assert.match(prompt, /Use when reload fails & skill is needed\./);
|
|
100
|
+
assert.match(prompt, /\/tmp\/fallback-skill\/SKILL.md/);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("appendDiscoveredSkillsFallback does not duplicate skills already in the prompt", () => {
|
|
104
|
+
const prompt = "base system prompt\n/tmp/already-loaded/SKILL.md";
|
|
105
|
+
|
|
106
|
+
assert.equal(appendDiscoveredSkillsFallback(prompt, [{
|
|
107
|
+
name: "already-loaded",
|
|
108
|
+
description: "Already present.",
|
|
109
|
+
location: "/tmp/already-loaded/SKILL.md",
|
|
110
|
+
}]), prompt);
|
|
111
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
applyUnitSkillVisibility,
|
|
6
|
+
effectiveSkillNamesForUnit,
|
|
7
|
+
resolveVisibleSkillNames,
|
|
8
|
+
unitHasSkillManifest,
|
|
9
|
+
} from "../skill-scope.ts";
|
|
10
|
+
|
|
11
|
+
test("resolveVisibleSkillNames: none policy suppresses catalog", () => {
|
|
12
|
+
assert.deepEqual(resolveVisibleSkillNames("workflow-preferences"), []);
|
|
13
|
+
assert.equal(unitHasSkillManifest("workflow-preferences"), true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("resolveVisibleSkillNames: wildcard units use full catalog", () => {
|
|
17
|
+
assert.equal(resolveVisibleSkillNames("execute-task"), undefined);
|
|
18
|
+
assert.equal(unitHasSkillManifest("execute-task"), false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("unitHasSkillManifest: manifest unit types return true", () => {
|
|
22
|
+
assert.equal(unitHasSkillManifest("research-milestone"), true);
|
|
23
|
+
assert.equal(unitHasSkillManifest("plan-slice"), true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("unitHasSkillManifest: wildcard unit types return false", () => {
|
|
27
|
+
assert.equal(unitHasSkillManifest("execute-task"), false);
|
|
28
|
+
assert.equal(unitHasSkillManifest(undefined), false);
|
|
29
|
+
assert.equal(unitHasSkillManifest("unknown-unit"), false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("applyUnitSkillVisibility: sets manifest names for scoped units", () => {
|
|
33
|
+
let visible: string[] | undefined;
|
|
34
|
+
applyUnitSkillVisibility({
|
|
35
|
+
setVisibleSkills: (names) => {
|
|
36
|
+
visible = names;
|
|
37
|
+
},
|
|
38
|
+
}, "research-milestone");
|
|
39
|
+
|
|
40
|
+
assert.ok(Array.isArray(visible));
|
|
41
|
+
assert.ok(visible!.includes("write-docs"));
|
|
42
|
+
assert.ok(visible!.length < 15);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("applyUnitSkillVisibility: restores full catalog for wildcard units", () => {
|
|
46
|
+
let visible: string[] | undefined = ["stale"];
|
|
47
|
+
applyUnitSkillVisibility({
|
|
48
|
+
setVisibleSkills: (names) => {
|
|
49
|
+
visible = names;
|
|
50
|
+
},
|
|
51
|
+
}, "execute-task");
|
|
52
|
+
|
|
53
|
+
assert.equal(visible, undefined);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("effectiveSkillNamesForUnit: filters installed names by manifest", () => {
|
|
57
|
+
const installed = ["write-docs", "review", "frontend-design", "tdd"];
|
|
58
|
+
const scoped = effectiveSkillNamesForUnit("research-milestone", installed);
|
|
59
|
+
assert.ok(scoped.includes("write-docs"));
|
|
60
|
+
assert.ok(!scoped.includes("review"));
|
|
61
|
+
assert.ok(scoped.length < installed.length);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("effectiveSkillNamesForUnit: pass-through for wildcard units", () => {
|
|
65
|
+
const installed = ["write-docs", "review"];
|
|
66
|
+
assert.deepEqual(effectiveSkillNamesForUnit("execute-task", installed), installed);
|
|
67
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import type { Skill } from "@gsd/pi-coding-agent";
|
|
4
|
+
import {
|
|
5
|
+
detectNewlyInstalledSkills,
|
|
6
|
+
getInstalledSkillNames,
|
|
7
|
+
getInstalledSkills,
|
|
8
|
+
normalizeSkillName,
|
|
9
|
+
resolveInstalledSkill,
|
|
10
|
+
snapshotInstalledSkillNames,
|
|
11
|
+
} from "../skills.js";
|
|
12
|
+
|
|
13
|
+
function makeSkill(name: string, filePath = `/tmp/${name}/SKILL.md`): Skill {
|
|
14
|
+
return {
|
|
15
|
+
name,
|
|
16
|
+
description: `Use for ${name}.`,
|
|
17
|
+
filePath,
|
|
18
|
+
baseDir: `/tmp/${name}`,
|
|
19
|
+
source: "user",
|
|
20
|
+
sourceInfo: {
|
|
21
|
+
path: filePath,
|
|
22
|
+
source: "local",
|
|
23
|
+
scope: "user",
|
|
24
|
+
origin: "top-level",
|
|
25
|
+
baseDir: `/tmp/${name}`,
|
|
26
|
+
},
|
|
27
|
+
disableModelInvocation: false,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("normalizeSkillName lowercases and trims", () => {
|
|
32
|
+
assert.equal(normalizeSkillName(" React-Best "), "react-best");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("getInstalledSkills returns explicit override", () => {
|
|
36
|
+
const skills = [makeSkill("alpha"), makeSkill("beta")];
|
|
37
|
+
assert.deepEqual(getInstalledSkills(skills).map((s) => s.name), ["alpha", "beta"]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("snapshotInstalledSkillNames and detectNewlyInstalledSkills diff normalized names", () => {
|
|
41
|
+
const baseline = snapshotInstalledSkillNames([makeSkill("alpha")]);
|
|
42
|
+
const added = detectNewlyInstalledSkills(baseline, [makeSkill("alpha"), makeSkill("Beta")]);
|
|
43
|
+
assert.deepEqual(added, ["beta"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("getInstalledSkillNames maps skill names", () => {
|
|
47
|
+
assert.deepEqual(getInstalledSkillNames([makeSkill("tdd")]), ["tdd"]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("resolveInstalledSkill matches installed catalog by name", () => {
|
|
51
|
+
const skills = [makeSkill("react", "/home/user/.gsd/agent/skills/react/SKILL.md")];
|
|
52
|
+
const result = resolveInstalledSkill("react", "/project", skills);
|
|
53
|
+
assert.equal(result.skill?.name, "react");
|
|
54
|
+
assert.equal(result.resolvedPath, "/home/user/.gsd/agent/skills/react/SKILL.md");
|
|
55
|
+
});
|
|
@@ -15,6 +15,13 @@ function readGsdFile(relativePath: string): string {
|
|
|
15
15
|
return readFileSync(resolve(gsdDir, relativePath), "utf-8");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function firstIndexOfAny(source: string, needles: string[]): number {
|
|
19
|
+
const indexes = needles
|
|
20
|
+
.map((needle) => source.indexOf(needle))
|
|
21
|
+
.filter((index) => index > -1);
|
|
22
|
+
return indexes.length > 0 ? Math.min(...indexes) : -1;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
test("command entrypoints use startAutoDetached instead of awaiting startAuto (#3733)", () => {
|
|
19
26
|
const autoHandlerSrc = readGsdFile("commands/handlers/auto.ts");
|
|
20
27
|
const workflowHandlerSrc = readGsdFile("commands/handlers/workflow.ts");
|
|
@@ -145,7 +152,11 @@ test("fresh start registers the auto worker before bootstrap enters worktree flo
|
|
|
145
152
|
const resumeEnterMilestoneIdx = resumeBody.indexOf("buildLifecycle().enterMilestone");
|
|
146
153
|
const dbOpenIdx = bootstrapBody.indexOf("await openProjectDbIfPresent(base);");
|
|
147
154
|
const bootstrapRegisterIdx = bootstrapBody.indexOf("registerAutoWorkerForSession(base);");
|
|
148
|
-
const enterMilestoneIdx = bootstrapBody
|
|
155
|
+
const enterMilestoneIdx = firstIndexOfAny(bootstrapBody, [
|
|
156
|
+
"buildLifecycle().enterMilestone",
|
|
157
|
+
"lifecycle.enterMilestone",
|
|
158
|
+
"lifecycle.adoptStrandedMilestone",
|
|
159
|
+
]);
|
|
149
160
|
|
|
150
161
|
assert.ok(startAutoIdx > -1, "startAuto should exist");
|
|
151
162
|
assert.ok(preBootstrapRegisterIdx > -1, "startAuto should register worker before bootstrap");
|
|
@@ -158,7 +169,7 @@ test("fresh start registers the auto worker before bootstrap enters worktree flo
|
|
|
158
169
|
assert.ok(bootstrapIdx > -1, "bootstrapAutoSession should exist");
|
|
159
170
|
assert.ok(dbOpenIdx > -1, "bootstrap should open the project DB");
|
|
160
171
|
assert.ok(bootstrapRegisterIdx > -1, "bootstrap should register worker after DB open");
|
|
161
|
-
assert.ok(enterMilestoneIdx > -1, "bootstrap should enter milestones through lifecycle");
|
|
172
|
+
assert.ok(enterMilestoneIdx > -1, "bootstrap should enter or adopt milestones through lifecycle");
|
|
162
173
|
assert.ok(
|
|
163
174
|
preBootstrapRegisterIdx < bootstrapCallIdx,
|
|
164
175
|
"worker registration must happen before bootstrap so enterMilestone can claim milestone leases on first entry",
|