@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
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Regression tests for ScheduleWakeup tool behavior.
|
|
3
|
+
|
|
4
|
+
import test, { mock } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
|
|
7
|
+
import { autoSession } from "../auto-runtime-state.ts";
|
|
8
|
+
import {
|
|
9
|
+
registerScheduleWakeupTool,
|
|
10
|
+
_resetInteractiveWakeupsForTest,
|
|
11
|
+
} from "../bootstrap/schedule-wakeup-tool.ts";
|
|
12
|
+
|
|
13
|
+
test("ScheduleWakeup arms an interactive wakeup when auto-mode is inactive", async () => {
|
|
14
|
+
autoSession.reset();
|
|
15
|
+
_resetInteractiveWakeupsForTest();
|
|
16
|
+
mock.timers.enable({ apis: ["setTimeout"] });
|
|
17
|
+
|
|
18
|
+
const sent: Array<{ message: unknown; options: unknown }> = [];
|
|
19
|
+
let tool: any;
|
|
20
|
+
const pi = {
|
|
21
|
+
registerTool(registered: any) {
|
|
22
|
+
tool = registered;
|
|
23
|
+
},
|
|
24
|
+
sendMessage(message: unknown, options: unknown) {
|
|
25
|
+
sent.push({ message, options });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
registerScheduleWakeupTool(pi as any);
|
|
31
|
+
assert.ok(tool, "ScheduleWakeup tool should be registered");
|
|
32
|
+
|
|
33
|
+
const result = await tool.execute(
|
|
34
|
+
"call-1",
|
|
35
|
+
{
|
|
36
|
+
delaySeconds: 1,
|
|
37
|
+
prompt: "Check the external job and report back.",
|
|
38
|
+
reason: "poll external job",
|
|
39
|
+
},
|
|
40
|
+
undefined,
|
|
41
|
+
undefined,
|
|
42
|
+
{ cwd: process.cwd() },
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
assert.equal(result.isError, undefined);
|
|
46
|
+
assert.match(result.content[0].text, /Wakeup scheduled for 1s/);
|
|
47
|
+
assert.equal(sent.length, 0, "wakeup should not dispatch synchronously");
|
|
48
|
+
|
|
49
|
+
mock.timers.tick(999);
|
|
50
|
+
assert.equal(sent.length, 0, "wakeup should wait for the configured delay");
|
|
51
|
+
|
|
52
|
+
mock.timers.tick(1);
|
|
53
|
+
assert.equal(sent.length, 1);
|
|
54
|
+
assert.deepEqual(sent[0].options, { triggerTurn: true });
|
|
55
|
+
assert.deepEqual(sent[0].message, {
|
|
56
|
+
customType: "gsd-schedule-wakeup",
|
|
57
|
+
content: "Check the external job and report back.",
|
|
58
|
+
display: true,
|
|
59
|
+
details: {
|
|
60
|
+
delaySeconds: 1,
|
|
61
|
+
reason: "poll external job",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
} finally {
|
|
65
|
+
mock.timers.reset();
|
|
66
|
+
_resetInteractiveWakeupsForTest();
|
|
67
|
+
autoSession.reset();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("ScheduleWakeup re-arm cancels the prior interactive timer instead of stacking", async () => {
|
|
72
|
+
autoSession.reset();
|
|
73
|
+
_resetInteractiveWakeupsForTest();
|
|
74
|
+
mock.timers.enable({ apis: ["setTimeout"] });
|
|
75
|
+
|
|
76
|
+
const sent: Array<{ message: any; options: unknown }> = [];
|
|
77
|
+
let tool: any;
|
|
78
|
+
const pi = {
|
|
79
|
+
registerTool(registered: any) {
|
|
80
|
+
tool = registered;
|
|
81
|
+
},
|
|
82
|
+
sendMessage(message: unknown, options: unknown) {
|
|
83
|
+
sent.push({ message, options } as { message: any; options: unknown });
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
registerScheduleWakeupTool(pi as any);
|
|
89
|
+
assert.ok(tool, "ScheduleWakeup tool should be registered");
|
|
90
|
+
|
|
91
|
+
const ctx = { cwd: process.cwd() };
|
|
92
|
+
await tool.execute(
|
|
93
|
+
"call-1",
|
|
94
|
+
{ delaySeconds: 5, prompt: "stale prompt", reason: "first arm" },
|
|
95
|
+
undefined,
|
|
96
|
+
undefined,
|
|
97
|
+
ctx,
|
|
98
|
+
);
|
|
99
|
+
await tool.execute(
|
|
100
|
+
"call-2",
|
|
101
|
+
{ delaySeconds: 5, prompt: "fresh prompt", reason: "re-arm" },
|
|
102
|
+
undefined,
|
|
103
|
+
undefined,
|
|
104
|
+
ctx,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
mock.timers.tick(5000);
|
|
108
|
+
|
|
109
|
+
assert.equal(sent.length, 1, "re-arming must not stack overlapping wakeups");
|
|
110
|
+
assert.equal(sent[0].message.content, "fresh prompt");
|
|
111
|
+
} finally {
|
|
112
|
+
mock.timers.reset();
|
|
113
|
+
_resetInteractiveWakeupsForTest();
|
|
114
|
+
autoSession.reset();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("ScheduleWakeup keeps interactive wakeups isolated per session base path", async () => {
|
|
119
|
+
autoSession.reset();
|
|
120
|
+
_resetInteractiveWakeupsForTest();
|
|
121
|
+
mock.timers.enable({ apis: ["setTimeout"] });
|
|
122
|
+
|
|
123
|
+
const sent: Array<{ message: any; options: unknown }> = [];
|
|
124
|
+
let tool: any;
|
|
125
|
+
const pi = {
|
|
126
|
+
registerTool(registered: any) {
|
|
127
|
+
tool = registered;
|
|
128
|
+
},
|
|
129
|
+
sendMessage(message: unknown, options: unknown) {
|
|
130
|
+
sent.push({ message, options } as { message: any; options: unknown });
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
registerScheduleWakeupTool(pi as any);
|
|
136
|
+
assert.ok(tool, "ScheduleWakeup tool should be registered");
|
|
137
|
+
|
|
138
|
+
// Two concurrent interactive sessions (different cwds) arm wakeups. One
|
|
139
|
+
// session re-arming must not cancel the other session's pending wakeup.
|
|
140
|
+
await tool.execute(
|
|
141
|
+
"call-a",
|
|
142
|
+
{ delaySeconds: 5, prompt: "session A", reason: "poll A" },
|
|
143
|
+
undefined,
|
|
144
|
+
undefined,
|
|
145
|
+
{ cwd: "/tmp" },
|
|
146
|
+
);
|
|
147
|
+
await tool.execute(
|
|
148
|
+
"call-b",
|
|
149
|
+
{ delaySeconds: 5, prompt: "session B", reason: "poll B" },
|
|
150
|
+
undefined,
|
|
151
|
+
undefined,
|
|
152
|
+
{ cwd: process.cwd() },
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
mock.timers.tick(5000);
|
|
156
|
+
|
|
157
|
+
assert.equal(sent.length, 2, "distinct sessions must each keep their wakeup");
|
|
158
|
+
assert.deepEqual(
|
|
159
|
+
sent.map((s) => s.message.content).sort(),
|
|
160
|
+
["session A", "session B"],
|
|
161
|
+
);
|
|
162
|
+
} finally {
|
|
163
|
+
mock.timers.reset();
|
|
164
|
+
_resetInteractiveWakeupsForTest();
|
|
165
|
+
autoSession.reset();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { tmpdir } from "node:os";
|
|
21
|
+
import { verifyExpectedArtifact } from "../auto-recovery.ts";
|
|
21
22
|
import { recoverTimedOutUnit, type RecoveryContext } from "../auto-timeout-recovery.ts";
|
|
22
23
|
import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
|
|
23
24
|
import { test } from 'node:test';
|
|
@@ -167,6 +168,54 @@ function makeRecordingCtx() {
|
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
// ═══ plan-slice verification accepts completed task summaries ═══════════════
|
|
172
|
+
|
|
173
|
+
{
|
|
174
|
+
console.log("\n=== plan-slice verification accepts completed task summaries ===");
|
|
175
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-plan-slice-complete-summary-"));
|
|
176
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S05");
|
|
177
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
178
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
182
|
+
insertMilestone({ id: "M001", title: "Milestone", status: "active" });
|
|
183
|
+
insertSlice({ id: "S05", milestoneId: "M001", title: "Slice", status: "in_progress" });
|
|
184
|
+
insertTask({ id: "T01", milestoneId: "M001", sliceId: "S05", title: "Done 1", status: "complete" });
|
|
185
|
+
insertTask({ id: "T02", milestoneId: "M001", sliceId: "S05", title: "Done 2", status: "complete" });
|
|
186
|
+
insertTask({ id: "T03", milestoneId: "M001", sliceId: "S05", title: "Pending 1", status: "pending" });
|
|
187
|
+
insertTask({ id: "T04", milestoneId: "M001", sliceId: "S05", title: "Pending 2", status: "pending" });
|
|
188
|
+
writeFileSync(
|
|
189
|
+
join(sliceDir, "S05-PLAN.md"),
|
|
190
|
+
[
|
|
191
|
+
"# S05: Slice",
|
|
192
|
+
"",
|
|
193
|
+
"## Tasks",
|
|
194
|
+
"",
|
|
195
|
+
"- [x] **T01: Done 1** `est:10m`",
|
|
196
|
+
"- [x] **T02: Done 2** `est:10m`",
|
|
197
|
+
"- [ ] **T03: Pending 1** `est:10m`",
|
|
198
|
+
"- [ ] **T04: Pending 2** `est:10m`",
|
|
199
|
+
"",
|
|
200
|
+
].join("\n"),
|
|
201
|
+
"utf-8",
|
|
202
|
+
);
|
|
203
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n", "utf-8");
|
|
204
|
+
writeFileSync(join(tasksDir, "T02-SUMMARY.md"), "# T02 Summary\n", "utf-8");
|
|
205
|
+
writeFileSync(join(tasksDir, "T03-PLAN.md"), "# T03 Plan\n", "utf-8");
|
|
206
|
+
writeFileSync(join(tasksDir, "T04-PLAN.md"), "# T04 Plan\n", "utf-8");
|
|
207
|
+
|
|
208
|
+
assert.equal(
|
|
209
|
+
verifyExpectedArtifact("plan-slice", "M001/S05", base),
|
|
210
|
+
true,
|
|
211
|
+
"completed DB tasks should be accounted for by SUMMARY files",
|
|
212
|
+
);
|
|
213
|
+
} finally {
|
|
214
|
+
closeDatabase();
|
|
215
|
+
rmSync(base, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
170
219
|
// ═══ #1855: valid RecoveryContext does not crash ═════════════════════════════
|
|
171
220
|
|
|
172
221
|
{
|
|
@@ -312,7 +312,7 @@ describe("#2945 Bug 3: mergeAndExit must teardown worktree after successful merg
|
|
|
312
312
|
} as unknown as AutoSession;
|
|
313
313
|
|
|
314
314
|
const deps = {
|
|
315
|
-
|
|
315
|
+
mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
|
|
316
316
|
worktreeProjection: new WorktreeStateProjection(),
|
|
317
317
|
gitServiceFactory: () => ({}) as unknown as ReturnType<
|
|
318
318
|
import("../worktree-lifecycle.js").WorktreeLifecycleDeps["gitServiceFactory"]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* status-per-repo.test.ts — `/gsd status` per-repository git-health block (#818).
|
|
3
|
+
*
|
|
4
|
+
* Covers the parent-workspace addition to formatTextStatus:
|
|
5
|
+
* - parent mode with child repos → "Repositories:" section listing each
|
|
6
|
+
* - dirty vs clean markers
|
|
7
|
+
* - single-repo (project-mode) project → no Repositories section
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
import { formatTextStatus } from "../commands/handlers/core.ts";
|
|
18
|
+
import type { GSDState } from "../types.ts";
|
|
19
|
+
|
|
20
|
+
function gitInit(cwd: string): void {
|
|
21
|
+
execFileSync("git", ["init"], { cwd, stdio: "ignore" });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeBaseState(): GSDState {
|
|
25
|
+
return {
|
|
26
|
+
activeMilestone: null,
|
|
27
|
+
activeSlice: null,
|
|
28
|
+
activeTask: null,
|
|
29
|
+
phase: "planning",
|
|
30
|
+
recentDecisions: [],
|
|
31
|
+
blockers: [],
|
|
32
|
+
nextAction: "None",
|
|
33
|
+
registry: [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeParentPrefs(base: string, repos: Record<string, { path: string; role?: string }>): void {
|
|
38
|
+
const repoLines = Object.entries(repos)
|
|
39
|
+
.map(([id, cfg]) => ` ${id}:\n path: ${cfg.path}${cfg.role ? `\n role: ${cfg.role}` : ""}`)
|
|
40
|
+
.join("\n");
|
|
41
|
+
writeFileSync(
|
|
42
|
+
join(base, ".gsd", "PREFERENCES.md"),
|
|
43
|
+
`---\nversion: 1\nworkspace:\n mode: parent\n repositories:\n${repoLines}\n---\n`,
|
|
44
|
+
"utf-8",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test("formatTextStatus shows a Repositories section for a clean parent workspace", () => {
|
|
49
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-status-repo-clean-"));
|
|
50
|
+
try {
|
|
51
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
52
|
+
gitInit(base);
|
|
53
|
+
mkdirSync(join(base, "frontend"), { recursive: true });
|
|
54
|
+
gitInit(join(base, "frontend"));
|
|
55
|
+
writeParentPrefs(base, { frontend: { path: "frontend", role: "web UI" } });
|
|
56
|
+
|
|
57
|
+
const out = formatTextStatus(makeBaseState(), base);
|
|
58
|
+
assert.match(out, /Repositories:/);
|
|
59
|
+
assert.match(out, /✓ frontend — web UI \(clean\)/);
|
|
60
|
+
} finally {
|
|
61
|
+
rmSync(base, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("formatTextStatus marks a dirty child repo with ✗", () => {
|
|
66
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-status-repo-dirty-"));
|
|
67
|
+
try {
|
|
68
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
69
|
+
gitInit(base);
|
|
70
|
+
mkdirSync(join(base, "backend"), { recursive: true });
|
|
71
|
+
gitInit(join(base, "backend"));
|
|
72
|
+
writeParentPrefs(base, { backend: { path: "backend" } });
|
|
73
|
+
// Make the backend repo dirty.
|
|
74
|
+
writeFileSync(join(base, "backend", "README.md"), "# dirty\n", "utf-8");
|
|
75
|
+
|
|
76
|
+
const out = formatTextStatus(makeBaseState(), base);
|
|
77
|
+
assert.match(out, /✗ backend \(uncommitted changes\)/);
|
|
78
|
+
} finally {
|
|
79
|
+
rmSync(base, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("formatTextStatus omits the Repositories section for single-repo projects", () => {
|
|
84
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-status-repo-single-"));
|
|
85
|
+
try {
|
|
86
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
87
|
+
gitInit(base);
|
|
88
|
+
// No workspace config → project mode (default).
|
|
89
|
+
const out = formatTextStatus(makeBaseState(), base);
|
|
90
|
+
assert.doesNotMatch(out, /Repositories:/);
|
|
91
|
+
} finally {
|
|
92
|
+
rmSync(base, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
@@ -552,6 +552,41 @@ test("discuss-milestone dispatch keeps required headless milestone tools after t
|
|
|
552
552
|
assert.ok(!activeTools.includes("gsd_complete_milestone"));
|
|
553
553
|
});
|
|
554
554
|
|
|
555
|
+
test("#1157: validate-milestone dispatch surface removes generic write and bash tools", () => {
|
|
556
|
+
const result = buildMinimalAutoGsdToolSet([
|
|
557
|
+
"ask_user_questions",
|
|
558
|
+
"bash",
|
|
559
|
+
"bg_shell",
|
|
560
|
+
"edit",
|
|
561
|
+
"find",
|
|
562
|
+
"glob",
|
|
563
|
+
"grep",
|
|
564
|
+
"ls",
|
|
565
|
+
"read",
|
|
566
|
+
"subagent",
|
|
567
|
+
"write",
|
|
568
|
+
"gsd_exec",
|
|
569
|
+
"gsd_exec_search",
|
|
570
|
+
"gsd_resume",
|
|
571
|
+
"gsd_milestone_status",
|
|
572
|
+
"gsd_validate_milestone",
|
|
573
|
+
"gsd_reassess_roadmap",
|
|
574
|
+
], "validate-milestone");
|
|
575
|
+
|
|
576
|
+
assert.ok(result.includes("read"));
|
|
577
|
+
assert.ok(result.includes("find"));
|
|
578
|
+
assert.ok(result.includes("subagent"));
|
|
579
|
+
assert.ok(result.includes("gsd_validate_milestone"));
|
|
580
|
+
assert.ok(result.includes("gsd_milestone_status"));
|
|
581
|
+
assert.ok(result.includes("gsd_exec"));
|
|
582
|
+
assert.ok(result.includes("gsd_reassess_roadmap"));
|
|
583
|
+
assert.ok(!result.includes("bash"));
|
|
584
|
+
assert.ok(!result.includes("bg_shell"));
|
|
585
|
+
assert.ok(!result.includes("write"));
|
|
586
|
+
assert.ok(!result.includes("edit"));
|
|
587
|
+
assert.ok(!result.includes("ask_user_questions"));
|
|
588
|
+
});
|
|
589
|
+
|
|
555
590
|
test("scopeGsdWorkflowToolsForDispatch applies and restores per-unit skill visibility", () => {
|
|
556
591
|
const calls: Array<{ kind: "tools" | "skills"; value: string[] | undefined }> = [];
|
|
557
592
|
let activeTools = [
|
|
@@ -385,3 +385,66 @@ console.log('\n── Loop guard: block reasons tell model to respond in text
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
388
|
+
// Distinct-arg browser-automation calls are a legitimate UAT, not a loop (#1120)
|
|
389
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
390
|
+
|
|
391
|
+
console.log('\n── Loop guard: distinct-arg browser automation calls are not capped by per-tool count (#1120) ──');
|
|
392
|
+
|
|
393
|
+
{
|
|
394
|
+
// A browser-backed UAT legitimately calls one browser verb many times with
|
|
395
|
+
// DIFFERENT arguments (read a validation message, count staged edits, inspect
|
|
396
|
+
// a control card, …). Guard 1's identical-signature streak never trips because
|
|
397
|
+
// the args vary; Guard 2's arg-independent per-tool cap must NOT block it.
|
|
398
|
+
for (const toolName of ['browser_evaluate', 'browser_act', 'browser_find']) {
|
|
399
|
+
resetToolCallLoopGuard();
|
|
400
|
+
for (let i = 1; i <= 12; i++) {
|
|
401
|
+
const result = checkToolCallLoop(toolName, { script: `step ${i}` });
|
|
402
|
+
assert.ok(result.block === false, `distinct ${toolName} call ${i} should be allowed`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// MCP-prefixed browser tools canonicalize to `browser_*` and are exempt too.
|
|
407
|
+
resetToolCallLoopGuard();
|
|
408
|
+
for (let i = 1; i <= 12; i++) {
|
|
409
|
+
const result = checkToolCallLoop('mcp__agent-browser__browser_evaluate', { script: `step ${i}` });
|
|
410
|
+
assert.ok(result.block === false, `distinct MCP-prefixed browser_evaluate call ${i} should be allowed`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
415
|
+
// Identical-arg browser loops are STILL caught by Guard 1 (#1120)
|
|
416
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
417
|
+
|
|
418
|
+
console.log('\n── Loop guard: identical-arg browser loops are still caught by Guard 1 (#1120) ──');
|
|
419
|
+
|
|
420
|
+
{
|
|
421
|
+
resetToolCallLoopGuard();
|
|
422
|
+
for (let i = 1; i <= 4; i++) {
|
|
423
|
+
const result = checkToolCallLoop('browser_evaluate', { script: 'document.title' });
|
|
424
|
+
assert.ok(result.block === false, `identical browser_evaluate call ${i} should be allowed`);
|
|
425
|
+
}
|
|
426
|
+
const blocked = checkToolCallLoop('browser_evaluate', { script: 'document.title' });
|
|
427
|
+
assert.ok(blocked.block === true, '5th identical browser_evaluate call should be blocked by Guard 1');
|
|
428
|
+
assert.ok(blocked.reason?.includes('identical args'), 'identical browser loops should be caught by Guard 1, not Guard 2');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
432
|
+
// The browser exemption must NOT widen to other non-exempt tools (#1120)
|
|
433
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
434
|
+
|
|
435
|
+
console.log('\n── Loop guard: browser exemption does not widen to other tools (#1120) ──');
|
|
436
|
+
|
|
437
|
+
{
|
|
438
|
+
// Negative invariant: an arbitrary non-exempt, non-browser tool called past
|
|
439
|
+
// the default cap with varied args must still be blocked by Guard 2.
|
|
440
|
+
resetToolCallLoopGuard();
|
|
441
|
+
for (let i = 1; i <= 6; i++) {
|
|
442
|
+
const result = checkToolCallLoop('some_workflow_tool', { arg: `v${i}` });
|
|
443
|
+
assert.ok(result.block === false, `non-exempt tool call ${i} should be allowed`);
|
|
444
|
+
}
|
|
445
|
+
const blocked = checkToolCallLoop('some_workflow_tool', { arg: 'v7' });
|
|
446
|
+
assert.ok(blocked.block === true, '7th varied-arg non-exempt call must still trip Guard 2');
|
|
447
|
+
assert.ok(blocked.reason?.includes('repeated tool'), 'non-exempt overflow should be caught by Guard 2');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -268,7 +268,7 @@ test("#4934: every manifest declares a tools policy", () => {
|
|
|
268
268
|
});
|
|
269
269
|
|
|
270
270
|
test("#4934: tools.mode is one of the declared policies", () => {
|
|
271
|
-
const validModes = new Set(["all", "read-only", "planning", "planning-dispatch", "docs", "verification"]);
|
|
271
|
+
const validModes = new Set(["all", "read-only", "planning", "planning-dispatch", "docs", "verification", "workflow-only"]);
|
|
272
272
|
for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
|
|
273
273
|
const mode = (manifest as { tools: { mode: string } }).tools.mode;
|
|
274
274
|
assert.ok(
|
|
@@ -327,23 +327,71 @@ test("#5453: complete-milestone uses all tools so bash verification is not plann
|
|
|
327
327
|
}
|
|
328
328
|
});
|
|
329
329
|
|
|
330
|
-
test("#5731:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
330
|
+
test("#5731: complete-slice uses all tools so closeout verification is not planning-dispatch blocked", () => {
|
|
331
|
+
const manifest = UNIT_MANIFESTS["complete-slice"];
|
|
332
|
+
assert.strictEqual(manifest.tools.mode, "all");
|
|
333
|
+
const result = shouldBlockPlanningUnit(
|
|
334
|
+
"bash",
|
|
335
|
+
"go test -short -count=1 ./...",
|
|
336
|
+
process.cwd(),
|
|
337
|
+
"complete-slice",
|
|
338
|
+
manifest.tools,
|
|
339
|
+
);
|
|
340
|
+
assert.strictEqual(
|
|
341
|
+
result.block,
|
|
342
|
+
false,
|
|
343
|
+
`complete-slice must allow verification bash commands: ${result.reason}`,
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("#1157: validate-milestone uses workflow-only tools and blocks manual artifact writes", () => {
|
|
348
|
+
const manifest = UNIT_MANIFESTS["validate-milestone"];
|
|
349
|
+
assert.strictEqual(manifest.tools.mode, "workflow-only");
|
|
350
|
+
assert.deepEqual(resolveSubagentPermissionContract("validate-milestone"), {
|
|
351
|
+
allowed: true,
|
|
352
|
+
allowedSubagents: ["reviewer", "security", "tester"],
|
|
353
|
+
toolsMode: "workflow-only",
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const validationWrite = shouldBlockPlanningUnit(
|
|
357
|
+
"write",
|
|
358
|
+
".gsd/milestones/M001/M001-VALIDATION.md",
|
|
359
|
+
process.cwd(),
|
|
360
|
+
"validate-milestone",
|
|
361
|
+
manifest.tools,
|
|
362
|
+
);
|
|
363
|
+
assert.strictEqual(validationWrite.block, true);
|
|
364
|
+
assert.match(validationWrite.reason!, /workflow-only/);
|
|
365
|
+
assert.match(validationWrite.reason!, /direct artifact writes are disabled/);
|
|
366
|
+
|
|
367
|
+
const bash = shouldBlockPlanningUnit(
|
|
368
|
+
"bash",
|
|
369
|
+
"npm test",
|
|
370
|
+
process.cwd(),
|
|
371
|
+
"validate-milestone",
|
|
372
|
+
manifest.tools,
|
|
373
|
+
);
|
|
374
|
+
assert.strictEqual(bash.block, true);
|
|
375
|
+
assert.match(bash.reason!, /bash is not permitted/);
|
|
376
|
+
|
|
377
|
+
const validateTool = shouldBlockPlanningUnit(
|
|
378
|
+
"gsd_validate_milestone",
|
|
379
|
+
"",
|
|
380
|
+
process.cwd(),
|
|
381
|
+
"validate-milestone",
|
|
382
|
+
manifest.tools,
|
|
383
|
+
);
|
|
384
|
+
assert.strictEqual(validateTool.block, false);
|
|
385
|
+
|
|
386
|
+
const reviewer = shouldBlockPlanningUnit(
|
|
387
|
+
"subagent",
|
|
388
|
+
"",
|
|
389
|
+
process.cwd(),
|
|
390
|
+
"validate-milestone",
|
|
391
|
+
manifest.tools,
|
|
392
|
+
["reviewer"],
|
|
393
|
+
);
|
|
394
|
+
assert.strictEqual(reviewer.block, false, reviewer.reason);
|
|
347
395
|
});
|
|
348
396
|
|
|
349
397
|
test("#5843: run-uat uses verification tools policy so build/test commands can run", () => {
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import test from "node:test";
|
|
9
9
|
import assert from "node:assert/strict";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
10
12
|
|
|
11
13
|
import {
|
|
12
14
|
KNOWN_UNIT_TYPES,
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
SECTION_CLOSE_GATE_UNIT_TYPES,
|
|
16
18
|
getUnitDescriptor,
|
|
17
19
|
getUnitPhaseChain,
|
|
20
|
+
getUnitPromptTemplate,
|
|
18
21
|
} from "../unit-registry.ts";
|
|
19
22
|
import {
|
|
20
23
|
AUTO_UNIT_SCOPED_TOOLS,
|
|
@@ -96,6 +99,37 @@ const EXPECTED_PHASE_CHAINS: Record<string, string[] | undefined> = {
|
|
|
96
99
|
"no-such-unit": undefined,
|
|
97
100
|
};
|
|
98
101
|
|
|
102
|
+
const EXPECTED_DIRECT_PROMPT_TEMPLATES: Record<string, string> = {
|
|
103
|
+
"research-milestone": "research-milestone",
|
|
104
|
+
"plan-milestone": "plan-milestone",
|
|
105
|
+
"validate-milestone": "validate-milestone",
|
|
106
|
+
"complete-milestone": "complete-milestone",
|
|
107
|
+
"research-slice": "research-slice",
|
|
108
|
+
"plan-slice": "plan-slice",
|
|
109
|
+
"refine-slice": "refine-slice",
|
|
110
|
+
"replan-slice": "replan-slice",
|
|
111
|
+
"complete-slice": "complete-slice",
|
|
112
|
+
"reassess-roadmap": "reassess-roadmap",
|
|
113
|
+
"execute-task": "execute-task",
|
|
114
|
+
"reactive-execute": "reactive-execute",
|
|
115
|
+
"run-uat": "run-uat",
|
|
116
|
+
"gate-evaluate": "gate-evaluate",
|
|
117
|
+
"rewrite-docs": "rewrite-docs",
|
|
118
|
+
"workflow-preferences": "guided-workflow-preferences",
|
|
119
|
+
"discuss-project": "guided-discuss-project",
|
|
120
|
+
"discuss-requirements": "guided-discuss-requirements",
|
|
121
|
+
"research-decision": "guided-research-decision",
|
|
122
|
+
"research-project": "guided-research-project",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const EXPECTED_UNDECLARED_PROMPT_TEMPLATE_TYPES = [
|
|
126
|
+
"discuss-milestone",
|
|
127
|
+
"discuss-slice",
|
|
128
|
+
"execute-task-simple",
|
|
129
|
+
"triage-captures",
|
|
130
|
+
"quick-task",
|
|
131
|
+
];
|
|
132
|
+
|
|
99
133
|
// ─── Derived-view parity ──────────────────────────────────────────────────
|
|
100
134
|
|
|
101
135
|
test("KNOWN_UNIT_TYPES derives exactly the pre-registry list, in order", () => {
|
|
@@ -134,6 +168,28 @@ test("phaseChainForUnit matches the pre-registry switch for every known input",
|
|
|
134
168
|
}
|
|
135
169
|
});
|
|
136
170
|
|
|
171
|
+
test("direct prompt-template associations live on the Unit Registry", () => {
|
|
172
|
+
for (const [unitType, promptTemplate] of Object.entries(EXPECTED_DIRECT_PROMPT_TEMPLATES)) {
|
|
173
|
+
assert.equal(
|
|
174
|
+
getUnitPromptTemplate(unitType),
|
|
175
|
+
promptTemplate,
|
|
176
|
+
`prompt template for ${unitType}`,
|
|
177
|
+
);
|
|
178
|
+
assert.ok(
|
|
179
|
+
existsSync(join(process.cwd(), "src/resources/extensions/gsd/prompts", `${promptTemplate}.md`)),
|
|
180
|
+
`prompt template file must exist for ${unitType}: ${promptTemplate}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const unitType of EXPECTED_UNDECLARED_PROMPT_TEMPLATE_TYPES) {
|
|
185
|
+
assert.equal(
|
|
186
|
+
getUnitPromptTemplate(unitType),
|
|
187
|
+
undefined,
|
|
188
|
+
`${unitType} prompt association is conditional or not verified yet`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
137
193
|
test("AUTO_UNIT_SCOPED_TOOLS mirrors each contract's allowed tools", () => {
|
|
138
194
|
for (const [unitType, contract] of Object.entries(UNIT_TOOL_CONTRACTS)) {
|
|
139
195
|
assert.deepEqual(AUTO_UNIT_SCOPED_TOOLS[unitType], contract.allowedGsdTools);
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
formatExecuteTaskRecoveryStatus,
|
|
7
7
|
inspectExecuteTaskDurability,
|
|
8
8
|
isInFlightRuntimePhase,
|
|
9
|
+
readUnitHarnessAbort,
|
|
9
10
|
readUnitRuntimeRecord,
|
|
11
|
+
recordUnitHarnessAbort,
|
|
10
12
|
writeUnitRuntimeRecord,
|
|
11
13
|
} from "../unit-runtime.ts";
|
|
12
14
|
import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
|
|
@@ -41,6 +43,34 @@ console.log("\n=== runtime record write/read/update ===");
|
|
|
41
43
|
assert.deepStrictEqual(loaded!.phase, "wrapup-warning-sent", "updated phase readable");
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
console.log("\n=== runtime harness abort preservation and explicit clear ===");
|
|
47
|
+
{
|
|
48
|
+
const startedAt = 3000;
|
|
49
|
+
recordUnitHarnessAbort(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt, {
|
|
50
|
+
kind: "tool-error",
|
|
51
|
+
reason: "Tool execution failed before the unit could complete its gate evaluation.",
|
|
52
|
+
toolName: "browser_click",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const preserved = writeUnitRuntimeRecord(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt, {
|
|
56
|
+
phase: "wrapup-warning-sent",
|
|
57
|
+
});
|
|
58
|
+
assert.equal(preserved.harnessAbort?.kind, "tool-error", "ordinary same-run updates preserve harness abort");
|
|
59
|
+
|
|
60
|
+
const cleared = writeUnitRuntimeRecord(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt, {
|
|
61
|
+
phase: "recovered",
|
|
62
|
+
harnessAbort: undefined,
|
|
63
|
+
});
|
|
64
|
+
assert.equal(cleared.harnessAbort, undefined, "explicit clear removes stale harness abort");
|
|
65
|
+
assert.equal(
|
|
66
|
+
readUnitHarnessAbort(base, "gate-evaluate", "M100/S02/gates+Q3", startedAt),
|
|
67
|
+
null,
|
|
68
|
+
"cleared harness abort no longer blocks result saves",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
clearUnitRuntimeRecord(base, "gate-evaluate", "M100/S02/gates+Q3");
|
|
72
|
+
}
|
|
73
|
+
|
|
44
74
|
console.log("\n=== execute-task durability inspection ===");
|
|
45
75
|
{
|
|
46
76
|
let status = await inspectExecuteTaskDurability(base, "M100/S02/T09");
|