@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,353 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createAssistantMessageEventStream } from "@gsd/pi-ai";
|
|
3
|
+
import { buildCursorAgentSpawnInvocation } from "./readiness.js";
|
|
4
|
+
const ZERO_USAGE = {
|
|
5
|
+
input: 0,
|
|
6
|
+
output: 0,
|
|
7
|
+
cacheRead: 0,
|
|
8
|
+
cacheWrite: 0,
|
|
9
|
+
totalTokens: 0,
|
|
10
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
11
|
+
};
|
|
12
|
+
function textBlocks(content) {
|
|
13
|
+
return content
|
|
14
|
+
.map((block) => block.type === "text" ? block.text : `[${block.type} omitted]`)
|
|
15
|
+
.join("\n");
|
|
16
|
+
}
|
|
17
|
+
function messageToText(message) {
|
|
18
|
+
if (message.role === "user") {
|
|
19
|
+
const content = typeof message.content === "string" ? message.content : textBlocks(message.content);
|
|
20
|
+
return `User:\n${content}`;
|
|
21
|
+
}
|
|
22
|
+
if (message.role === "assistant") {
|
|
23
|
+
const text = message.content
|
|
24
|
+
.map((block) => {
|
|
25
|
+
if (block.type === "text")
|
|
26
|
+
return block.text;
|
|
27
|
+
if (block.type === "thinking")
|
|
28
|
+
return "[thinking omitted]";
|
|
29
|
+
if (block.type === "toolCall")
|
|
30
|
+
return `[tool call: ${block.name}]`;
|
|
31
|
+
if (block.type === "serverToolUse")
|
|
32
|
+
return `[server tool: ${block.name}]`;
|
|
33
|
+
if (block.type === "webSearchResult")
|
|
34
|
+
return "[web search result omitted]";
|
|
35
|
+
return `[${block.type} omitted]`;
|
|
36
|
+
})
|
|
37
|
+
.join("\n");
|
|
38
|
+
return `Assistant:\n${text}`;
|
|
39
|
+
}
|
|
40
|
+
return `Tool result (${message.toolName}):\n${textBlocks(message.content)}`;
|
|
41
|
+
}
|
|
42
|
+
export function buildCursorPrompt(context) {
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (context.systemPrompt?.trim())
|
|
45
|
+
parts.push(`System instructions:\n${context.systemPrompt.trim()}`);
|
|
46
|
+
if (context.messages.length > 0)
|
|
47
|
+
parts.push(context.messages.map(messageToText).join("\n\n"));
|
|
48
|
+
if (context.tools?.length) {
|
|
49
|
+
parts.push(`The external Cursor agent may execute its own tools. ` +
|
|
50
|
+
`GSD will not redispatch Cursor-owned internal tool events locally. ` +
|
|
51
|
+
`Requested GSD tools: ${context.tools.map((tool) => tool.name).join(", ")}`);
|
|
52
|
+
}
|
|
53
|
+
return parts.join("\n\n").trim();
|
|
54
|
+
}
|
|
55
|
+
export function buildCursorSpawnInvocation(command, args, platform = process.platform) {
|
|
56
|
+
return buildCursorAgentSpawnInvocation(command, args, platform);
|
|
57
|
+
}
|
|
58
|
+
export function buildCursorAgentRunPlan(modelId, prompt, cwd, platform = process.platform) {
|
|
59
|
+
return buildCursorSpawnInvocation(process.env.CURSOR_AGENT_BIN?.trim() || "cursor-agent", ["-p", prompt, "--output-format", "stream-json", "--model", modelId, "--workspace", cwd, "--trust"], platform);
|
|
60
|
+
}
|
|
61
|
+
function emitCompleteLines(chunk, pending, onLine) {
|
|
62
|
+
pending.value += chunk;
|
|
63
|
+
const lines = pending.value.split(/\r?\n/);
|
|
64
|
+
pending.value = lines.pop() ?? "";
|
|
65
|
+
for (const line of lines)
|
|
66
|
+
onLine(line);
|
|
67
|
+
}
|
|
68
|
+
function flushPendingLine(pending, onLine) {
|
|
69
|
+
if (!pending.value)
|
|
70
|
+
return;
|
|
71
|
+
onLine(pending.value);
|
|
72
|
+
pending.value = "";
|
|
73
|
+
}
|
|
74
|
+
function runCursorAgent(plan, options, onLine) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const child = spawn(plan.command, plan.args, {
|
|
77
|
+
cwd: options?.cwd || process.cwd(),
|
|
78
|
+
env: process.env,
|
|
79
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
80
|
+
});
|
|
81
|
+
let stdout = "";
|
|
82
|
+
let stderr = "";
|
|
83
|
+
let settled = false;
|
|
84
|
+
const pendingStdout = { value: "" };
|
|
85
|
+
const settle = (fn) => {
|
|
86
|
+
if (settled)
|
|
87
|
+
return;
|
|
88
|
+
settled = true;
|
|
89
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
90
|
+
fn();
|
|
91
|
+
};
|
|
92
|
+
const onAbort = () => {
|
|
93
|
+
child.kill("SIGTERM");
|
|
94
|
+
settle(() => reject(new Error("Request was aborted")));
|
|
95
|
+
};
|
|
96
|
+
if (options?.signal?.aborted) {
|
|
97
|
+
onAbort();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
options?.signal?.addEventListener("abort", onAbort);
|
|
101
|
+
child.stdout?.setEncoding("utf8");
|
|
102
|
+
child.stderr?.setEncoding("utf8");
|
|
103
|
+
child.stdout?.on("data", (chunk) => {
|
|
104
|
+
stdout += chunk;
|
|
105
|
+
try {
|
|
106
|
+
emitCompleteLines(chunk, pendingStdout, onLine);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
child.kill("SIGTERM");
|
|
110
|
+
settle(() => reject(error));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
child.stderr?.on("data", (chunk) => { stderr += chunk; });
|
|
114
|
+
child.on("error", (error) => settle(() => reject(error)));
|
|
115
|
+
child.on("close", (code, signal) => settle(() => {
|
|
116
|
+
try {
|
|
117
|
+
flushPendingLine(pendingStdout, onLine);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
reject(error);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
resolve({ stdout, stderr, code, signal });
|
|
124
|
+
}));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function stringValue(...values) {
|
|
128
|
+
for (const value of values) {
|
|
129
|
+
if (typeof value === "string" && value.length > 0)
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
function objectValue(value) {
|
|
135
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
136
|
+
}
|
|
137
|
+
function extractText(value) {
|
|
138
|
+
if (typeof value === "string")
|
|
139
|
+
return value;
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return value
|
|
142
|
+
.map((item) => stringValue(objectValue(item)?.text, objectValue(item)?.content, item))
|
|
143
|
+
.filter((item) => Boolean(item))
|
|
144
|
+
.join("\n");
|
|
145
|
+
}
|
|
146
|
+
const obj = objectValue(value);
|
|
147
|
+
if (!obj)
|
|
148
|
+
return "";
|
|
149
|
+
return stringValue(obj.text, obj.content, obj.message, obj.result) ?? "";
|
|
150
|
+
}
|
|
151
|
+
function mapUsage(raw) {
|
|
152
|
+
const input = Number(raw.input_tokens ?? raw.input ?? raw.prompt_tokens ?? 0);
|
|
153
|
+
const output = Number(raw.output_tokens ?? raw.output ?? raw.completion_tokens ?? 0);
|
|
154
|
+
const cacheRead = Number(raw.cache_read_input_tokens ?? raw.cacheRead ?? 0);
|
|
155
|
+
const cacheWrite = Number(raw.cache_creation_input_tokens ?? raw.cacheWrite ?? 0);
|
|
156
|
+
return {
|
|
157
|
+
input,
|
|
158
|
+
output,
|
|
159
|
+
cacheRead,
|
|
160
|
+
cacheWrite,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function normalizeToolArguments(input) {
|
|
164
|
+
return objectValue(input) ?? { input };
|
|
165
|
+
}
|
|
166
|
+
function normalizeToolResult(raw) {
|
|
167
|
+
const text = extractText(raw.content ?? raw.result ?? raw.output);
|
|
168
|
+
return {
|
|
169
|
+
content: text ? [{ type: "text", text }] : [],
|
|
170
|
+
isError: Boolean(raw.is_error ?? raw.isError ?? raw.error),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export function parseCursorAgentLine(line) {
|
|
174
|
+
const trimmed = line.trim();
|
|
175
|
+
if (!trimmed)
|
|
176
|
+
return { type: "ignore" };
|
|
177
|
+
let event;
|
|
178
|
+
try {
|
|
179
|
+
event = JSON.parse(trimmed);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return { type: "text", text: trimmed };
|
|
183
|
+
}
|
|
184
|
+
const type = stringValue(event.type, event.event) ?? "";
|
|
185
|
+
if (type === "error") {
|
|
186
|
+
return { type: "error", message: stringValue(event.message, event.error) ?? "cursor_agent_error" };
|
|
187
|
+
}
|
|
188
|
+
if (type === "tool_call") {
|
|
189
|
+
if (event.call_id || event.subtype || event.tool_call)
|
|
190
|
+
return { type: "ignore" };
|
|
191
|
+
const id = stringValue(event.id, event.tool_call_id) ?? `cursor_tool_${Date.now()}`;
|
|
192
|
+
const name = stringValue(event.name, event.tool_name) ?? "cursor_tool";
|
|
193
|
+
return {
|
|
194
|
+
type: "tool_call",
|
|
195
|
+
toolCall: { type: "toolCall", id, name, arguments: normalizeToolArguments(event.input ?? event.arguments) },
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (type === "tool_result") {
|
|
199
|
+
return {
|
|
200
|
+
type: "tool_result",
|
|
201
|
+
toolCallId: stringValue(event.tool_call_id, event.id) ?? "",
|
|
202
|
+
result: normalizeToolResult(event),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (type === "result") {
|
|
206
|
+
const usage = objectValue(event.usage);
|
|
207
|
+
return usage ? { type: "usage", usage: mapUsage(usage) } : { type: "ignore" };
|
|
208
|
+
}
|
|
209
|
+
const text = extractText(event.delta ?? event.text ?? event.content ?? objectValue(event.message)?.content ?? event.message);
|
|
210
|
+
if (text)
|
|
211
|
+
return { type: "text", text };
|
|
212
|
+
const usage = objectValue(event.usage);
|
|
213
|
+
if (usage)
|
|
214
|
+
return { type: "usage", usage: mapUsage(usage) };
|
|
215
|
+
return { type: "ignore" };
|
|
216
|
+
}
|
|
217
|
+
function createUsage(partial) {
|
|
218
|
+
const input = partial?.input ?? 0;
|
|
219
|
+
const output = partial?.output ?? 0;
|
|
220
|
+
const cacheRead = partial?.cacheRead ?? 0;
|
|
221
|
+
const cacheWrite = partial?.cacheWrite ?? 0;
|
|
222
|
+
return {
|
|
223
|
+
...ZERO_USAGE,
|
|
224
|
+
input,
|
|
225
|
+
output,
|
|
226
|
+
cacheRead,
|
|
227
|
+
cacheWrite,
|
|
228
|
+
totalTokens: input + output + cacheWrite,
|
|
229
|
+
cost: { ...ZERO_USAGE.cost },
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function buildAssistantMessage(model, content, usage, stopReason = "stop", errorMessage) {
|
|
233
|
+
return {
|
|
234
|
+
role: "assistant",
|
|
235
|
+
content,
|
|
236
|
+
api: model.api,
|
|
237
|
+
provider: model.provider,
|
|
238
|
+
model: model.id,
|
|
239
|
+
usage: createUsage(usage),
|
|
240
|
+
stopReason,
|
|
241
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
242
|
+
timestamp: Date.now(),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function emitDone(stream, message, text) {
|
|
246
|
+
stream.push({ type: "start", partial: { ...message, content: [] } });
|
|
247
|
+
if (text) {
|
|
248
|
+
stream.push({ type: "text_start", contentIndex: 0, partial: message });
|
|
249
|
+
stream.push({ type: "text_delta", contentIndex: 0, delta: text, partial: message });
|
|
250
|
+
stream.push({ type: "text_end", contentIndex: 0, content: text, partial: message });
|
|
251
|
+
}
|
|
252
|
+
message.content.forEach((block, index) => {
|
|
253
|
+
if (block.type !== "toolCall")
|
|
254
|
+
return;
|
|
255
|
+
stream.push({ type: "toolcall_start", contentIndex: index, partial: message });
|
|
256
|
+
stream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: message });
|
|
257
|
+
});
|
|
258
|
+
stream.push({ type: "done", reason: "stop", message });
|
|
259
|
+
stream.end(message);
|
|
260
|
+
}
|
|
261
|
+
function emitError(stream, model, error) {
|
|
262
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
263
|
+
const output = buildAssistantMessage(model, [], undefined, "error", message);
|
|
264
|
+
stream.push({ type: "error", reason: "error", error: output });
|
|
265
|
+
stream.end(output);
|
|
266
|
+
}
|
|
267
|
+
export function streamViaCursorAgent(model, context, options) {
|
|
268
|
+
const stream = createAssistantMessageEventStream();
|
|
269
|
+
queueMicrotask(async () => {
|
|
270
|
+
try {
|
|
271
|
+
const cwd = options?.cwd || process.cwd();
|
|
272
|
+
const prompt = buildCursorPrompt(context);
|
|
273
|
+
const plan = buildCursorAgentRunPlan(model.id, prompt, cwd);
|
|
274
|
+
const runForTest = options?._cursorAgentRunnerForTest;
|
|
275
|
+
let text = "";
|
|
276
|
+
let usage;
|
|
277
|
+
let streamStarted = false;
|
|
278
|
+
let textStarted = false;
|
|
279
|
+
const toolCalls = new Map();
|
|
280
|
+
const content = [];
|
|
281
|
+
const partialMessage = () => {
|
|
282
|
+
const partialContent = text ? [{ type: "text", text }, ...content] : [...content];
|
|
283
|
+
return buildAssistantMessage(model, partialContent, usage);
|
|
284
|
+
};
|
|
285
|
+
const ensureStart = () => {
|
|
286
|
+
if (streamStarted)
|
|
287
|
+
return;
|
|
288
|
+
streamStarted = true;
|
|
289
|
+
stream.push({ type: "start", partial: buildAssistantMessage(model, []) });
|
|
290
|
+
};
|
|
291
|
+
const handleLine = (line) => {
|
|
292
|
+
const parsed = parseCursorAgentLine(line);
|
|
293
|
+
if (parsed.type === "ignore")
|
|
294
|
+
return;
|
|
295
|
+
if (parsed.type === "error")
|
|
296
|
+
throw new Error(parsed.message);
|
|
297
|
+
if (parsed.type === "text") {
|
|
298
|
+
ensureStart();
|
|
299
|
+
if (!textStarted) {
|
|
300
|
+
textStarted = true;
|
|
301
|
+
stream.push({ type: "text_start", contentIndex: 0, partial: partialMessage() });
|
|
302
|
+
}
|
|
303
|
+
text += parsed.text;
|
|
304
|
+
stream.push({ type: "text_delta", contentIndex: 0, delta: parsed.text, partial: partialMessage() });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (parsed.type === "usage") {
|
|
308
|
+
usage = { ...(usage ?? {}), ...parsed.usage };
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (parsed.type === "tool_call") {
|
|
312
|
+
ensureStart();
|
|
313
|
+
toolCalls.set(parsed.toolCall.id, parsed.toolCall);
|
|
314
|
+
content.push(parsed.toolCall);
|
|
315
|
+
const index = text ? content.length : content.length - 1;
|
|
316
|
+
const partial = partialMessage();
|
|
317
|
+
stream.push({ type: "toolcall_start", contentIndex: index, partial });
|
|
318
|
+
stream.push({ type: "toolcall_end", contentIndex: index, toolCall: parsed.toolCall, partial });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (parsed.type === "tool_result") {
|
|
322
|
+
const toolCall = toolCalls.get(parsed.toolCallId);
|
|
323
|
+
if (toolCall)
|
|
324
|
+
toolCall.externalResult = parsed.result;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const result = runForTest
|
|
328
|
+
? await runForTest(plan, options, handleLine)
|
|
329
|
+
: await runCursorAgent(plan, options, handleLine);
|
|
330
|
+
if (runForTest) {
|
|
331
|
+
for (const line of result.stdout.split(/\r?\n/))
|
|
332
|
+
handleLine(line);
|
|
333
|
+
}
|
|
334
|
+
if (result.code !== 0) {
|
|
335
|
+
throw new Error((result.stderr || result.stdout || `cursor-agent exited with code ${result.code}`).trim());
|
|
336
|
+
}
|
|
337
|
+
const finalContent = text ? [{ type: "text", text }, ...content] : content;
|
|
338
|
+
const finalMessage = buildAssistantMessage(model, finalContent, usage);
|
|
339
|
+
if (!streamStarted) {
|
|
340
|
+
emitDone(stream, finalMessage, text);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (textStarted)
|
|
344
|
+
stream.push({ type: "text_end", contentIndex: 0, content: text, partial: finalMessage });
|
|
345
|
+
stream.push({ type: "done", reason: "stop", message: finalMessage });
|
|
346
|
+
stream.end(finalMessage);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
emitError(stream, model, error);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
return stream;
|
|
353
|
+
}
|
|
@@ -13,6 +13,7 @@ import { join } from "node:path";
|
|
|
13
13
|
import { GSDError, GSD_IO_ERROR } from "./errors.js";
|
|
14
14
|
const SEQ_PREFIX_RE = /^(\d+)-/;
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
16
|
+
import { redactSecrets } from "./redact-secrets.js";
|
|
16
17
|
import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js";
|
|
17
18
|
import { isUnifiedAuditEnabled } from "./uok/audit-toggle.js";
|
|
18
19
|
const activityLogState = new Map();
|
|
@@ -104,7 +105,9 @@ export function saveActivityLog(ctx, basePath, unitType, unitId) {
|
|
|
104
105
|
const fd = openSync(filePath, "w");
|
|
105
106
|
try {
|
|
106
107
|
for (const entry of entries) {
|
|
107
|
-
|
|
108
|
+
// Redact secret-shaped substrings before persisting; .gsd/ is skipped by
|
|
109
|
+
// the secret scanner, so raw LLM/tool content could otherwise leak keys.
|
|
110
|
+
writeSync(fd, redactSecrets(JSON.stringify(entry)) + "\n");
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
finally {
|
|
@@ -47,6 +47,10 @@ function parseRoadmapForRecovery(content) {
|
|
|
47
47
|
return _roadmapParserFn(content);
|
|
48
48
|
return parseLegacyRoadmap(content);
|
|
49
49
|
}
|
|
50
|
+
/** Slice count for plan-milestone verification; shared by scoped and legacy paths. */
|
|
51
|
+
export function countPlanMilestoneRoadmapSlices(content) {
|
|
52
|
+
return parseRoadmapForRecovery(content).slices.length;
|
|
53
|
+
}
|
|
50
54
|
export function diagnoseWorktreeIntegrityFailure(basePath) {
|
|
51
55
|
if (!isGsdWorktreePath(basePath))
|
|
52
56
|
return null;
|
|
@@ -294,8 +298,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
294
298
|
}
|
|
295
299
|
if (unitType === "plan-milestone") {
|
|
296
300
|
try {
|
|
297
|
-
|
|
298
|
-
if (roadmap.slices.length === 0) {
|
|
301
|
+
if (countPlanMilestoneRoadmapSlices(readFileSync(absPath, "utf-8")) === 0) {
|
|
299
302
|
logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap has zero slices at ${absPath}`);
|
|
300
303
|
return false;
|
|
301
304
|
}
|
|
@@ -311,6 +314,19 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
311
314
|
try {
|
|
312
315
|
let taskIds = null;
|
|
313
316
|
let dbPrimary = false;
|
|
317
|
+
const planContent = readFileSync(absPath, "utf-8");
|
|
318
|
+
let parsedTaskIds = null;
|
|
319
|
+
const getParsedTaskIds = () => {
|
|
320
|
+
if (parsedTaskIds)
|
|
321
|
+
return parsedTaskIds;
|
|
322
|
+
parsedTaskIds = parseLegacyPlan(planContent).tasks.map((t) => t.id);
|
|
323
|
+
return parsedTaskIds;
|
|
324
|
+
};
|
|
325
|
+
const tasksBlockMatch = planContent.match(/<tasks>([\s\S]*?)<\/tasks>/i);
|
|
326
|
+
const tasksBlock = tasksBlockMatch?.[1] ?? "";
|
|
327
|
+
const hasEmbeddedTaskEntries = tasksBlock.length > 0 &&
|
|
328
|
+
(/^\s*- \[[xX ]\] \*\*T\d+/m.test(tasksBlock) ||
|
|
329
|
+
/^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(tasksBlock));
|
|
314
330
|
if (isDbAvailable()) {
|
|
315
331
|
const refreshed = refreshWorkflowDatabaseFromDisk();
|
|
316
332
|
if (refreshed) {
|
|
@@ -322,24 +338,24 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
322
338
|
}
|
|
323
339
|
}
|
|
324
340
|
if (!taskIds) {
|
|
325
|
-
const planContent = readFileSync(absPath, "utf-8");
|
|
326
341
|
const hasCheckboxTask = /^\s*- \[[xX ]\] \*\*T\d+/m.test(planContent);
|
|
327
342
|
const hasHeadingTask = /^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
|
|
328
343
|
if (!hasCheckboxTask && !hasHeadingTask) {
|
|
329
344
|
logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
|
|
330
345
|
return false;
|
|
331
346
|
}
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
taskIds =
|
|
347
|
+
const parsedIds = getParsedTaskIds();
|
|
348
|
+
if (parsedIds.length > 0)
|
|
349
|
+
taskIds = parsedIds;
|
|
335
350
|
}
|
|
336
|
-
if (taskIds && taskIds.length > 0) {
|
|
351
|
+
if (taskIds && taskIds.length > 0 && !hasEmbeddedTaskEntries) {
|
|
337
352
|
const tasksDir = join(dirname(absPath), "tasks");
|
|
338
353
|
if (existsSync(tasksDir)) {
|
|
339
354
|
for (const tid of taskIds) {
|
|
340
355
|
const taskPlanFile = join(tasksDir, `${tid}-PLAN.md`);
|
|
341
|
-
|
|
342
|
-
|
|
356
|
+
const taskSummaryFile = join(tasksDir, `${tid}-SUMMARY.md`);
|
|
357
|
+
if (!existsSync(taskPlanFile) && !existsSync(taskSummaryFile)) {
|
|
358
|
+
logWarning("recovery", `verify-fail ${unitType} ${unitId}: task artifact missing for ${tid}`);
|
|
343
359
|
return false;
|
|
344
360
|
}
|
|
345
361
|
}
|
|
@@ -37,62 +37,51 @@ export async function restorePreflightStashOrStop(ic, preflight, milestoneId) {
|
|
|
37
37
|
return stopOnPostflightRecoveryNeeded(ic, result, milestoneId);
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* Run a milestone merge
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
40
|
+
* Run a milestone merge through Worktree Lifecycle's guarded merge option,
|
|
41
|
+
* which surrounds the inner merge with preflight stash + always-on postflight
|
|
42
|
+
* pop. The previous closeout code popped the stash only after a successful
|
|
43
|
+
* merge, which leaked `gsd-preflight-stash:M00x:*` entries whenever
|
|
44
|
+
* `mergeAndExit` threw — leaving the user's pre-merge working tree silently
|
|
45
|
+
* stashed away after a merge-conflict or other merge error. Lifecycle now
|
|
46
|
+
* restores the stash on every attempted merge path, then this adapter surfaces
|
|
47
|
+
* the merge or stash failure (in priority order) as the loop's stop reason.
|
|
47
48
|
*
|
|
48
49
|
* Returns a `break` action when auto-mode must stop, or `null` when the merge
|
|
49
50
|
* succeeded and the stash (if any) was restored cleanly.
|
|
50
51
|
*/
|
|
51
52
|
export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, options = {}) {
|
|
52
53
|
const { ctx, pi, s, deps } = ic;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const projectRoot = s.originalBasePath || s.basePath;
|
|
55
|
+
const mergeResult = deps.lifecycle.exitMilestone(milestoneId, {
|
|
56
|
+
merge: true,
|
|
57
|
+
guardedMerge: {
|
|
58
|
+
projectRoot,
|
|
59
|
+
preflightCleanRoot: deps.preflightCleanRoot,
|
|
60
|
+
postflightPopStash: deps.postflightPopStash,
|
|
61
|
+
},
|
|
62
|
+
}, ctx.ui);
|
|
63
|
+
if (mergeResult.ok) {
|
|
64
|
+
await markMilestoneMergedAndRebuild(s);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (mergeResult.reason === "postflight-stash-restore-failed") {
|
|
68
|
+
await markMilestoneMergedAndRebuild(s);
|
|
69
|
+
}
|
|
70
|
+
if (mergeResult.reason === "preflight-dirty-overlap" || mergeResult.reason === "preflight-unmerged-conflicts") {
|
|
71
|
+
const reason = mergeResult.reason === "preflight-unmerged-conflicts"
|
|
56
72
|
? `Pre-merge unresolved Git conflicts block milestone ${milestoneId}`
|
|
57
73
|
: `Pre-merge dirty working tree overlaps milestone ${milestoneId}`;
|
|
58
74
|
await deps.stopAuto(ctx, pi, reason, {
|
|
59
75
|
preserveCompletedMilestoneBranch: true,
|
|
60
76
|
preserveCloseoutTranscript: options.preserveCloseoutTranscript,
|
|
61
77
|
});
|
|
62
|
-
return {
|
|
63
|
-
action: "break",
|
|
64
|
-
reason: preflight.blockedReason === "unmerged-conflicts"
|
|
65
|
-
? "preflight-unmerged-conflicts"
|
|
66
|
-
: "preflight-dirty-overlap",
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
let mergeError = null;
|
|
70
|
-
const exitResult = deps.lifecycle.exitMilestone(milestoneId, { merge: true }, ctx.ui);
|
|
71
|
-
if (exitResult.ok) {
|
|
72
|
-
s.milestoneMergedInPhases = true;
|
|
73
|
-
try {
|
|
74
|
-
const projectRoot = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
75
|
-
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
76
|
-
await rebuildMarkdownProjectionsFromDb(projectRoot);
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
logWarning("engine", `markdown projection rebuild after milestone merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
mergeError = exitResult.cause ?? new Error(`exit ${exitResult.reason}`);
|
|
84
|
-
}
|
|
85
|
-
// Always attempt to restore the stashed working tree, even on merge error.
|
|
86
|
-
// postflightPopStash itself does not throw; failures surface via the
|
|
87
|
-
// PostflightResult.needsManualRecovery flag.
|
|
88
|
-
let stashResult = null;
|
|
89
|
-
if (preflight.stashPushed) {
|
|
90
|
-
stashResult = deps.postflightPopStash(s.originalBasePath || s.basePath, milestoneId, preflight.stashMarker, ctx.ui.notify.bind(ctx.ui));
|
|
78
|
+
return { action: "break", reason: mergeResult.reason };
|
|
91
79
|
}
|
|
92
80
|
// Merge failure takes priority over stash recovery — the merge is the
|
|
93
81
|
// authoritative gate. If the stash also needed manual recovery, the user
|
|
94
82
|
// already saw the postflightPopStash notify above.
|
|
95
|
-
if (
|
|
83
|
+
if (mergeResult.reason === "merge-conflict") {
|
|
84
|
+
const mergeError = mergeResult.cause;
|
|
96
85
|
if (mergeError instanceof MergeConflictError) {
|
|
97
86
|
// A merge conflict is a recoverable human checkpoint, not an
|
|
98
87
|
// infrastructure failure — the user resolves the conflict and runs
|
|
@@ -109,6 +98,9 @@ export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, option
|
|
|
109
98
|
});
|
|
110
99
|
return { action: "break", reason: "merge-conflict" };
|
|
111
100
|
}
|
|
101
|
+
}
|
|
102
|
+
if (mergeResult.reason === "merge-failed" || mergeResult.reason === "merge-conflict") {
|
|
103
|
+
const mergeError = mergeResult.cause;
|
|
112
104
|
logError("engine", "Milestone merge failed with non-conflict error", {
|
|
113
105
|
milestone: milestoneId,
|
|
114
106
|
error: String(mergeError),
|
|
@@ -125,11 +117,22 @@ export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, option
|
|
|
125
117
|
});
|
|
126
118
|
return { action: "break", reason: "merge-failed" };
|
|
127
119
|
}
|
|
128
|
-
if (
|
|
129
|
-
return stopOnPostflightRecoveryNeeded(ic,
|
|
120
|
+
if (mergeResult.postflight) {
|
|
121
|
+
return stopOnPostflightRecoveryNeeded(ic, mergeResult.postflight, milestoneId);
|
|
130
122
|
}
|
|
131
123
|
return null;
|
|
132
124
|
}
|
|
125
|
+
async function markMilestoneMergedAndRebuild(s) {
|
|
126
|
+
s.milestoneMergedInPhases = true;
|
|
127
|
+
try {
|
|
128
|
+
const rebuildBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
|
|
129
|
+
const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
|
|
130
|
+
await rebuildMarkdownProjectionsFromDb(rebuildBasePath);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
logWarning("engine", `markdown projection rebuild after milestone merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
133
136
|
export async function _runMilestoneMergeOnceWithStashRestore(ic, milestoneId, options = {}) {
|
|
134
137
|
if (ic.s.milestoneMergedInPhases) {
|
|
135
138
|
debugLog("autoLoop", {
|
|
@@ -280,6 +280,15 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
280
280
|
files: leak.files.map((file) => ({ path: file.path, status: file.status })),
|
|
281
281
|
});
|
|
282
282
|
ctx.ui.notify(message, "error");
|
|
283
|
+
try {
|
|
284
|
+
deps.checkpointWorkflowDatabase?.();
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
debugLog("autoLoop", {
|
|
288
|
+
phase: "root-write-leak-checkpoint-failed",
|
|
289
|
+
error: err instanceof Error ? err.message : String(err),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
283
292
|
await deps.stopAuto(ctx, pi, "Root-write leak during isolated auto-mode", {
|
|
284
293
|
preserveCompletedMilestoneBranch: true,
|
|
285
294
|
});
|
|
@@ -560,7 +560,7 @@ export class AutoOrchestrator {
|
|
|
560
560
|
return new GitServiceImpl(basePath, gitConfig);
|
|
561
561
|
},
|
|
562
562
|
worktreeProjection: new WorktreeStateProjection(),
|
|
563
|
-
|
|
563
|
+
mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
|
|
564
564
|
});
|
|
565
565
|
}
|
|
566
566
|
rebuildScope(rawPath, milestoneId) {
|
|
@@ -6,7 +6,7 @@ import { debugLog } from "../debug-logger.js";
|
|
|
6
6
|
import { logWarning } from "../workflow-logger.js";
|
|
7
7
|
import { resolveAutoSupervisorConfig } from "../preferences.js";
|
|
8
8
|
import { readUnitRuntimeRecord } from "../unit-runtime.js";
|
|
9
|
-
import { consumeAutoWakeup } from "./schedule-wakeup.js";
|
|
9
|
+
import { clearAutoWakeup, consumeAutoWakeup } from "./schedule-wakeup.js";
|
|
10
10
|
import { applyUnitSkillVisibility } from "../skill-scope.js";
|
|
11
11
|
const UNIT_FAILSAFE_BUFFER_MS = 30_000;
|
|
12
12
|
const UNIT_FAILSAFE_RECHECK_MS = 30_000;
|
|
@@ -192,7 +192,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
192
192
|
const UNIT_HARD_TIMEOUT_MS = Math.max(UNIT_FAILSAFE_BUFFER_MS, ((supervisor.hard_timeout_minutes ?? 30) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS);
|
|
193
193
|
const freshProgressMs = Math.max(UNIT_FAILSAFE_BUFFER_MS, ((supervisor.idle_timeout_minutes ?? 10) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS);
|
|
194
194
|
let unitTimeoutHandle;
|
|
195
|
-
let result;
|
|
195
|
+
let result = { status: "cancelled" };
|
|
196
196
|
try {
|
|
197
197
|
const awaitAgentEndWithTimeout = (agentEndPromise) => {
|
|
198
198
|
const timeoutResult = new Promise((resolve) => {
|
|
@@ -297,6 +297,9 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
297
297
|
finally {
|
|
298
298
|
if (unitTimeoutHandle)
|
|
299
299
|
clearTimeout(unitTimeoutHandle);
|
|
300
|
+
if (result.status !== "completed") {
|
|
301
|
+
clearAutoWakeup(s.basePath, unitType, unitId);
|
|
302
|
+
}
|
|
300
303
|
ctx.ui.setWorkingMessage?.(undefined);
|
|
301
304
|
setVisibleSkills?.(undefined);
|
|
302
305
|
}
|
|
@@ -13,6 +13,9 @@ export function consumeAutoWakeup(basePath, unitType, unitId) {
|
|
|
13
13
|
pendingWakeups.delete(key);
|
|
14
14
|
return wakeup;
|
|
15
15
|
}
|
|
16
|
+
export function clearAutoWakeup(basePath, unitType, unitId) {
|
|
17
|
+
pendingWakeups.delete(wakeupKey(basePath, unitType, unitId));
|
|
18
|
+
}
|
|
16
19
|
export function _resetAutoWakeupsForTest() {
|
|
17
20
|
pendingWakeups.clear();
|
|
18
21
|
}
|