@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
|
@@ -16,6 +16,7 @@ import { GSDError, GSD_IO_ERROR } from "./errors.js";
|
|
|
16
16
|
const SEQ_PREFIX_RE = /^(\d+)-/;
|
|
17
17
|
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
18
18
|
import { gsdRoot } from "./paths.js";
|
|
19
|
+
import { redactSecrets } from "./redact-secrets.js";
|
|
19
20
|
import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js";
|
|
20
21
|
import { isUnifiedAuditEnabled } from "./uok/audit-toggle.js";
|
|
21
22
|
|
|
@@ -127,7 +128,9 @@ export function saveActivityLog(
|
|
|
127
128
|
const fd = openSync(filePath, "w");
|
|
128
129
|
try {
|
|
129
130
|
for (const entry of entries) {
|
|
130
|
-
|
|
131
|
+
// Redact secret-shaped substrings before persisting; .gsd/ is skipped by
|
|
132
|
+
// the secret scanner, so raw LLM/tool content could otherwise leak keys.
|
|
133
|
+
writeSync(fd, redactSecrets(JSON.stringify(entry)) + "\n");
|
|
131
134
|
}
|
|
132
135
|
} finally {
|
|
133
136
|
closeSync(fd);
|
|
@@ -45,7 +45,6 @@ import { resolveWorktreeProjectRoot } from "./worktree-root.js";
|
|
|
45
45
|
import { hasImplementationArtifacts } from "./milestone-implementation-evidence.js";
|
|
46
46
|
import { loadAllCaptures, loadPendingCaptures } from "./captures.js";
|
|
47
47
|
import { proveMilestoneCloseout } from "./milestone-closeout-proof.js";
|
|
48
|
-
|
|
49
48
|
/**
|
|
50
49
|
* Optional override for the legacy roadmap parser used by verifyExpectedArtifact.
|
|
51
50
|
* Production leaves this null so the real parseLegacyRoadmap runs; tests inject
|
|
@@ -72,6 +71,11 @@ function parseRoadmapForRecovery(content: string): ReturnType<NonNullable<typeof
|
|
|
72
71
|
return parseLegacyRoadmap(content) as unknown as ReturnType<NonNullable<typeof _roadmapParserFn>>;
|
|
73
72
|
}
|
|
74
73
|
|
|
74
|
+
/** Slice count for plan-milestone verification; shared by scoped and legacy paths. */
|
|
75
|
+
export function countPlanMilestoneRoadmapSlices(content: string): number {
|
|
76
|
+
return parseRoadmapForRecovery(content).slices.length;
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
export function diagnoseWorktreeIntegrityFailure(basePath: string): string | null {
|
|
76
80
|
if (!isGsdWorktreePath(basePath)) return null;
|
|
77
81
|
if (!existsSync(basePath)) {
|
|
@@ -336,8 +340,7 @@ export function verifyExpectedArtifact(
|
|
|
336
340
|
|
|
337
341
|
if (unitType === "plan-milestone") {
|
|
338
342
|
try {
|
|
339
|
-
|
|
340
|
-
if (roadmap.slices.length === 0) {
|
|
343
|
+
if (countPlanMilestoneRoadmapSlices(readFileSync(absPath, "utf-8")) === 0) {
|
|
341
344
|
logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap has zero slices at ${absPath}`);
|
|
342
345
|
return false;
|
|
343
346
|
}
|
|
@@ -353,6 +356,19 @@ export function verifyExpectedArtifact(
|
|
|
353
356
|
try {
|
|
354
357
|
let taskIds: string[] | null = null;
|
|
355
358
|
let dbPrimary = false;
|
|
359
|
+
const planContent = readFileSync(absPath, "utf-8");
|
|
360
|
+
let parsedTaskIds: string[] | null = null;
|
|
361
|
+
const getParsedTaskIds = (): string[] => {
|
|
362
|
+
if (parsedTaskIds) return parsedTaskIds;
|
|
363
|
+
parsedTaskIds = parseLegacyPlan(planContent).tasks.map((t: { id: string }) => t.id);
|
|
364
|
+
return parsedTaskIds;
|
|
365
|
+
};
|
|
366
|
+
const tasksBlockMatch = planContent.match(/<tasks>([\s\S]*?)<\/tasks>/i);
|
|
367
|
+
const tasksBlock = tasksBlockMatch?.[1] ?? "";
|
|
368
|
+
const hasEmbeddedTaskEntries =
|
|
369
|
+
tasksBlock.length > 0 &&
|
|
370
|
+
(/^\s*- \[[xX ]\] \*\*T\d+/m.test(tasksBlock) ||
|
|
371
|
+
/^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(tasksBlock));
|
|
356
372
|
if (isDbAvailable()) {
|
|
357
373
|
const refreshed = refreshWorkflowDatabaseFromDisk();
|
|
358
374
|
if (refreshed) {
|
|
@@ -365,24 +381,24 @@ export function verifyExpectedArtifact(
|
|
|
365
381
|
}
|
|
366
382
|
|
|
367
383
|
if (!taskIds) {
|
|
368
|
-
const planContent = readFileSync(absPath, "utf-8");
|
|
369
384
|
const hasCheckboxTask = /^\s*- \[[xX ]\] \*\*T\d+/m.test(planContent);
|
|
370
385
|
const hasHeadingTask = /^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
|
|
371
386
|
if (!hasCheckboxTask && !hasHeadingTask) {
|
|
372
387
|
logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
|
|
373
388
|
return false;
|
|
374
389
|
}
|
|
375
|
-
const
|
|
376
|
-
if (
|
|
390
|
+
const parsedIds = getParsedTaskIds();
|
|
391
|
+
if (parsedIds.length > 0) taskIds = parsedIds;
|
|
377
392
|
}
|
|
378
393
|
|
|
379
|
-
if (taskIds && taskIds.length > 0) {
|
|
394
|
+
if (taskIds && taskIds.length > 0 && !hasEmbeddedTaskEntries) {
|
|
380
395
|
const tasksDir = join(dirname(absPath), "tasks");
|
|
381
396
|
if (existsSync(tasksDir)) {
|
|
382
397
|
for (const tid of taskIds) {
|
|
383
398
|
const taskPlanFile = join(tasksDir, `${tid}-PLAN.md`);
|
|
384
|
-
|
|
385
|
-
|
|
399
|
+
const taskSummaryFile = join(tasksDir, `${tid}-SUMMARY.md`);
|
|
400
|
+
if (!existsSync(taskPlanFile) && !existsSync(taskSummaryFile)) {
|
|
401
|
+
logWarning("recovery", `verify-fail ${unitType} ${unitId}: task artifact missing for ${tid}`);
|
|
386
402
|
return false;
|
|
387
403
|
}
|
|
388
404
|
}
|
|
@@ -79,13 +79,14 @@ export async function restorePreflightStashOrStop(
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
* Run a milestone merge
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
82
|
+
* Run a milestone merge through Worktree Lifecycle's guarded merge option,
|
|
83
|
+
* which surrounds the inner merge with preflight stash + always-on postflight
|
|
84
|
+
* pop. The previous closeout code popped the stash only after a successful
|
|
85
|
+
* merge, which leaked `gsd-preflight-stash:M00x:*` entries whenever
|
|
86
|
+
* `mergeAndExit` threw — leaving the user's pre-merge working tree silently
|
|
87
|
+
* stashed away after a merge-conflict or other merge error. Lifecycle now
|
|
88
|
+
* restores the stash on every attempted merge path, then this adapter surfaces
|
|
89
|
+
* the merge or stash failure (in priority order) as the loop's stop reason.
|
|
89
90
|
*
|
|
90
91
|
* Returns a `break` action when auto-mode must stop, or `null` when the merge
|
|
91
92
|
* succeeded and the stash (if any) was restored cleanly.
|
|
@@ -97,66 +98,45 @@ export async function _runMilestoneMergeWithStashRestore(
|
|
|
97
98
|
): Promise<{ action: "break"; reason: string } | null> {
|
|
98
99
|
const { ctx, pi, s, deps } = ic;
|
|
99
100
|
|
|
100
|
-
const
|
|
101
|
-
|
|
101
|
+
const projectRoot = s.originalBasePath || s.basePath;
|
|
102
|
+
const mergeResult = deps.lifecycle.exitMilestone(
|
|
102
103
|
milestoneId,
|
|
103
|
-
|
|
104
|
+
{
|
|
105
|
+
merge: true,
|
|
106
|
+
guardedMerge: {
|
|
107
|
+
projectRoot,
|
|
108
|
+
preflightCleanRoot: deps.preflightCleanRoot,
|
|
109
|
+
postflightPopStash: deps.postflightPopStash,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
ctx.ui,
|
|
104
113
|
);
|
|
105
|
-
|
|
106
|
-
|
|
114
|
+
|
|
115
|
+
if (mergeResult.ok) {
|
|
116
|
+
await markMilestoneMergedAndRebuild(s);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (mergeResult.reason === "postflight-stash-restore-failed") {
|
|
121
|
+
await markMilestoneMergedAndRebuild(s);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (mergeResult.reason === "preflight-dirty-overlap" || mergeResult.reason === "preflight-unmerged-conflicts") {
|
|
125
|
+
const reason = mergeResult.reason === "preflight-unmerged-conflicts"
|
|
107
126
|
? `Pre-merge unresolved Git conflicts block milestone ${milestoneId}`
|
|
108
127
|
: `Pre-merge dirty working tree overlaps milestone ${milestoneId}`;
|
|
109
128
|
await deps.stopAuto(ctx, pi, reason, {
|
|
110
129
|
preserveCompletedMilestoneBranch: true,
|
|
111
130
|
preserveCloseoutTranscript: options.preserveCloseoutTranscript,
|
|
112
131
|
});
|
|
113
|
-
return {
|
|
114
|
-
action: "break",
|
|
115
|
-
reason: preflight.blockedReason === "unmerged-conflicts"
|
|
116
|
-
? "preflight-unmerged-conflicts"
|
|
117
|
-
: "preflight-dirty-overlap",
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let mergeError: unknown = null;
|
|
122
|
-
const exitResult = deps.lifecycle.exitMilestone(
|
|
123
|
-
milestoneId,
|
|
124
|
-
{ merge: true },
|
|
125
|
-
ctx.ui,
|
|
126
|
-
);
|
|
127
|
-
if (exitResult.ok) {
|
|
128
|
-
s.milestoneMergedInPhases = true;
|
|
129
|
-
try {
|
|
130
|
-
const projectRoot = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
131
|
-
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
132
|
-
await rebuildMarkdownProjectionsFromDb(projectRoot);
|
|
133
|
-
} catch (err) {
|
|
134
|
-
logWarning(
|
|
135
|
-
"engine",
|
|
136
|
-
`markdown projection rebuild after milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
mergeError = exitResult.cause ?? new Error(`exit ${exitResult.reason}`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Always attempt to restore the stashed working tree, even on merge error.
|
|
144
|
-
// postflightPopStash itself does not throw; failures surface via the
|
|
145
|
-
// PostflightResult.needsManualRecovery flag.
|
|
146
|
-
let stashResult: PostflightResult | null = null;
|
|
147
|
-
if (preflight.stashPushed) {
|
|
148
|
-
stashResult = deps.postflightPopStash(
|
|
149
|
-
s.originalBasePath || s.basePath,
|
|
150
|
-
milestoneId,
|
|
151
|
-
preflight.stashMarker,
|
|
152
|
-
ctx.ui.notify.bind(ctx.ui),
|
|
153
|
-
);
|
|
132
|
+
return { action: "break", reason: mergeResult.reason };
|
|
154
133
|
}
|
|
155
134
|
|
|
156
135
|
// Merge failure takes priority over stash recovery — the merge is the
|
|
157
136
|
// authoritative gate. If the stash also needed manual recovery, the user
|
|
158
137
|
// already saw the postflightPopStash notify above.
|
|
159
|
-
if (
|
|
138
|
+
if (mergeResult.reason === "merge-conflict") {
|
|
139
|
+
const mergeError = mergeResult.cause;
|
|
160
140
|
if (mergeError instanceof MergeConflictError) {
|
|
161
141
|
// A merge conflict is a recoverable human checkpoint, not an
|
|
162
142
|
// infrastructure failure — the user resolves the conflict and runs
|
|
@@ -173,6 +153,10 @@ export async function _runMilestoneMergeWithStashRestore(
|
|
|
173
153
|
});
|
|
174
154
|
return { action: "break", reason: "merge-conflict" };
|
|
175
155
|
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (mergeResult.reason === "merge-failed" || mergeResult.reason === "merge-conflict") {
|
|
159
|
+
const mergeError = mergeResult.cause;
|
|
176
160
|
logError("engine", "Milestone merge failed with non-conflict error", {
|
|
177
161
|
milestone: milestoneId,
|
|
178
162
|
error: String(mergeError),
|
|
@@ -190,12 +174,26 @@ export async function _runMilestoneMergeWithStashRestore(
|
|
|
190
174
|
return { action: "break", reason: "merge-failed" };
|
|
191
175
|
}
|
|
192
176
|
|
|
193
|
-
if (
|
|
194
|
-
return stopOnPostflightRecoveryNeeded(ic,
|
|
177
|
+
if (mergeResult.postflight) {
|
|
178
|
+
return stopOnPostflightRecoveryNeeded(ic, mergeResult.postflight, milestoneId);
|
|
195
179
|
}
|
|
196
180
|
return null;
|
|
197
181
|
}
|
|
198
182
|
|
|
183
|
+
async function markMilestoneMergedAndRebuild(s: AutoSession): Promise<void> {
|
|
184
|
+
s.milestoneMergedInPhases = true;
|
|
185
|
+
try {
|
|
186
|
+
const rebuildBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
187
|
+
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
188
|
+
await rebuildMarkdownProjectionsFromDb(rebuildBasePath);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
logWarning(
|
|
191
|
+
"engine",
|
|
192
|
+
`markdown projection rebuild after milestone merge failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
199
197
|
export async function _runMilestoneMergeOnceWithStashRestore(
|
|
200
198
|
ic: IterationContext,
|
|
201
199
|
milestoneId: string,
|
|
@@ -373,6 +373,14 @@ export async function runFinalize(
|
|
|
373
373
|
files: leak.files.map((file) => ({ path: file.path, status: file.status })),
|
|
374
374
|
});
|
|
375
375
|
ctx.ui.notify(message, "error");
|
|
376
|
+
try {
|
|
377
|
+
deps.checkpointWorkflowDatabase?.();
|
|
378
|
+
} catch (err) {
|
|
379
|
+
debugLog("autoLoop", {
|
|
380
|
+
phase: "root-write-leak-checkpoint-failed",
|
|
381
|
+
error: err instanceof Error ? err.message : String(err),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
376
384
|
await deps.stopAuto(ctx, pi, "Root-write leak during isolated auto-mode", {
|
|
377
385
|
preserveCompletedMilestoneBranch: true,
|
|
378
386
|
});
|
|
@@ -731,7 +731,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
|
731
731
|
return new GitServiceImpl(basePath, gitConfig);
|
|
732
732
|
},
|
|
733
733
|
worktreeProjection: new WorktreeStateProjection(),
|
|
734
|
-
|
|
734
|
+
mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
|
|
735
735
|
});
|
|
736
736
|
}
|
|
737
737
|
|
|
@@ -26,7 +26,7 @@ import { debugLog } from "../debug-logger.js";
|
|
|
26
26
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
27
27
|
import { resolveAutoSupervisorConfig } from "../preferences.js";
|
|
28
28
|
import { readUnitRuntimeRecord, type AutoUnitRuntimeRecord } from "../unit-runtime.js";
|
|
29
|
-
import { consumeAutoWakeup } from "./schedule-wakeup.js";
|
|
29
|
+
import { clearAutoWakeup, consumeAutoWakeup } from "./schedule-wakeup.js";
|
|
30
30
|
import { applyUnitSkillVisibility } from "../skill-scope.js";
|
|
31
31
|
|
|
32
32
|
const UNIT_FAILSAFE_BUFFER_MS = 30_000;
|
|
@@ -248,7 +248,7 @@ export async function runUnit(
|
|
|
248
248
|
((supervisor.idle_timeout_minutes ?? 10) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS,
|
|
249
249
|
);
|
|
250
250
|
let unitTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
251
|
-
let result: UnitResult;
|
|
251
|
+
let result: UnitResult = { status: "cancelled" };
|
|
252
252
|
try {
|
|
253
253
|
const awaitAgentEndWithTimeout = (agentEndPromise: Promise<UnitResult>): Promise<UnitResult> => {
|
|
254
254
|
const timeoutResult = new Promise<UnitResult>((resolve) => {
|
|
@@ -360,6 +360,9 @@ export async function runUnit(
|
|
|
360
360
|
}
|
|
361
361
|
} finally {
|
|
362
362
|
if (unitTimeoutHandle) clearTimeout(unitTimeoutHandle);
|
|
363
|
+
if (result.status !== "completed") {
|
|
364
|
+
clearAutoWakeup(s.basePath, unitType, unitId);
|
|
365
|
+
}
|
|
363
366
|
ctx.ui.setWorkingMessage?.(undefined);
|
|
364
367
|
setVisibleSkills?.(undefined);
|
|
365
368
|
}
|
|
@@ -35,6 +35,14 @@ export function consumeAutoWakeup(
|
|
|
35
35
|
return wakeup;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function clearAutoWakeup(
|
|
39
|
+
basePath: string,
|
|
40
|
+
unitType: string,
|
|
41
|
+
unitId: string,
|
|
42
|
+
): void {
|
|
43
|
+
pendingWakeups.delete(wakeupKey(basePath, unitType, unitId));
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
export function _resetAutoWakeupsForTest(): void {
|
|
39
47
|
pendingWakeups.clear();
|
|
40
48
|
}
|
|
@@ -838,7 +838,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
838
838
|
},
|
|
839
839
|
},
|
|
840
840
|
{
|
|
841
|
-
name: "uat-verdict-gate (non-PASS
|
|
841
|
+
name: "uat-verdict-gate (non-PASS observed; closeout enforces)",
|
|
842
842
|
match: async ({ mid, basePath, prefs }) => {
|
|
843
843
|
// Only applies when UAT dispatch is enabled
|
|
844
844
|
if (!prefs?.uat_dispatch) return null;
|
|
@@ -852,9 +852,9 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
852
852
|
const { verdict, uatType } = result;
|
|
853
853
|
|
|
854
854
|
if (!isAcceptableUatVerdict(verdict, uatType)) {
|
|
855
|
-
//
|
|
856
|
-
// so follow-up slices can remediate, while
|
|
857
|
-
// enforces manual UAT PASS sign-off before closure.
|
|
855
|
+
// Observe non-PASS verdicts without hard-stopping auto-mode. Allow
|
|
856
|
+
// progression so follow-up slices can remediate, while
|
|
857
|
+
// complete-milestone still enforces manual UAT PASS sign-off before closure.
|
|
858
858
|
continue;
|
|
859
859
|
}
|
|
860
860
|
}
|
|
@@ -1130,9 +1130,21 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
1130
1130
|
// call site.
|
|
1131
1131
|
const sliceContextFile = resolveSliceFile(basePath, mid, state.activeSlice.id, "CONTEXT");
|
|
1132
1132
|
if (sliceContextFile && existsSync(sliceContextFile)) return null; // discussion already done, proceed
|
|
1133
|
+
|
|
1134
|
+
const closedSliceIds = getClosedSliceIds(mid);
|
|
1135
|
+
const justClosedSliceId = closedSliceIds[closedSliceIds.length - 1];
|
|
1136
|
+
let priorVerdictWarning = "";
|
|
1137
|
+
if (justClosedSliceId) {
|
|
1138
|
+
const prior = await readUatGateVerdict(basePath, mid, justClosedSliceId);
|
|
1139
|
+
if (prior && !isAcceptableUatVerdict(prior.verdict, prior.uatType)) {
|
|
1140
|
+
priorVerdictWarning =
|
|
1141
|
+
` Note: the slice just closed (${justClosedSliceId}) recorded a non-PASS UAT verdict (${prior.verdict.toUpperCase()}); review before continuing.`;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1133
1145
|
return {
|
|
1134
1146
|
action: "stop" as const,
|
|
1135
|
-
reason: `Slice ${state.activeSlice.id} requires discussion before planning (require_slice_discussion is enabled). Run /gsd discuss to discuss this slice, then /gsd auto to resume
|
|
1147
|
+
reason: `Slice ${state.activeSlice.id} requires discussion before planning (require_slice_discussion is enabled). Run /gsd discuss to discuss this slice, then /gsd auto to resume.${priorVerdictWarning}`,
|
|
1136
1148
|
level: "warning" as const,
|
|
1137
1149
|
};
|
|
1138
1150
|
},
|
|
@@ -409,7 +409,13 @@ export function resolvePreferredModelConfig(
|
|
|
409
409
|
availableModelIds?: string[],
|
|
410
410
|
preferredModelId?: string,
|
|
411
411
|
): PreferredModelConfig | undefined {
|
|
412
|
-
const explicitConfig = resolveModelWithFallbacksForUnit(
|
|
412
|
+
const explicitConfig = resolveModelWithFallbacksForUnit(
|
|
413
|
+
unitType,
|
|
414
|
+
basePath,
|
|
415
|
+
availableModelIds,
|
|
416
|
+
preferredModelId,
|
|
417
|
+
true,
|
|
418
|
+
);
|
|
413
419
|
if (explicitConfig) {
|
|
414
420
|
return {
|
|
415
421
|
...explicitConfig,
|
|
@@ -422,26 +428,55 @@ export function resolvePreferredModelConfig(
|
|
|
422
428
|
if (!isAutoMode) return undefined;
|
|
423
429
|
|
|
424
430
|
const routingConfig = resolveDynamicRoutingConfig();
|
|
425
|
-
if (!routingConfig.enabled
|
|
431
|
+
if (!routingConfig.enabled) return undefined;
|
|
426
432
|
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
433
|
+
// Only an explicit flat-rate opt-out suppresses synthesized routing.
|
|
434
|
+
// `dynamic_routing.enabled: true` is user intent to run routing even when
|
|
435
|
+
// the start provider is a flat-rate subscription. Applied ahead of both the
|
|
436
|
+
// tier-pin and profile-derived synthesis paths so the opt-out is uniform.
|
|
430
437
|
if (
|
|
431
|
-
|
|
438
|
+
routingConfig.allow_flat_rate_providers === false &&
|
|
432
439
|
autoModeStartModel &&
|
|
433
440
|
isFlatRateProvider(autoModeStartModel.provider, autoModeStartModel.flatRateCtx)
|
|
434
441
|
) {
|
|
435
442
|
return undefined;
|
|
436
443
|
}
|
|
437
444
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (
|
|
445
|
+
// With explicit `tier_models` pins, synthesize a heavy-tier ceiling that
|
|
446
|
+
// dynamic routing downgrades from per unit.
|
|
447
|
+
if (routingConfig.tier_models) {
|
|
448
|
+
const ceilingModel = routingConfig.tier_models.heavy
|
|
449
|
+
?? (autoModeStartModel ? `${autoModeStartModel.provider}/${autoModeStartModel.id}` : undefined);
|
|
450
|
+
if (!ceilingModel) return undefined;
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
primary: ceilingModel,
|
|
454
|
+
fallbacks: [],
|
|
455
|
+
source: "synthesized",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// No `tier_models` pins: fall back to the token-profile's per-phase model so
|
|
460
|
+
// profile tier resolution survives. `skipProfileDefaults` is left false here
|
|
461
|
+
// (unlike the explicit-detection call above) precisely because we WANT the
|
|
462
|
+
// profile defaults this time — they are the routing ceiling, not a hard
|
|
463
|
+
// user selection. Labeling the result `synthesized` (not `explicit`) keeps
|
|
464
|
+
// dynamic routing and fallbacks enabled downstream. Without this branch,
|
|
465
|
+
// `resolvePreferredModelConfig` returns undefined for profile-only configs
|
|
466
|
+
// and auto dispatch bleeds the start model onto every unit, silently
|
|
467
|
+
// discarding the profile (#1115: routing/fallbacks disabled when a
|
|
468
|
+
// token_profile is set but no tier_models are pinned).
|
|
469
|
+
const profileConfig = resolveModelWithFallbacksForUnit(
|
|
470
|
+
unitType,
|
|
471
|
+
basePath,
|
|
472
|
+
availableModelIds,
|
|
473
|
+
preferredModelId,
|
|
474
|
+
false,
|
|
475
|
+
);
|
|
476
|
+
if (!profileConfig) return undefined;
|
|
441
477
|
|
|
442
478
|
return {
|
|
443
|
-
|
|
444
|
-
fallbacks: [],
|
|
479
|
+
...profileConfig,
|
|
445
480
|
source: "synthesized",
|
|
446
481
|
};
|
|
447
482
|
}
|
|
@@ -647,15 +682,11 @@ export async function selectAndApplyModel(
|
|
|
647
682
|
}
|
|
648
683
|
}
|
|
649
684
|
|
|
650
|
-
// Disable routing for flat-rate providers
|
|
651
|
-
//
|
|
652
|
-
// model provides no cost benefit — it only degrades quality.
|
|
685
|
+
// Disable routing for flat-rate providers only when the user explicitly
|
|
686
|
+
// opts out with dynamic_routing.allow_flat_rate_providers: false.
|
|
653
687
|
// Fail-closed: if primary model can't be resolved, fall back to
|
|
654
688
|
// provider-level signals rather than allowing unwanted downgrades.
|
|
655
|
-
|
|
656
|
-
// claude-code subscribers can still get intelligent per-task selection
|
|
657
|
-
// across their subscription (#4386).
|
|
658
|
-
if (routingConfig.enabled && !routingConfig.allow_flat_rate_providers) {
|
|
689
|
+
if (routingConfig.enabled && routingConfig.allow_flat_rate_providers === false) {
|
|
659
690
|
const primaryModel = resolveModelId(modelConfig.primary, routingEligibleModels, ctx.model?.provider);
|
|
660
691
|
if (primaryModel) {
|
|
661
692
|
const primaryFlatRateCtx = buildFlatRateContext(primaryModel.provider, ctx, prefs);
|
|
@@ -20,14 +20,15 @@ import {
|
|
|
20
20
|
relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile,
|
|
21
21
|
} from "./paths.js";
|
|
22
22
|
import { resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
23
|
+
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
23
24
|
import { isContextModeEnabled } from "./preferences-types.js";
|
|
24
25
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
25
26
|
import type { GSDState, InlineLevel } from "./types.js";
|
|
26
27
|
import type { GSDPreferences } from "./preferences.js";
|
|
27
|
-
import { join, basename, relative } from "node:path";
|
|
28
|
+
import { join, basename, relative, sep } from "node:path";
|
|
28
29
|
import { existsSync } from "node:fs";
|
|
29
30
|
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary, type MinimalModelRegistry } from "./context-budget.js";
|
|
30
|
-
import { getPendingGates, getPendingGatesForTurn } from "./gsd-db.js";
|
|
31
|
+
import { getGateResults, getPendingGates, getPendingGatesForTurn } from "./gsd-db.js";
|
|
31
32
|
import {
|
|
32
33
|
GATE_REGISTRY,
|
|
33
34
|
assertGateCoverage,
|
|
@@ -393,6 +394,37 @@ function formatExecutorConstraints(
|
|
|
393
394
|
].join("\n");
|
|
394
395
|
}
|
|
395
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Builds the Declared Repositories block for parent-workspace planners.
|
|
399
|
+
* Returns "" for single-repo projects so the common-case prompt is unchanged;
|
|
400
|
+
* only parent mode with declared child repos gets the registry list and the
|
|
401
|
+
* targetRepositories assignment instruction.
|
|
402
|
+
*/
|
|
403
|
+
export function buildRepoRegistryBlock(base: string): string {
|
|
404
|
+
let registry;
|
|
405
|
+
try {
|
|
406
|
+
registry = createRepositoryRegistryFromPreferences(base, loadEffectiveGSDPreferences(base)?.preferences);
|
|
407
|
+
} catch {
|
|
408
|
+
return "";
|
|
409
|
+
}
|
|
410
|
+
const hasChildRepo = registry.repositories.some((repo) => repo.id !== "project");
|
|
411
|
+
if (registry.mode !== "parent" || !hasChildRepo) return "";
|
|
412
|
+
|
|
413
|
+
const lines = registry.repositories.map((repo) => {
|
|
414
|
+
const relPath = relative(registry.projectRoot, repo.root).split(sep).join("/") || ".";
|
|
415
|
+
const role = repo.role ? ` — ${repo.role}` : "";
|
|
416
|
+
const root = repo.id === "project" ? " (root)" : ` (${relPath})`;
|
|
417
|
+
return `- \`${repo.id}\`${root}${role}`;
|
|
418
|
+
});
|
|
419
|
+
return [
|
|
420
|
+
"### Declared Repositories",
|
|
421
|
+
"",
|
|
422
|
+
...lines,
|
|
423
|
+
"",
|
|
424
|
+
"This is a parent workspace. Assign each task's `targetRepositories` to the repository id(s) it touches (from the list above). Omit `targetRepositories` to inherit the slice-wide default from `gsd_plan_slice`; set it explicitly when a task differs (e.g. `[\"project\"]` for parent-root work).",
|
|
425
|
+
].join("\n");
|
|
426
|
+
}
|
|
427
|
+
|
|
396
428
|
/**
|
|
397
429
|
* Returns a markdown bullet list of known context file paths for the given
|
|
398
430
|
* milestone (and optionally slice). Falls back to a generic tool-agnostic
|
|
@@ -2504,6 +2536,7 @@ async function renderSlicePrompt(options: {
|
|
|
2504
2536
|
inlinedContext,
|
|
2505
2537
|
dependencySummaries: depContent,
|
|
2506
2538
|
sourceFilePaths: buildSourceFilePaths(base, mid, sid),
|
|
2539
|
+
repoRegistry: buildRepoRegistryBlock(base),
|
|
2507
2540
|
executorContextConstraints,
|
|
2508
2541
|
commitInstruction,
|
|
2509
2542
|
skillActivation: buildSkillActivationBlock({
|
|
@@ -3362,6 +3395,44 @@ export async function buildValidateMilestonePrompt(
|
|
|
3362
3395
|
trackPromptContext(contextTelemetry, "outstanding-items", "inline", outstandingBlock);
|
|
3363
3396
|
}
|
|
3364
3397
|
|
|
3398
|
+
const persistedGateFlags: string[] = [];
|
|
3399
|
+
try {
|
|
3400
|
+
const gateLabels = {
|
|
3401
|
+
Q3: "Threat Surface",
|
|
3402
|
+
Q4: "Requirement Impact",
|
|
3403
|
+
} as const;
|
|
3404
|
+
for (const sid of seenValSlices) {
|
|
3405
|
+
for (const gate of getGateResults(mid, sid, "slice")) {
|
|
3406
|
+
if (gate.gate_id !== "Q3" && gate.gate_id !== "Q4") continue;
|
|
3407
|
+
const findings = gate.findings.trim();
|
|
3408
|
+
const rationale = gate.rationale.trim();
|
|
3409
|
+
const verdict = gate.verdict ?? "";
|
|
3410
|
+
const hasNonPassVerdict = verdict !== "" && verdict !== "pass" && verdict !== "omitted";
|
|
3411
|
+
if (!findings && !hasNonPassVerdict) continue;
|
|
3412
|
+
|
|
3413
|
+
const displayVerdict = verdict || gate.status;
|
|
3414
|
+
const detail = findings || rationale || "_No findings text recorded; reconcile the persisted non-pass verdict._";
|
|
3415
|
+
persistedGateFlags.push(`- **${sid} / ${gate.gate_id} (${gateLabels[gate.gate_id]}) / ${displayVerdict}:** ${detail}`);
|
|
3416
|
+
if (findings && rationale) persistedGateFlags.push(` - Rationale: ${rationale}`);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
} catch (err) {
|
|
3420
|
+
logWarning("prompt", `buildValidateMilestonePrompt persisted gate flags lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3421
|
+
}
|
|
3422
|
+
if (persistedGateFlags.length > 0) {
|
|
3423
|
+
const persistedGateFlagsBlock = [
|
|
3424
|
+
"### Persisted Slice-Level Gate Flags (from quality_gates)",
|
|
3425
|
+
"",
|
|
3426
|
+
"These slice gates recorded findings or non-pass verdicts during execution. Reconcile each against the milestone-level evidence before returning MV03/MV04 verdicts.",
|
|
3427
|
+
"",
|
|
3428
|
+
...persistedGateFlags,
|
|
3429
|
+
].join("\n");
|
|
3430
|
+
inlined.push(persistedGateFlagsBlock);
|
|
3431
|
+
trackPromptContext(contextTelemetry, "persisted-slice-gate-flags", "inline", persistedGateFlagsBlock);
|
|
3432
|
+
} else {
|
|
3433
|
+
trackPromptContext(contextTelemetry, "persisted-slice-gate-flags", "skipped", null, "none");
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3365
3436
|
// Inline existing VALIDATION file if this is a re-validation round
|
|
3366
3437
|
const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
|
|
3367
3438
|
const validationRel = relMilestoneFile(base, mid, "VALIDATION");
|
|
@@ -1849,8 +1849,9 @@ export async function bootstrapAutoSession(
|
|
|
1849
1849
|
: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "default";
|
|
1850
1850
|
|
|
1851
1851
|
// Flat-rate providers (e.g. GitHub Copilot, claude-code, user-declared
|
|
1852
|
-
// subscription proxies, externalCli CLIs) suppress routing
|
|
1853
|
-
//
|
|
1852
|
+
// subscription proxies, externalCli CLIs) suppress routing only when the
|
|
1853
|
+
// user explicitly opts out with allow_flat_rate_providers: false. Reflect
|
|
1854
|
+
// that in the banner. Thread the same
|
|
1854
1855
|
// FlatRateContext used by selectAndApplyModel so user-declared
|
|
1855
1856
|
// flat-rate providers and externalCli auto-detection are respected.
|
|
1856
1857
|
const { isFlatRateProvider, buildFlatRateContext } = await import("./auto-model-selection.js");
|
|
@@ -1862,7 +1863,7 @@ export async function bootstrapAutoSession(
|
|
|
1862
1863
|
)?.preferences;
|
|
1863
1864
|
const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
|
|
1864
1865
|
const effectivelyEnabled = routingConfig.enabled
|
|
1865
|
-
&& (routingConfig.allow_flat_rate_providers
|
|
1866
|
+
&& (routingConfig.allow_flat_rate_providers !== false
|
|
1866
1867
|
|| !(effectiveProvider && isFlatRateProvider(
|
|
1867
1868
|
effectiveProvider,
|
|
1868
1869
|
buildFlatRateContext(effectiveProvider, ctx, bannerPrefs),
|
|
@@ -97,6 +97,8 @@ export async function recoverTimedOutUnit(
|
|
|
97
97
|
lastProgressAt: Date.now(),
|
|
98
98
|
progressCount: (runtime?.progressCount ?? 0) + 1,
|
|
99
99
|
lastProgressKind: reason === "idle" ? "idle-recovery-retry" : "hard-recovery-retry",
|
|
100
|
+
// This steering retry is a fresh chance for the same unit to save a valid result.
|
|
101
|
+
harnessAbort: undefined,
|
|
100
102
|
});
|
|
101
103
|
|
|
102
104
|
const steeringLines = isEscalation
|
|
@@ -222,6 +224,8 @@ export async function recoverTimedOutUnit(
|
|
|
222
224
|
lastProgressAt: Date.now(),
|
|
223
225
|
progressCount: (runtime?.progressCount ?? 0) + 1,
|
|
224
226
|
lastProgressKind: reason === "idle" ? "idle-recovery-retry" : "hard-recovery-retry",
|
|
227
|
+
// This steering retry is a fresh chance for the same unit to save a valid result.
|
|
228
|
+
harnessAbort: undefined,
|
|
225
229
|
});
|
|
226
230
|
|
|
227
231
|
const steeringLines = isEscalation
|