@opengsd/gsd-pi 1.4.0 → 1.5.0
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 +11 -11
- package/dist/onboarding.js +11 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +20 -25
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
- package/dist/resources/extensions/cursor-cli/index.js +16 -0
- package/dist/resources/extensions/cursor-cli/models.js +63 -0
- package/dist/resources/extensions/cursor-cli/package.json +11 -0
- package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
- package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
- package/dist/resources/extensions/gsd/activity-log.js +4 -1
- package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
- package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
- package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
- package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
- package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
- package/dist/resources/extensions/gsd/auto-start.js +4 -3
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
- package/dist/resources/extensions/gsd/auto.js +23 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
- package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
- package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
- package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
- package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
- package/dist/resources/extensions/gsd/db/queries.js +39 -0
- package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
- package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
- package/dist/resources/extensions/gsd/doctor.js +93 -3
- package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
- package/dist/resources/extensions/gsd/git-service.js +14 -18
- package/dist/resources/extensions/gsd/gsd-db.js +52 -0
- package/dist/resources/extensions/gsd/guidance.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +40 -2
- package/dist/resources/extensions/gsd/key-manager.js +1 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
- package/dist/resources/extensions/gsd/migrate-external.js +24 -7
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
- package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
- package/dist/resources/extensions/gsd/preferences-models.js +6 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
- package/dist/resources/extensions/gsd/preferences.js +29 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
- package/dist/resources/extensions/gsd/repo-identity.js +16 -0
- package/dist/resources/extensions/gsd/repository-registry.js +6 -1
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
- package/dist/resources/extensions/gsd/safe-fs.js +23 -2
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
- package/dist/resources/extensions/gsd/unit-registry.js +27 -2
- package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
- package/dist/resources/extensions/gsd/workspace.js +26 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- 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/api/update/route.js +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 +7 -7
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- 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/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +8 -1
- package/integrations/hermes/CONTEXT.md +47 -0
- package/integrations/hermes/README.md +21 -0
- package/integrations/hermes/__init__.py +31 -0
- package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
- package/integrations/hermes/docs/setup.md +4 -0
- package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
- package/integrations/hermes/open_gsd_hermes/config.py +2 -0
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
- package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
- package/integrations/hermes/plugin.yaml +4 -0
- package/integrations/hermes/tests/test_config.py +6 -0
- package/integrations/hermes/tests/test_directory_loader.py +37 -0
- package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
- package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
- package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
- package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
- package/integrations/hermes/tests/test_milestone_client.py +447 -0
- package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
- package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
- package/package.json +8 -8
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
- package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-runtime.js +25 -1
- package/packages/daemon/dist/cloud-runtime.js.map +1 -1
- package/packages/daemon/dist/cloud-runtime.test.js +51 -0
- package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
- package/packages/daemon/dist/message-batcher.d.ts +7 -0
- package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
- package/packages/daemon/dist/message-batcher.js +13 -0
- package/packages/daemon/dist/message-batcher.js.map +1 -1
- package/packages/daemon/dist/message-batcher.test.js +48 -0
- package/packages/daemon/dist/message-batcher.test.js.map +1 -1
- package/packages/daemon/dist/session-manager.js +1 -1
- package/packages/daemon/dist/session-manager.js.map +1 -1
- package/packages/daemon/package.json +5 -5
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/server.d.ts +18 -5
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +46 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +1 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +3 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- 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/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/dist/rpc-client.js +1 -1
- package/packages/rpc-client/dist/rpc-client.js.map +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/link-workspace-packages.cjs +14 -4
- package/src/resources/GSD-WORKFLOW.md +20 -25
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
- package/src/resources/extensions/cursor-cli/index.ts +18 -0
- package/src/resources/extensions/cursor-cli/models.ts +65 -0
- package/src/resources/extensions/cursor-cli/package.json +11 -0
- package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
- package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
- package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
- package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
- package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
- package/src/resources/extensions/gsd/activity-log.ts +4 -1
- package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
- package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
- package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
- package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
- package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
- package/src/resources/extensions/gsd/auto-start.ts +4 -3
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
- package/src/resources/extensions/gsd/auto.ts +22 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
- package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
- package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
- package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
- package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
- package/src/resources/extensions/gsd/db/queries.ts +40 -0
- package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
- package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
- package/src/resources/extensions/gsd/doctor-types.ts +5 -1
- package/src/resources/extensions/gsd/doctor.ts +95 -3
- package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
- package/src/resources/extensions/gsd/git-service.ts +14 -17
- package/src/resources/extensions/gsd/gsd-db.ts +72 -0
- package/src/resources/extensions/gsd/guidance.ts +2 -0
- package/src/resources/extensions/gsd/guided-flow.ts +45 -2
- package/src/resources/extensions/gsd/key-manager.ts +1 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
- package/src/resources/extensions/gsd/migrate-external.ts +23 -6
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
- package/src/resources/extensions/gsd/model-router.ts +4 -5
- package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
- package/src/resources/extensions/gsd/preferences-models.ts +6 -3
- package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
- package/src/resources/extensions/gsd/preferences.ts +38 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
- package/src/resources/extensions/gsd/repo-identity.ts +17 -0
- package/src/resources/extensions/gsd/repository-registry.ts +7 -1
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
- package/src/resources/extensions/gsd/safe-fs.ts +22 -2
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
- package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
- package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
- package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
- package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
- package/src/resources/extensions/gsd/unit-registry.ts +34 -2
- package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
- package/src/resources/extensions/gsd/workspace.ts +42 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
- package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
|
@@ -4,7 +4,7 @@ import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
|
|
7
|
-
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask, getSlice, getTask, getGateResults } from '../gsd-db.ts';
|
|
7
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask, getSlice, getTask, getSliceTasks, getGateResults } from '../gsd-db.ts';
|
|
8
8
|
import { handlePlanTask } from '../tools/plan-task.ts';
|
|
9
9
|
import { parseTaskPlanFile } from '../files.ts';
|
|
10
10
|
|
|
@@ -24,6 +24,26 @@ function seedParent(): void {
|
|
|
24
24
|
insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Planning slice', status: 'pending', demo: 'Rendered plans exist.' });
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function writeParentWorkspacePreferences(base: string): void {
|
|
28
|
+
mkdirSync(join(base, 'frontend', 'src'), { recursive: true });
|
|
29
|
+
mkdirSync(join(base, 'backend', 'src'), { recursive: true });
|
|
30
|
+
writeFileSync(
|
|
31
|
+
join(base, '.gsd', 'PREFERENCES.md'),
|
|
32
|
+
[
|
|
33
|
+
'---',
|
|
34
|
+
'workspace:',
|
|
35
|
+
' mode: parent',
|
|
36
|
+
' repositories:',
|
|
37
|
+
' frontend:',
|
|
38
|
+
' path: frontend',
|
|
39
|
+
' backend:',
|
|
40
|
+
' path: backend',
|
|
41
|
+
'---',
|
|
42
|
+
].join('\n'),
|
|
43
|
+
'utf-8',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
function validParams() {
|
|
28
48
|
return {
|
|
29
49
|
milestoneId: 'M001',
|
|
@@ -155,6 +175,32 @@ test('handlePlanTask rejects absolute task IO paths outside the active worktree'
|
|
|
155
175
|
}
|
|
156
176
|
});
|
|
157
177
|
|
|
178
|
+
test('handlePlanTask rejects parent-checkout task IO paths from an active worktree', async () => {
|
|
179
|
+
const projectRoot = mkdtempSync(join(tmpdir(), 'gsd-plan-task-project-'));
|
|
180
|
+
const worktree = join(projectRoot, '.gsd', 'worktrees', 'M001');
|
|
181
|
+
mkdirSync(join(projectRoot, '.gsd'), { recursive: true });
|
|
182
|
+
mkdirSync(join(worktree, '.gsd', 'phases', '01-test'), { recursive: true });
|
|
183
|
+
openDatabase(join(projectRoot, '.gsd', 'gsd.db'));
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
seedParent();
|
|
187
|
+
const parentCheckoutPath = join(projectRoot, 'src', 'index.ts');
|
|
188
|
+
const result = await handlePlanTask({
|
|
189
|
+
...validParams(),
|
|
190
|
+
files: [parentCheckoutPath],
|
|
191
|
+
inputs: [parentCheckoutPath],
|
|
192
|
+
expectedOutput: [parentCheckoutPath],
|
|
193
|
+
}, worktree);
|
|
194
|
+
|
|
195
|
+
assert.ok('error' in result);
|
|
196
|
+
assert.match(result.error, /validation failed: files contains path outside allowed repository roots/);
|
|
197
|
+
assert.match(result.error, new RegExp(worktree.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
198
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'parent-checkout paths must not persist worktree task planning');
|
|
199
|
+
} finally {
|
|
200
|
+
cleanup(projectRoot);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
158
204
|
test('handlePlanTask rejects missing parent slice', async () => {
|
|
159
205
|
const base = makeTmpBase();
|
|
160
206
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -243,3 +289,220 @@ test('handlePlanTask reruns idempotently and refreshes parse-visible state', asy
|
|
|
243
289
|
cleanup(base);
|
|
244
290
|
}
|
|
245
291
|
});
|
|
292
|
+
|
|
293
|
+
test('handlePlanTask persists targetRepositories for parent-workspace tasks', async () => {
|
|
294
|
+
const base = makeTmpBase();
|
|
295
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
seedParent();
|
|
299
|
+
const result = await handlePlanTask({
|
|
300
|
+
...validParams(),
|
|
301
|
+
targetRepositories: ['project'],
|
|
302
|
+
}, base);
|
|
303
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
304
|
+
|
|
305
|
+
const tasks = getSliceTasks('M001', 'S02');
|
|
306
|
+
const planned = tasks.find((t) => t.id === 'T02');
|
|
307
|
+
assert.ok(planned, 'planned task should exist');
|
|
308
|
+
assert.deepEqual(planned?.target_repositories, ['project']);
|
|
309
|
+
} finally {
|
|
310
|
+
cleanup(base);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('handlePlanTask rejects non-array targetRepositories', async () => {
|
|
315
|
+
const base = makeTmpBase();
|
|
316
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
seedParent();
|
|
320
|
+
const result = await handlePlanTask({
|
|
321
|
+
...validParams(),
|
|
322
|
+
targetRepositories: 'frontend' as unknown as string[],
|
|
323
|
+
}, base);
|
|
324
|
+
assert.ok('error' in result);
|
|
325
|
+
assert.match(result.error, /validation failed: targetRepositories/);
|
|
326
|
+
} finally {
|
|
327
|
+
cleanup(base);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('handlePlanTask rejects empty targetRepositories', async () => {
|
|
332
|
+
const base = makeTmpBase();
|
|
333
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
seedParent();
|
|
337
|
+
const result = await handlePlanTask({
|
|
338
|
+
...validParams(),
|
|
339
|
+
targetRepositories: [],
|
|
340
|
+
}, base);
|
|
341
|
+
assert.ok('error' in result);
|
|
342
|
+
assert.match(result.error, /validation failed: targetRepositories must include at least one repository id when provided/);
|
|
343
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'invalid target repositories must not persist');
|
|
344
|
+
} finally {
|
|
345
|
+
cleanup(base);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('handlePlanTask rejects unknown target repositories', async () => {
|
|
350
|
+
const base = makeTmpBase();
|
|
351
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
seedParent();
|
|
355
|
+
const result = await handlePlanTask({
|
|
356
|
+
...validParams(),
|
|
357
|
+
targetRepositories: ['frontend'],
|
|
358
|
+
}, base);
|
|
359
|
+
assert.ok('error' in result);
|
|
360
|
+
assert.match(result.error, /validation failed: unknown targetRepositories:/);
|
|
361
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'unknown target repositories must not persist');
|
|
362
|
+
} finally {
|
|
363
|
+
cleanup(base);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('handlePlanTask enforces path scope to declared target repositories', async () => {
|
|
368
|
+
const base = makeTmpBase();
|
|
369
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
seedParent();
|
|
373
|
+
mkdirSync(join(base, 'frontend', 'src'), { recursive: true });
|
|
374
|
+
mkdirSync(join(base, 'backend', 'src'), { recursive: true });
|
|
375
|
+
writeFileSync(
|
|
376
|
+
join(base, '.gsd', 'PREFERENCES.md'),
|
|
377
|
+
[
|
|
378
|
+
'---',
|
|
379
|
+
'workspace:',
|
|
380
|
+
' mode: parent',
|
|
381
|
+
' repositories:',
|
|
382
|
+
' frontend:',
|
|
383
|
+
' path: frontend',
|
|
384
|
+
' backend:',
|
|
385
|
+
' path: backend',
|
|
386
|
+
'---',
|
|
387
|
+
].join('\n'),
|
|
388
|
+
'utf-8',
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
const result = await handlePlanTask({
|
|
392
|
+
...validParams(),
|
|
393
|
+
targetRepositories: ['frontend'],
|
|
394
|
+
files: [join(base, 'backend', 'src', 'server.ts')],
|
|
395
|
+
inputs: ['app.js'],
|
|
396
|
+
expectedOutput: ['app.js'],
|
|
397
|
+
}, base);
|
|
398
|
+
assert.ok('error' in result);
|
|
399
|
+
assert.match(result.error, /validation failed: files contains path outside allowed repository roots/);
|
|
400
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'invalid scoped paths must not persist');
|
|
401
|
+
} finally {
|
|
402
|
+
cleanup(base);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('handlePlanTask inherits parent slice target repositories for path scope', async () => {
|
|
407
|
+
const base = makeTmpBase();
|
|
408
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
seedParent();
|
|
412
|
+
insertSlice({
|
|
413
|
+
id: 'S02',
|
|
414
|
+
milestoneId: 'M001',
|
|
415
|
+
planning: { targetRepositories: ['frontend'] },
|
|
416
|
+
});
|
|
417
|
+
writeParentWorkspacePreferences(base);
|
|
418
|
+
|
|
419
|
+
const result = await handlePlanTask({
|
|
420
|
+
...validParams(),
|
|
421
|
+
files: [join(base, 'backend', 'src', 'server.ts')],
|
|
422
|
+
inputs: ['app.js'],
|
|
423
|
+
expectedOutput: ['app.js'],
|
|
424
|
+
}, base);
|
|
425
|
+
assert.ok('error' in result);
|
|
426
|
+
assert.match(result.error, /validation failed: files contains path outside allowed repository roots/);
|
|
427
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'slice-scoped invalid paths must not persist');
|
|
428
|
+
} finally {
|
|
429
|
+
cleanup(base);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('handlePlanTask preserves stored task target repositories when omitted replan needs them for path scope', async () => {
|
|
434
|
+
const base = makeTmpBase();
|
|
435
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
seedParent();
|
|
439
|
+
writeParentWorkspacePreferences(base);
|
|
440
|
+
insertSlice({
|
|
441
|
+
id: 'S02',
|
|
442
|
+
milestoneId: 'M001',
|
|
443
|
+
planning: { targetRepositories: ['frontend'] },
|
|
444
|
+
});
|
|
445
|
+
insertTask({
|
|
446
|
+
id: 'T02',
|
|
447
|
+
sliceId: 'S02',
|
|
448
|
+
milestoneId: 'M001',
|
|
449
|
+
title: 'Parent root work',
|
|
450
|
+
status: 'pending',
|
|
451
|
+
planning: { targetRepositories: ['project'] },
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const rootFile = join(base, 'root-config.json');
|
|
455
|
+
const result = await handlePlanTask({
|
|
456
|
+
...validParams(),
|
|
457
|
+
files: [rootFile],
|
|
458
|
+
inputs: [rootFile],
|
|
459
|
+
expectedOutput: [rootFile],
|
|
460
|
+
}, base);
|
|
461
|
+
|
|
462
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
463
|
+
assert.deepEqual(getTask('M001', 'S02', 'T02')?.target_repositories, ['project']);
|
|
464
|
+
} finally {
|
|
465
|
+
cleanup(base);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test('handlePlanTask follows the current slice target default when omitted replan paths validate there', async () => {
|
|
470
|
+
const base = makeTmpBase();
|
|
471
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
seedParent();
|
|
475
|
+
writeParentWorkspacePreferences(base);
|
|
476
|
+
insertSlice({
|
|
477
|
+
id: 'S02',
|
|
478
|
+
milestoneId: 'M001',
|
|
479
|
+
planning: { targetRepositories: ['frontend'] },
|
|
480
|
+
});
|
|
481
|
+
insertTask({
|
|
482
|
+
id: 'T02',
|
|
483
|
+
sliceId: 'S02',
|
|
484
|
+
milestoneId: 'M001',
|
|
485
|
+
title: 'Child repo work',
|
|
486
|
+
status: 'pending',
|
|
487
|
+
planning: { targetRepositories: ['frontend'] },
|
|
488
|
+
});
|
|
489
|
+
insertSlice({
|
|
490
|
+
id: 'S02',
|
|
491
|
+
milestoneId: 'M001',
|
|
492
|
+
planning: { targetRepositories: ['backend'] },
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const backendFile = join(base, 'backend', 'src', 'server.ts');
|
|
496
|
+
const result = await handlePlanTask({
|
|
497
|
+
...validParams(),
|
|
498
|
+
files: [backendFile],
|
|
499
|
+
inputs: [backendFile],
|
|
500
|
+
expectedOutput: [backendFile],
|
|
501
|
+
}, base);
|
|
502
|
+
|
|
503
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
504
|
+
assert.deepEqual(getTask('M001', 'S02', 'T02')?.target_repositories, ['backend']);
|
|
505
|
+
} finally {
|
|
506
|
+
cleanup(base);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
@@ -141,6 +141,26 @@ test("invalid value types produce errors and fall back to undefined", () => {
|
|
|
141
141
|
}
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
+
test("remote_questions accepts object config and false disables without error", () => {
|
|
145
|
+
const disabled = validatePreferences({ remote_questions: false } as any);
|
|
146
|
+
assert.equal(disabled.errors.length, 0);
|
|
147
|
+
assert.equal(disabled.preferences.remote_questions, undefined);
|
|
148
|
+
|
|
149
|
+
const configured = validatePreferences({
|
|
150
|
+
remote_questions: { channel: "telegram", channel_id: "12345" },
|
|
151
|
+
});
|
|
152
|
+
assert.equal(configured.errors.length, 0);
|
|
153
|
+
assert.deepEqual(configured.preferences.remote_questions, {
|
|
154
|
+
channel: "telegram",
|
|
155
|
+
channel_id: "12345",
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
for (const value of ["telegram", 0]) {
|
|
159
|
+
const invalid = validatePreferences({ remote_questions: value } as any);
|
|
160
|
+
assert.ok(invalid.errors.includes("remote_questions must be an object"));
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
144
164
|
test("flat_rate_providers: accepts string array", () => {
|
|
145
165
|
const { errors, preferences } = validatePreferences({
|
|
146
166
|
flat_rate_providers: ["my-proxy", "private-cli"],
|
|
@@ -327,6 +347,136 @@ test("workspace.repositories duplicate path error includes both repository ids",
|
|
|
327
347
|
);
|
|
328
348
|
});
|
|
329
349
|
|
|
350
|
+
test("workspace.mode parent with no repositories is rejected", () => {
|
|
351
|
+
const { errors } = validatePreferences({
|
|
352
|
+
workspace: {
|
|
353
|
+
mode: "parent",
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
assert.ok(
|
|
358
|
+
errors.some((e) =>
|
|
359
|
+
e.includes('workspace.mode "parent" requires at least one repository under workspace.repositories'),
|
|
360
|
+
),
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("workspace.mode parent with empty repositories map is rejected", () => {
|
|
365
|
+
const { errors } = validatePreferences({
|
|
366
|
+
workspace: {
|
|
367
|
+
mode: "parent",
|
|
368
|
+
repositories: {},
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
assert.ok(
|
|
373
|
+
errors.some((e) =>
|
|
374
|
+
e.includes('workspace.mode "parent" requires at least one repository under workspace.repositories'),
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("workspace.mode parent with declared repositories is valid", () => {
|
|
380
|
+
const { errors } = validatePreferences({
|
|
381
|
+
workspace: {
|
|
382
|
+
mode: "parent",
|
|
383
|
+
repositories: {
|
|
384
|
+
frontend: { path: "frontend" },
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
assert.equal(errors.length, 0);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("mode:team + workspace.mode:parent warns about root-only push/PR", () => {
|
|
393
|
+
const { warnings } = validatePreferences({
|
|
394
|
+
mode: "team",
|
|
395
|
+
workspace: {
|
|
396
|
+
mode: "parent",
|
|
397
|
+
repositories: {
|
|
398
|
+
frontend: { path: "frontend" },
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
const crossAxisWarnings = warnings.filter((w) => w.includes("mode:team + workspace.mode:parent"));
|
|
403
|
+
|
|
404
|
+
assert.ok(
|
|
405
|
+
crossAxisWarnings.some((w) =>
|
|
406
|
+
w.includes("ADR-044"),
|
|
407
|
+
),
|
|
408
|
+
"expected a cross-axis warning naming ADR-044",
|
|
409
|
+
);
|
|
410
|
+
assert.equal(crossAxisWarnings.length, 1, "expected exactly one cross-axis warning");
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("mode:team + workspace.mode:parent warns after global/project merge", (t) => {
|
|
414
|
+
const originalCwd = process.cwd();
|
|
415
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
416
|
+
const tempProject = mkdtempSync(join(tmpdir(), "gsd-prefs-cross-axis-project-"));
|
|
417
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-prefs-cross-axis-home-"));
|
|
418
|
+
|
|
419
|
+
t.after(() => {
|
|
420
|
+
clearGSDPreferencesCache();
|
|
421
|
+
process.chdir(originalCwd);
|
|
422
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
423
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
424
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
425
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
429
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
430
|
+
process.chdir(tempProject);
|
|
431
|
+
clearGSDPreferencesCache();
|
|
432
|
+
|
|
433
|
+
writeFileSync(getGlobalGSDPreferencesPath(), "---\nversion: 1\nmode: team\n---\n", "utf-8");
|
|
434
|
+
writeFileSync(
|
|
435
|
+
getProjectGSDPreferencesPath(tempProject),
|
|
436
|
+
[
|
|
437
|
+
"---",
|
|
438
|
+
"version: 1",
|
|
439
|
+
"workspace:",
|
|
440
|
+
" mode: parent",
|
|
441
|
+
" repositories:",
|
|
442
|
+
" frontend:",
|
|
443
|
+
" path: frontend",
|
|
444
|
+
"---",
|
|
445
|
+
"",
|
|
446
|
+
].join("\n"),
|
|
447
|
+
"utf-8",
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const rawGlobalWarnings = loadGlobalGSDPreferences()?.warnings ?? [];
|
|
451
|
+
const rawProjectWarnings = loadProjectGSDPreferences(tempProject)?.warnings ?? [];
|
|
452
|
+
assert.equal(rawGlobalWarnings.filter((w) => w.includes("workspace.mode:parent")).length, 0);
|
|
453
|
+
assert.equal(rawProjectWarnings.filter((w) => w.includes("mode:team + workspace.mode:parent")).length, 0);
|
|
454
|
+
|
|
455
|
+
const loaded = loadEffectiveGSDPreferences(tempProject);
|
|
456
|
+
assert.equal(loaded?.preferences.mode, "team");
|
|
457
|
+
assert.equal(loaded?.preferences.workspace?.mode, "parent");
|
|
458
|
+
assert.equal(
|
|
459
|
+
(loaded?.warnings ?? []).filter((w) => w.includes("mode:team + workspace.mode:parent")).length,
|
|
460
|
+
1,
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const diagnostics = collectPreferenceDiagnostics(tempProject).filter((diagnostic) =>
|
|
464
|
+
diagnostic.message.includes("mode:team + workspace.mode:parent"),
|
|
465
|
+
);
|
|
466
|
+
assert.equal(diagnostics.length, 1);
|
|
467
|
+
assert.equal(diagnostics[0]?.scope, "project");
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("mode:team without workspace.mode:parent does not warn about push", () => {
|
|
471
|
+
const { warnings } = validatePreferences({
|
|
472
|
+
mode: "team",
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
assert.ok(
|
|
476
|
+
!warnings.some((w) => w.includes("workspace.mode:parent")),
|
|
477
|
+
"team mode alone must not trigger the parent-workspace push warning",
|
|
478
|
+
);
|
|
479
|
+
});
|
|
330
480
|
|
|
331
481
|
test("workspace is a recognized preference key (no unknown warning)", () => {
|
|
332
482
|
const { warnings } = validatePreferences({
|
|
@@ -182,6 +182,70 @@ test("prefs wizard save path preserves every known preference key", async () =>
|
|
|
182
182
|
}
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
test("verification wizard prompts for per-unit cost cap and persists it (regression #1121)", async () => {
|
|
186
|
+
// Regression for #1121: the `/gsd prefs` Verification category never prompted for
|
|
187
|
+
// `per_unit_cost_cap_usd`, so the per-unit cost cap could only be set by hand-editing
|
|
188
|
+
// PREFERENCES.md. Drive the wizard into Verification, answer the cap prompt, and assert
|
|
189
|
+
// the prompt exists and the entered value is serialized + round-trips back as a number.
|
|
190
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-prefs-wizard-"));
|
|
191
|
+
const prefsPath = join(dir, "PREFERENCES.md");
|
|
192
|
+
|
|
193
|
+
const inputLabels: string[] = [];
|
|
194
|
+
let topMenuVisits = 0;
|
|
195
|
+
|
|
196
|
+
const ctx = {
|
|
197
|
+
ui: {
|
|
198
|
+
notify() {},
|
|
199
|
+
select: async (label: string, options: string[]) => {
|
|
200
|
+
if (label === "GSD Preferences") {
|
|
201
|
+
topMenuVisits += 1;
|
|
202
|
+
if (topMenuVisits === 1) {
|
|
203
|
+
const verification = options.find((o) => o.startsWith("Verification"));
|
|
204
|
+
assert.ok(verification, "wizard menu must offer a Verification category");
|
|
205
|
+
return verification;
|
|
206
|
+
}
|
|
207
|
+
return "── Save & Exit ──";
|
|
208
|
+
}
|
|
209
|
+
// verification_commands string-list sub-menu — leave it untouched
|
|
210
|
+
if (options.includes("Done")) return "Done";
|
|
211
|
+
// boolean/enum prompts within the category — keep the current value
|
|
212
|
+
if (options.includes("(keep current)")) return "(keep current)";
|
|
213
|
+
return options[options.length - 1];
|
|
214
|
+
},
|
|
215
|
+
input: async (label: string) => {
|
|
216
|
+
inputLabels.push(label);
|
|
217
|
+
if (label.includes("Per-unit cost cap")) return "12.5";
|
|
218
|
+
return null; // escape every other numeric/string prompt (no change)
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
waitForIdle: async () => {},
|
|
222
|
+
reload: async () => {},
|
|
223
|
+
} as any;
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
await handlePrefsWizard(ctx, "project", {}, { pathOverride: prefsPath });
|
|
227
|
+
|
|
228
|
+
// The Verification wizard must actually ask for the per-unit cost cap.
|
|
229
|
+
assert.ok(
|
|
230
|
+
inputLabels.some((l) => l.includes("Per-unit cost cap")),
|
|
231
|
+
`Verification wizard must prompt for the per-unit cost cap; prompts seen:\n${inputLabels.join("\n")}`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// The entered value must be serialized to PREFERENCES.md...
|
|
235
|
+
const saved = readFileSync(prefsPath, "utf-8");
|
|
236
|
+
assert.match(saved, /per_unit_cost_cap_usd:\s*12\.5(\s|$)/m);
|
|
237
|
+
|
|
238
|
+
// ...and round-trip back through the parser/validator as a number.
|
|
239
|
+
const parsed = parsePreferencesMarkdown(saved);
|
|
240
|
+
assert.notEqual(parsed, null);
|
|
241
|
+
const { errors, preferences } = validatePreferences(parsed!);
|
|
242
|
+
assert.deepEqual(errors, []);
|
|
243
|
+
assert.equal(preferences.per_unit_cost_cap_usd, 12.5);
|
|
244
|
+
} finally {
|
|
245
|
+
rmSync(dir, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
185
249
|
test("category summaries expose the wizard menu surface for configured prefs", () => {
|
|
186
250
|
const summaries = buildCategorySummaries(PREF_SAMPLE_VALUES);
|
|
187
251
|
assert.deepEqual(
|
|
@@ -203,11 +267,14 @@ test("category summaries expose the wizard menu surface for configured prefs", (
|
|
|
203
267
|
"timeouts",
|
|
204
268
|
"uok",
|
|
205
269
|
"verification",
|
|
270
|
+
"workspace",
|
|
206
271
|
],
|
|
207
272
|
);
|
|
208
273
|
assert.match(summaries.models, /phase/);
|
|
209
274
|
assert.match(summaries.integrations, /remote: C123/);
|
|
210
275
|
assert.match(summaries.verification, /1 cmd/);
|
|
276
|
+
assert.match(summaries.workspace, /mode: parent/);
|
|
277
|
+
assert.match(summaries.workspace, /1 repo/);
|
|
211
278
|
});
|
|
212
279
|
|
|
213
280
|
test("models wizard offers discovered models for enabled providers", async () => {
|
|
@@ -276,3 +343,78 @@ test("models wizard offers discovered models for enabled providers", async () =>
|
|
|
276
343
|
rmSync(dir, { recursive: true, force: true });
|
|
277
344
|
}
|
|
278
345
|
});
|
|
346
|
+
|
|
347
|
+
test("workspace wizard category configures parent mode with a declared repository", async () => {
|
|
348
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-prefs-wizard-workspace-"));
|
|
349
|
+
const prefsPath = join(dir, "PREFERENCES.md");
|
|
350
|
+
try {
|
|
351
|
+
// Drive: Workspace → parent mode → Add repository (id=frontend, path=frontend,
|
|
352
|
+
// role=ui, verification=npm test, commit policy auto) → Done → Save & Exit.
|
|
353
|
+
const selects = [
|
|
354
|
+
"Workspace (single-repo)", // pick the Workspace category
|
|
355
|
+
"parent", // promptEnum: workspace mode
|
|
356
|
+
"Add repository", // sub-menu
|
|
357
|
+
"(keep current)", // promptEnum: commit policy -> keep auto default
|
|
358
|
+
"Done", // exit repo sub-menu
|
|
359
|
+
"── Save & Exit ──", // exit wizard
|
|
360
|
+
];
|
|
361
|
+
const inputs = [
|
|
362
|
+
"frontend", // repository id
|
|
363
|
+
"frontend", // repository path
|
|
364
|
+
"ui", // role
|
|
365
|
+
"npm test", // verification commands
|
|
366
|
+
];
|
|
367
|
+
const ctx = {
|
|
368
|
+
ui: {
|
|
369
|
+
notify() {},
|
|
370
|
+
select: async () => selects.shift(),
|
|
371
|
+
input: async () => inputs.shift(),
|
|
372
|
+
},
|
|
373
|
+
waitForIdle: async () => {},
|
|
374
|
+
reload: async () => {},
|
|
375
|
+
} as any;
|
|
376
|
+
|
|
377
|
+
await handlePrefsWizard(ctx, "project", {}, { pathOverride: prefsPath });
|
|
378
|
+
|
|
379
|
+
const saved = readFileSync(prefsPath, "utf-8");
|
|
380
|
+
assert.match(saved, /mode: parent/);
|
|
381
|
+
assert.match(saved, /frontend:/);
|
|
382
|
+
assert.match(saved, /path: frontend/);
|
|
383
|
+
assert.match(saved, /role: ui/);
|
|
384
|
+
assert.match(saved, /npm test/);
|
|
385
|
+
assert.equal(selects.length, 0, "all queued selects should be consumed");
|
|
386
|
+
assert.equal(inputs.length, 0, "all queued inputs should be consumed");
|
|
387
|
+
} finally {
|
|
388
|
+
rmSync(dir, { recursive: true, force: true });
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("workspace wizard does not save parent mode when no repository is declared", async () => {
|
|
393
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-prefs-wizard-workspace-empty-"));
|
|
394
|
+
const prefsPath = join(dir, "PREFERENCES.md");
|
|
395
|
+
try {
|
|
396
|
+
// parent mode with no repos added → should warn and not persist workspace.
|
|
397
|
+
const selects = [
|
|
398
|
+
"Workspace (single-repo)",
|
|
399
|
+
"parent",
|
|
400
|
+
"Done", // add nothing
|
|
401
|
+
"── Save & Exit ──",
|
|
402
|
+
];
|
|
403
|
+
const ctx = {
|
|
404
|
+
ui: {
|
|
405
|
+
notify() {},
|
|
406
|
+
select: async () => selects.shift(),
|
|
407
|
+
input: async () => "",
|
|
408
|
+
},
|
|
409
|
+
waitForIdle: async () => {},
|
|
410
|
+
reload: async () => {},
|
|
411
|
+
} as any;
|
|
412
|
+
|
|
413
|
+
await handlePrefsWizard(ctx, "project", {}, { pathOverride: prefsPath });
|
|
414
|
+
|
|
415
|
+
const saved = readFileSync(prefsPath, "utf-8");
|
|
416
|
+
assert.doesNotMatch(saved, /mode: parent/, "parent mode must not persist without a declared repo");
|
|
417
|
+
} finally {
|
|
418
|
+
rmSync(dir, { recursive: true, force: true });
|
|
419
|
+
}
|
|
420
|
+
});
|
|
@@ -227,7 +227,7 @@ test("plan-slice verify-fail logs a recovery warning when the tasks dir is missi
|
|
|
227
227
|
}
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
test("plan-slice verify-fail logs a recovery warning when an individual task
|
|
230
|
+
test("plan-slice verify-fail logs a recovery warning when an individual task artifact is missing", () => {
|
|
231
231
|
const base = createFixtureBase("gsd-recovery-logs-taskplan-");
|
|
232
232
|
try {
|
|
233
233
|
const dir = sliceDir(base, "M001", "S01");
|
|
@@ -236,17 +236,17 @@ test("plan-slice verify-fail logs a recovery warning when an individual task pla
|
|
|
236
236
|
"# S01: Has task\n\n## Tasks\n\n- [ ] **T01: A** `est:15m`\n",
|
|
237
237
|
"utf-8",
|
|
238
238
|
);
|
|
239
|
-
// Create the tasks dir but NOT
|
|
239
|
+
// Create the tasks dir but NOT any T01 task artifact inside it.
|
|
240
240
|
mkdirSync(join(dir, "tasks"), { recursive: true });
|
|
241
241
|
|
|
242
242
|
const { result, logs } = verifyAndCaptureLogs("plan-slice", "M001/S01", base);
|
|
243
243
|
|
|
244
|
-
assert.equal(result, false, "a missing task
|
|
244
|
+
assert.equal(result, false, "a missing task artifact must fail verification");
|
|
245
245
|
const recovery = findRecovery(logs);
|
|
246
246
|
assert.ok(recovery, "a recovery warning must be logged");
|
|
247
247
|
assert.match(
|
|
248
248
|
recovery!.message,
|
|
249
|
-
/verify-fail plan-slice M001\/S01: task
|
|
249
|
+
/verify-fail plan-slice M001\/S01: task artifact missing for T01/u,
|
|
250
250
|
);
|
|
251
251
|
} finally {
|
|
252
252
|
rmSync(base, { recursive: true, force: true });
|