@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
-
import { getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, _getAdapter, } from "./gsd-db.js";
|
|
2
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
import { deleteArtifactByPath, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, repairTaskCompletionFromSummary, _getAdapter, } from "./gsd-db.js";
|
|
4
4
|
import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
|
|
5
5
|
import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
6
6
|
import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "./paths.js";
|
|
@@ -11,9 +11,38 @@ import { readEvents } from "./workflow-events.js";
|
|
|
11
11
|
import { flushWorkflowProjections } from "./projection-flush.js";
|
|
12
12
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
13
13
|
import { parsePlan } from "./parsers-legacy.js";
|
|
14
|
+
import { parseSummary } from "./files.js";
|
|
15
|
+
const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
|
|
16
|
+
function escapeRegExp(value) {
|
|
17
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18
|
+
}
|
|
14
19
|
function relativeFile(basePath, filePath) {
|
|
15
20
|
return relative(basePath, filePath).split("\\").join("/");
|
|
16
21
|
}
|
|
22
|
+
function normalizedArtifactType(artifactType) {
|
|
23
|
+
return artifactType.trim().toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
function isUserAuthoredArtifactType(artifactType) {
|
|
26
|
+
return USER_AUTHORED_ARTIFACT_TYPES.has(normalizedArtifactType(artifactType));
|
|
27
|
+
}
|
|
28
|
+
function userContentRecoveryCommand(artifactType) {
|
|
29
|
+
return normalizedArtifactType(artifactType) === "CONTEXT" ? "/gsd discuss" : "/gsd auto";
|
|
30
|
+
}
|
|
31
|
+
function userContentMissingMessage(path, artifactType) {
|
|
32
|
+
const type = normalizedArtifactType(artifactType) || "UNKNOWN";
|
|
33
|
+
return `Artifact \`${path}\` is a user-authored ${type} file recorded in the database but missing from disk. Re-run \`${userContentRecoveryCommand(type)}\` in this milestone to regenerate it.`;
|
|
34
|
+
}
|
|
35
|
+
function artifactPathRelativeToGsd(artifactPath) {
|
|
36
|
+
const parts = artifactPath.split(/[\\/]+/);
|
|
37
|
+
const gsdIndex = parts.lastIndexOf(".gsd");
|
|
38
|
+
if (gsdIndex < 0 || gsdIndex === parts.length - 1)
|
|
39
|
+
return artifactPath;
|
|
40
|
+
return parts.slice(gsdIndex + 1).join("/");
|
|
41
|
+
}
|
|
42
|
+
function isPathInside(basePath, candidatePath) {
|
|
43
|
+
const rel = relative(basePath, candidatePath);
|
|
44
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
45
|
+
}
|
|
17
46
|
function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, unitId, status, checkboxDone) {
|
|
18
47
|
const dbDone = isClosedStatus(status);
|
|
19
48
|
if (checkboxDone === dbDone)
|
|
@@ -85,12 +114,97 @@ function isClearedByMilestoneShellProjectionFlush(basePath, issue, reRenderedMil
|
|
|
85
114
|
return issue.file === relativeFile(basePath, roadmapPath);
|
|
86
115
|
}
|
|
87
116
|
function artifactExistsOnDisk(basePath, artifactPath) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
117
|
+
return resolveArtifactDiskPath(basePath, artifactPath) !== null;
|
|
118
|
+
}
|
|
119
|
+
function resolveArtifactDiskPath(basePath, artifactPath) {
|
|
120
|
+
const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
|
|
121
|
+
if (isAbsolute(relativeArtifactPath)) {
|
|
122
|
+
return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
|
|
123
|
+
}
|
|
124
|
+
for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
|
|
125
|
+
const candidate = join(root, relativeArtifactPath);
|
|
126
|
+
if (isPathInside(root, candidate) && existsSync(candidate))
|
|
127
|
+
return candidate;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function taskExistsInPlan(basePath, milestoneId, sliceId, taskId) {
|
|
132
|
+
const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
|
|
133
|
+
if (!planPath || !existsSync(planPath))
|
|
134
|
+
return false;
|
|
135
|
+
try {
|
|
136
|
+
return parsePlan(readFileSync(planPath, "utf-8")).tasks.some((task) => task.id === taskId);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function isPassingVerificationResult(value) {
|
|
143
|
+
const normalized = value.trim().toLowerCase();
|
|
144
|
+
if (!normalized)
|
|
145
|
+
return false;
|
|
146
|
+
if (/\b(fail(?:ed|ing)?|error|blocked|mixed|untested)\b/.test(normalized))
|
|
147
|
+
return false;
|
|
148
|
+
if (/\b(?:not|never|no|didn't|did\s+not|cannot|can't|won't|couldn't)\s+(?:\w+\s+){0,3}?(?:pass(?:ed|ing)?|success(?:ful)?|succeeded)\b/.test(normalized)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return /\b(pass(?:ed|ing)?|success(?:ful)?|succeeded)\b/.test(normalized) || normalized === "all-pass";
|
|
152
|
+
}
|
|
153
|
+
function readTaskCompletionEvidenceFromSummary(basePath, row) {
|
|
154
|
+
if (!row.slice_id || !row.task_id)
|
|
155
|
+
return null;
|
|
156
|
+
if (!row.task_status && Number(row.task_count) > 0 && !taskExistsInPlan(basePath, row.milestone_id, row.slice_id, row.task_id)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const diskPath = resolveArtifactDiskPath(basePath, row.path);
|
|
160
|
+
if (!diskPath)
|
|
161
|
+
return null;
|
|
162
|
+
try {
|
|
163
|
+
const fullSummaryMd = readFileSync(diskPath, "utf-8");
|
|
164
|
+
const summary = parseSummary(fullSummaryMd);
|
|
165
|
+
const fm = summary.frontmatter;
|
|
166
|
+
if (fm.id !== row.task_id || fm.parent !== row.slice_id || fm.milestone !== row.milestone_id)
|
|
167
|
+
return null;
|
|
168
|
+
if (fm.blocker_discovered)
|
|
169
|
+
return null;
|
|
170
|
+
if (!isPassingVerificationResult(fm.verification_result))
|
|
171
|
+
return null;
|
|
172
|
+
const completedAt = fm.completed_at.trim();
|
|
173
|
+
if (!completedAt || !Number.isFinite(Date.parse(completedAt)))
|
|
174
|
+
return null;
|
|
175
|
+
return {
|
|
176
|
+
completedAt,
|
|
177
|
+
verificationResult: fm.verification_result.trim(),
|
|
178
|
+
title: summary.title.replace(new RegExp(`^${escapeRegExp(row.task_id)}:\\s*`), "").trim(),
|
|
179
|
+
oneLiner: summary.oneLiner,
|
|
180
|
+
narrative: summary.whatHappened,
|
|
181
|
+
duration: fm.duration,
|
|
182
|
+
blockerDiscovered: fm.blocker_discovered,
|
|
183
|
+
deviations: summary.deviations,
|
|
184
|
+
knownIssues: summary.knownLimitations,
|
|
185
|
+
keyFiles: fm.key_files.filter((file) => file !== "(none)"),
|
|
186
|
+
keyDecisions: fm.key_decisions.filter((decision) => decision !== "(none)"),
|
|
187
|
+
fullSummaryMd,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function repairTaskArtifactDbStatusDivergence(basePath, row) {
|
|
195
|
+
const evidence = readTaskCompletionEvidenceFromSummary(basePath, row);
|
|
196
|
+
if (!evidence || !row.slice_id || !row.task_id)
|
|
197
|
+
return false;
|
|
198
|
+
repairTaskCompletionFromSummary({
|
|
199
|
+
milestoneId: row.milestone_id,
|
|
200
|
+
sliceId: row.slice_id,
|
|
201
|
+
taskId: row.task_id,
|
|
202
|
+
...evidence,
|
|
203
|
+
});
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
function artifactDbStatusDivergenceFixable(basePath, row) {
|
|
207
|
+
return readTaskCompletionEvidenceFromSummary(basePath, row) !== null;
|
|
94
208
|
}
|
|
95
209
|
function artifactUnitId(row) {
|
|
96
210
|
if (!row.milestone_id)
|
|
@@ -110,7 +224,39 @@ function artifactScope(row) {
|
|
|
110
224
|
return "milestone";
|
|
111
225
|
return "project";
|
|
112
226
|
}
|
|
113
|
-
|
|
227
|
+
function sameArtifactIdentity(left, right) {
|
|
228
|
+
return left.artifact_type === right.artifact_type &&
|
|
229
|
+
left.milestone_id === right.milestone_id &&
|
|
230
|
+
left.slice_id === right.slice_id &&
|
|
231
|
+
left.task_id === right.task_id;
|
|
232
|
+
}
|
|
233
|
+
function isMilestonesArtifactPath(artifactPath) {
|
|
234
|
+
return artifactPathRelativeToGsd(artifactPath).startsWith("milestones/");
|
|
235
|
+
}
|
|
236
|
+
function expectedMilestonesArtifactPath(row) {
|
|
237
|
+
if (!row.milestone_id)
|
|
238
|
+
return null;
|
|
239
|
+
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
240
|
+
if (!artifactType)
|
|
241
|
+
return null;
|
|
242
|
+
if (row.slice_id && row.task_id) {
|
|
243
|
+
return `milestones/${row.milestone_id}/slices/${row.slice_id}/tasks/${row.task_id}-${artifactType}.md`;
|
|
244
|
+
}
|
|
245
|
+
if (row.slice_id) {
|
|
246
|
+
return `milestones/${row.milestone_id}/slices/${row.slice_id}/${row.slice_id}-${artifactType}.md`;
|
|
247
|
+
}
|
|
248
|
+
return `milestones/${row.milestone_id}/${row.milestone_id}-${artifactType}.md`;
|
|
249
|
+
}
|
|
250
|
+
function hasPresentMilestonesReplacement(basePath, row, artifactRows) {
|
|
251
|
+
const expectedPath = expectedMilestonesArtifactPath(row);
|
|
252
|
+
if (expectedPath && artifactExistsOnDisk(basePath, expectedPath))
|
|
253
|
+
return true;
|
|
254
|
+
return artifactRows.some((other) => other.path !== row.path &&
|
|
255
|
+
isMilestonesArtifactPath(other.path) &&
|
|
256
|
+
sameArtifactIdentity(row, other) &&
|
|
257
|
+
artifactExistsOnDisk(basePath, other.path));
|
|
258
|
+
}
|
|
259
|
+
export async function checkEngineHealth(basePath, issues, fixesApplied, options) {
|
|
114
260
|
const dbPath = resolveGsdPathContract(basePath).projectDb;
|
|
115
261
|
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
116
262
|
issues.push({
|
|
@@ -301,6 +447,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
301
447
|
// Non-fatal — completed-milestone reopen check failed
|
|
302
448
|
}
|
|
303
449
|
// f. Artifact rows reference files that no longer exist on disk.
|
|
450
|
+
const missingUserContentArtifacts = [];
|
|
304
451
|
try {
|
|
305
452
|
const artifactRows = adapter
|
|
306
453
|
.prepare(`SELECT path, artifact_type, milestone_id, slice_id, task_id
|
|
@@ -312,20 +459,48 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
312
459
|
if (artifactExistsOnDisk(basePath, row.path))
|
|
313
460
|
continue;
|
|
314
461
|
const unitId = artifactUnitId(row);
|
|
462
|
+
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
463
|
+
if (options?.repair && issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows)) {
|
|
464
|
+
// Route the write through the Single Writer owner (gsd-db.ts) instead
|
|
465
|
+
// of issuing raw DELETE SQL here — doctor is a read-only consumer and
|
|
466
|
+
// the single-writer invariant forbids write SQL outside the allowlist.
|
|
467
|
+
deleteArtifactByPath(row.path);
|
|
468
|
+
fixesApplied.push(`pruned stale flat-phase artifact row ${row.path}`);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (isUserAuthoredArtifactType(row.artifact_type)) {
|
|
472
|
+
const artifactType = normalizedArtifactType(row.artifact_type);
|
|
473
|
+
missingUserContentArtifacts.push({ path: issuePath, artifactType });
|
|
474
|
+
issues.push({
|
|
475
|
+
severity: "warning",
|
|
476
|
+
code: "artifact_user_content_missing",
|
|
477
|
+
scope: artifactScope(row),
|
|
478
|
+
unitId,
|
|
479
|
+
message: userContentMissingMessage(issuePath, artifactType),
|
|
480
|
+
file: issuePath,
|
|
481
|
+
fixable: false,
|
|
482
|
+
});
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
315
485
|
issues.push({
|
|
316
486
|
severity: "error",
|
|
317
487
|
code: "artifact_file_missing",
|
|
318
488
|
scope: artifactScope(row),
|
|
319
489
|
unitId,
|
|
320
|
-
message: `Artifact ${
|
|
321
|
-
file:
|
|
322
|
-
fixable:
|
|
490
|
+
message: `Artifact ${issuePath} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
|
|
491
|
+
file: issuePath,
|
|
492
|
+
fixable: issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows),
|
|
323
493
|
});
|
|
324
494
|
}
|
|
325
495
|
}
|
|
326
496
|
catch {
|
|
327
497
|
// Non-fatal — artifact file existence check failed
|
|
328
498
|
}
|
|
499
|
+
if (options?.repair) {
|
|
500
|
+
for (const artifact of missingUserContentArtifacts) {
|
|
501
|
+
fixesApplied.push(`skipped user-authored ${artifact.artifactType} artifact ${artifact.path} (content cannot be regenerated from the database)`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
329
504
|
// g. Completion artifacts disagree with open DB hierarchy rows.
|
|
330
505
|
try {
|
|
331
506
|
const rows = adapter
|
|
@@ -361,13 +536,30 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
361
536
|
if (seen.has(unitId))
|
|
362
537
|
continue;
|
|
363
538
|
seen.add(unitId);
|
|
539
|
+
const fixable = artifactDbStatusDivergenceFixable(basePath, row);
|
|
540
|
+
let repairFailed = false;
|
|
541
|
+
if (options?.repair && fixable) {
|
|
542
|
+
try {
|
|
543
|
+
if (repairTaskArtifactDbStatusDivergence(basePath, row)) {
|
|
544
|
+
fixesApplied.push(`repaired task completion from SUMMARY artifact for ${unitId}`);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
repairFailed = true;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
364
552
|
issues.push({
|
|
365
553
|
severity: "error",
|
|
366
554
|
code: "artifact_db_status_divergence",
|
|
367
555
|
scope: row.task_id ? "task" : row.slice_id ? "slice" : "milestone",
|
|
368
556
|
unitId,
|
|
369
|
-
message:
|
|
370
|
-
|
|
557
|
+
message: repairFailed
|
|
558
|
+
? `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Doctor found valid SUMMARY completion evidence but could not repair the database state.`
|
|
559
|
+
: fixable
|
|
560
|
+
? `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Doctor can repair this from the SUMMARY completion evidence.`
|
|
561
|
+
: `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Runtime will not import it silently; run explicit recovery/repair after review.`,
|
|
562
|
+
fixable,
|
|
371
563
|
});
|
|
372
564
|
}
|
|
373
565
|
}
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { existsSync, readFileSync } from "node:fs";
|
|
14
14
|
import { access, readFile } from "node:fs/promises";
|
|
15
|
+
import { homedir } from "node:os";
|
|
15
16
|
import { delimiter, join } from "node:path";
|
|
16
17
|
import { AuthStorage } from "@gsd/pi-coding-agent";
|
|
17
18
|
import { getEnvApiKey } from "@gsd/pi-ai";
|
|
19
|
+
import { isCursorAgentReadyUncached } from "../cursor-cli/readiness.js";
|
|
18
20
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
19
21
|
import { getAuthPath, PROVIDER_REGISTRY, supportsBrowserOAuth } from "./key-manager.js";
|
|
20
|
-
import { homedir } from "node:os";
|
|
21
22
|
// ── Provider routing constants ────────────────────────────────────────────────
|
|
22
23
|
/**
|
|
23
24
|
* Providers that use external CLI authentication (not API keys).
|
|
@@ -27,6 +28,7 @@ const CLI_AUTH_PROVIDERS = new Set([
|
|
|
27
28
|
"claude-code",
|
|
28
29
|
"google-gemini-cli",
|
|
29
30
|
"google-antigravity",
|
|
31
|
+
"cursor-agent",
|
|
30
32
|
]);
|
|
31
33
|
/**
|
|
32
34
|
* Providers that can serve models normally associated with another provider.
|
|
@@ -34,9 +36,9 @@ const CLI_AUTH_PROVIDERS = new Set([
|
|
|
34
36
|
* e.g. GitHub Copilot subscriptions can access Claude and GPT models.
|
|
35
37
|
*/
|
|
36
38
|
const PROVIDER_ROUTES = {
|
|
37
|
-
anthropic: ["github-copilot", "claude-code"],
|
|
38
|
-
openai: ["github-copilot", "openai-codex"],
|
|
39
|
-
google: ["google-antigravity", "google-gemini-cli"],
|
|
39
|
+
anthropic: ["github-copilot", "claude-code", "cursor-agent"],
|
|
40
|
+
openai: ["github-copilot", "openai-codex", "cursor-agent"],
|
|
41
|
+
google: ["google-antigravity", "google-gemini-cli", "cursor-agent"],
|
|
40
42
|
};
|
|
41
43
|
// ── Model → Provider ID mapping ───────────────────────────────────────────────
|
|
42
44
|
/**
|
|
@@ -61,6 +63,7 @@ function modelToProviderId(model) {
|
|
|
61
63
|
anthropic: "anthropic",
|
|
62
64
|
openai: "openai",
|
|
63
65
|
"github-copilot": "github-copilot",
|
|
66
|
+
"cursor-agent": "cursor-agent",
|
|
64
67
|
};
|
|
65
68
|
if (prefixMap[prefix])
|
|
66
69
|
return prefixMap[prefix];
|
|
@@ -73,6 +76,8 @@ function modelToProviderId(model) {
|
|
|
73
76
|
return "openai";
|
|
74
77
|
if (lower.startsWith("gemini"))
|
|
75
78
|
return "google";
|
|
79
|
+
if (lower.startsWith("composer"))
|
|
80
|
+
return "cursor-agent";
|
|
76
81
|
if (lower.startsWith("llama") || lower.startsWith("mixtral"))
|
|
77
82
|
return "groq";
|
|
78
83
|
if (lower.startsWith("grok"))
|
|
@@ -129,10 +134,12 @@ const CLI_BINARY_MAP = {
|
|
|
129
134
|
"claude-code": ["claude", "claude-code"],
|
|
130
135
|
"google-gemini-cli": ["gemini"],
|
|
131
136
|
"google-antigravity": ["agy"],
|
|
137
|
+
"cursor-agent": ["cursor-agent"],
|
|
132
138
|
};
|
|
133
139
|
const CLI_AUTH_PATH_CHECK_PROVIDERS = new Set([
|
|
134
140
|
"google-gemini-cli",
|
|
135
141
|
"google-antigravity",
|
|
142
|
+
"cursor-agent",
|
|
136
143
|
]);
|
|
137
144
|
let asyncCliBinaryPathCache = null;
|
|
138
145
|
function cliExecutableNames(providerId) {
|
|
@@ -173,6 +180,17 @@ function isCliBinaryInPath(providerId) {
|
|
|
173
180
|
const pathDirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
174
181
|
return pathDirs.some(dir => executableNames.some(name => existsSync(join(dir, name))));
|
|
175
182
|
}
|
|
183
|
+
function isExternalCliProviderReady(providerId) {
|
|
184
|
+
if (providerId === "cursor-agent")
|
|
185
|
+
return isCursorAgentReadyUncached();
|
|
186
|
+
return isCliBinaryInPath(providerId);
|
|
187
|
+
}
|
|
188
|
+
function cursorAgentFailureDetail() {
|
|
189
|
+
if (!isCliBinaryInPath("cursor-agent")) {
|
|
190
|
+
return "Install Cursor Agent and ensure `cursor-agent` is on PATH";
|
|
191
|
+
}
|
|
192
|
+
return "Run `cursor-agent login` or set CURSOR_API_KEY";
|
|
193
|
+
}
|
|
176
194
|
async function isCliBinaryInPathAsync(providerId) {
|
|
177
195
|
const executableNames = cliExecutableNames(providerId);
|
|
178
196
|
if (executableNames.length === 0)
|
|
@@ -293,6 +311,11 @@ function resolveKeyFromAuthOrEnv(providerId) {
|
|
|
293
311
|
return null;
|
|
294
312
|
}
|
|
295
313
|
function resolveKey(providerId) {
|
|
314
|
+
if (providerId === "cursor-agent") {
|
|
315
|
+
return isExternalCliProviderReady(providerId)
|
|
316
|
+
? { found: true, source: "env", backedOff: false }
|
|
317
|
+
: { found: false, source: "none", backedOff: false };
|
|
318
|
+
}
|
|
296
319
|
const direct = resolveKeyFromAuthOrEnv(providerId);
|
|
297
320
|
if (direct)
|
|
298
321
|
return direct;
|
|
@@ -301,7 +324,7 @@ function resolveKey(providerId) {
|
|
|
301
324
|
}
|
|
302
325
|
// Cross-provider routes can use a local CLI when it is installed. Explicit
|
|
303
326
|
// external CLI provider selections are handled in checkLlmProviders() below.
|
|
304
|
-
if (CLI_AUTH_PROVIDERS.has(providerId) &&
|
|
327
|
+
if (CLI_AUTH_PROVIDERS.has(providerId) && isExternalCliProviderReady(providerId)) {
|
|
305
328
|
return { found: true, source: "env", backedOff: false };
|
|
306
329
|
}
|
|
307
330
|
return { found: false, source: "none", backedOff: false };
|
|
@@ -316,17 +339,22 @@ function checkLlmProviders() {
|
|
|
316
339
|
if (CLI_AUTH_PROVIDERS.has(providerId)) {
|
|
317
340
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
318
341
|
const label = info?.label ?? providerId;
|
|
319
|
-
if (CLI_AUTH_PATH_CHECK_PROVIDERS.has(providerId) && !
|
|
342
|
+
if (CLI_AUTH_PATH_CHECK_PROVIDERS.has(providerId) && !isExternalCliProviderReady(providerId)) {
|
|
320
343
|
const binaries = CLI_BINARY_MAP[providerId]?.map(binary => `\`${binary}\``).join(" or ");
|
|
344
|
+
const detail = providerId === "cursor-agent"
|
|
345
|
+
? cursorAgentFailureDetail()
|
|
346
|
+
: binaries
|
|
347
|
+
? `Install ${label} and ensure ${binaries} is on PATH`
|
|
348
|
+
: `Install ${label} and ensure its CLI is on PATH`;
|
|
321
349
|
results.push({
|
|
322
350
|
name: providerId,
|
|
323
351
|
label,
|
|
324
352
|
category: "llm",
|
|
325
353
|
status: "error",
|
|
326
|
-
message:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
354
|
+
message: providerId === "cursor-agent" && isCliBinaryInPath(providerId)
|
|
355
|
+
? `${label} — CLI not authenticated`
|
|
356
|
+
: `${label} — CLI not found`,
|
|
357
|
+
detail,
|
|
330
358
|
required: true,
|
|
331
359
|
});
|
|
332
360
|
continue;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
3
4
|
import { loadFile, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
4
5
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
5
6
|
import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
|
|
@@ -12,6 +13,8 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
|
12
13
|
import { collectPreferenceDiagnostics, formatPreferenceDiagnosticDetail } from "./preferences-diagnostics.js";
|
|
13
14
|
import { isClosedStatus } from "./status-guards.js";
|
|
14
15
|
import { GLOBAL_STATE_CODES } from "./doctor-types.js";
|
|
16
|
+
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
17
|
+
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
15
18
|
import { checkGitHealth, checkRuntimeHealth, checkGlobalHealth, checkEngineHealth } from "./doctor-checks.js";
|
|
16
19
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
17
20
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
@@ -281,6 +284,92 @@ export async function readDoctorHistory(basePath, lastN = 50) {
|
|
|
281
284
|
return [];
|
|
282
285
|
}
|
|
283
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Resolve the git working-tree root for a path, or null if it is not a repo.
|
|
289
|
+
* Used by the workspace-repository probe to check that a declared child path is
|
|
290
|
+
* itself a git repository (not merely nested inside the parent's repo).
|
|
291
|
+
*/
|
|
292
|
+
function resolveGitToplevel(cwd) {
|
|
293
|
+
try {
|
|
294
|
+
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
295
|
+
cwd,
|
|
296
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
297
|
+
encoding: "utf-8",
|
|
298
|
+
env: GIT_NO_PROMPT_ENV,
|
|
299
|
+
}).trim();
|
|
300
|
+
return out ? resolve(out) : null;
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/** realpath that falls back to the resolved path if the link cannot be read. */
|
|
307
|
+
function realpathSafe(p) {
|
|
308
|
+
try {
|
|
309
|
+
return realpathSync(p);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return resolve(p);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Parent-workspace probe (#818): validate that every declared child repository
|
|
317
|
+
* exists on disk and is a git repository at its own root. The registry only
|
|
318
|
+
* checks paths stay inside the project root; it never checks existence or
|
|
319
|
+
* git-ness, so a typo'd path (e.g. `frontned`) would otherwise build cleanly
|
|
320
|
+
* and silently produce "no problems found." No-op for single-repo projects.
|
|
321
|
+
*/
|
|
322
|
+
function checkWorkspaceRepositoryHealth(basePath, prefs, issues) {
|
|
323
|
+
if (prefs?.workspace?.mode !== "parent")
|
|
324
|
+
return;
|
|
325
|
+
let registry;
|
|
326
|
+
try {
|
|
327
|
+
registry = createRepositoryRegistryFromPreferences(basePath, prefs);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
issues.push({
|
|
331
|
+
severity: "error",
|
|
332
|
+
code: "invalid_preferences",
|
|
333
|
+
scope: "project",
|
|
334
|
+
unitId: "workspace",
|
|
335
|
+
message: `workspace registry failed to build: ${err instanceof Error ? err.message : String(err)}`,
|
|
336
|
+
fixable: false,
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
for (const repo of registry.repositories) {
|
|
341
|
+
if (repo.id === "project")
|
|
342
|
+
continue;
|
|
343
|
+
if (!existsSync(repo.root)) {
|
|
344
|
+
issues.push({
|
|
345
|
+
severity: "error",
|
|
346
|
+
code: "workspace_repo_path_missing",
|
|
347
|
+
scope: "project",
|
|
348
|
+
unitId: `workspace.repositories.${repo.id}`,
|
|
349
|
+
message: `declared repository "${repo.id}" path does not exist on disk: ${repo.root}`,
|
|
350
|
+
fixable: false,
|
|
351
|
+
});
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
// Must be a repo at its OWN root — a plain dir nested in the parent repo
|
|
355
|
+
// would otherwise pass a bare `nativeIsRepo` check via the enclosing
|
|
356
|
+
// parent's .git. Compare on realpath so symlinked temp roots (macOS
|
|
357
|
+
// /var vs /private/var) don't cause a false mismatch.
|
|
358
|
+
const toplevel = resolveGitToplevel(repo.root);
|
|
359
|
+
const declaredReal = realpathSafe(repo.root);
|
|
360
|
+
const toplevelReal = toplevel ? realpathSafe(toplevel) : null;
|
|
361
|
+
if (toplevelReal !== declaredReal) {
|
|
362
|
+
issues.push({
|
|
363
|
+
severity: "warning",
|
|
364
|
+
code: "workspace_repo_not_a_repo",
|
|
365
|
+
scope: "project",
|
|
366
|
+
unitId: `workspace.repositories.${repo.id}`,
|
|
367
|
+
message: `declared repository "${repo.id}" path is not a git repository at its own root: ${repo.root}`,
|
|
368
|
+
fixable: false,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
284
373
|
export async function runGSDDoctor(basePath, options) {
|
|
285
374
|
const issues = [];
|
|
286
375
|
const fixesApplied = [];
|
|
@@ -336,6 +425,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
336
425
|
(prefs?.preferences?.git?.isolation === "worktree" ? "worktree" :
|
|
337
426
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "none");
|
|
338
427
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode, dryRun);
|
|
428
|
+
checkWorkspaceRepositoryHealth(basePath, prefs?.preferences, issues);
|
|
339
429
|
const gitMs = Date.now() - t0git;
|
|
340
430
|
// Runtime health checks — timed
|
|
341
431
|
const t0runtime = Date.now();
|
|
@@ -352,7 +442,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
352
442
|
});
|
|
353
443
|
const envMs = Date.now() - t0env;
|
|
354
444
|
// Engine health checks — DB constraints and projection drift
|
|
355
|
-
await checkEngineHealth(basePath, issues, fixesApplied);
|
|
445
|
+
await checkEngineHealth(basePath, issues, fixesApplied, { repair: fix && !dryRun });
|
|
356
446
|
const milestonesPath = milestonesDir(basePath);
|
|
357
447
|
const legacyMilestonesPath2 = legacyMilestonesDir(basePath);
|
|
358
448
|
if (!existsSync(milestonesPath) && !existsSync(legacyMilestonesPath2)) {
|
|
@@ -12,6 +12,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { resolve } from "node:path";
|
|
14
14
|
import { getShellConfig, killProcessTree, SIGKILL_GRACE_MS, HARD_DEADLINE_MS } from "@gsd/pi-coding-agent";
|
|
15
|
+
import { redactSecrets } from "./redact-secrets.js";
|
|
15
16
|
const ALWAYS_FORWARD_ENV = ["PATH", "HOME"];
|
|
16
17
|
// SIGKILL_GRACE_MS / HARD_DEADLINE_MS are imported from @gsd/pi-coding-agent
|
|
17
18
|
// (shell.ts) — the single source of truth for the graceful-kill timing ladder —
|
|
@@ -232,8 +233,11 @@ export function runExecSandbox(request, opts) {
|
|
|
232
233
|
const stderrBuf = Buffer.concat(stderrChunks);
|
|
233
234
|
const stdoutSuffix = stdoutTruncated ? "\n[truncated: stdout cap reached]\n" : "";
|
|
234
235
|
const stderrSuffix = stderrTruncated ? "\n[truncated: stderr cap reached]\n" : "";
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
// Redact secret-shaped substrings before persisting to .gsd/exec/ (skipped by
|
|
237
|
+
// the secret scanner). The returned digest is derived from the raw buffer
|
|
238
|
+
// below and is redacted downstream when the session is written to activity/.
|
|
239
|
+
writeFileSync(stdoutPath, redactSecrets(Buffer.concat([stdoutBuf, Buffer.from(stdoutSuffix, "utf-8")]).toString("utf-8")));
|
|
240
|
+
writeFileSync(stderrPath, redactSecrets(Buffer.concat([stderrBuf, Buffer.from(stderrSuffix, "utf-8")]).toString("utf-8")));
|
|
237
241
|
const digestBody = tail(stdoutBuf, opts.digest_chars);
|
|
238
242
|
const digest = digestBody.length > 0
|
|
239
243
|
? digestBody
|