@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
|
@@ -1014,6 +1014,143 @@ test("runProviderChecks detects claude.exe in PATH on Windows (#4548)", { skip:
|
|
|
1014
1014
|
});
|
|
1015
1015
|
});
|
|
1016
1016
|
|
|
1017
|
+
|
|
1018
|
+
test("runProviderChecks reports error for required cursor-agent when binary is missing", () => {
|
|
1019
|
+
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-repo-")));
|
|
1020
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
1021
|
+
writeFileSync(
|
|
1022
|
+
join(repo, ".gsd", "PREFERENCES.md"),
|
|
1023
|
+
[
|
|
1024
|
+
"---",
|
|
1025
|
+
"models:",
|
|
1026
|
+
" execution:",
|
|
1027
|
+
" model: composer-2.5",
|
|
1028
|
+
" provider: cursor-agent",
|
|
1029
|
+
"---",
|
|
1030
|
+
"",
|
|
1031
|
+
].join("\n"),
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-home-")));
|
|
1035
|
+
|
|
1036
|
+
withEnv({
|
|
1037
|
+
HOME: tmpHome,
|
|
1038
|
+
PATH: tmpHome,
|
|
1039
|
+
CURSOR_API_KEY: undefined,
|
|
1040
|
+
}, () => {
|
|
1041
|
+
withCwd(repo, () => {
|
|
1042
|
+
const results = runProviderChecks();
|
|
1043
|
+
const cursor = results.find(r => r.name === "cursor-agent");
|
|
1044
|
+
assert.ok(cursor, "cursor-agent result should exist");
|
|
1045
|
+
assert.equal(cursor!.status, "error", "cursor-agent should error when the CLI binary is missing");
|
|
1046
|
+
assert.ok(cursor!.detail?.includes("Cursor Agent"), "should explain Cursor Agent must be installed");
|
|
1047
|
+
});
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1051
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
test("runProviderChecks does not route OpenAI via unauthenticated cursor-agent binary in PATH", () => {
|
|
1055
|
+
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-route-repo-")));
|
|
1056
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
1057
|
+
writeFileSync(
|
|
1058
|
+
join(repo, ".gsd", "PREFERENCES.md"),
|
|
1059
|
+
[
|
|
1060
|
+
"---",
|
|
1061
|
+
"models:",
|
|
1062
|
+
" execution: gpt-5.5",
|
|
1063
|
+
"---",
|
|
1064
|
+
"",
|
|
1065
|
+
].join("\n"),
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-route-home-")));
|
|
1069
|
+
const binDir = join(tmpHome, "bin");
|
|
1070
|
+
mkdirSync(binDir, { recursive: true });
|
|
1071
|
+
const fakeCursor = join(binDir, "cursor-agent");
|
|
1072
|
+
writeFileSync(fakeCursor, [
|
|
1073
|
+
"#!/bin/sh",
|
|
1074
|
+
"if [ \"$1\" = \"status\" ]; then echo \"Not logged in\"; exit 0; fi",
|
|
1075
|
+
"echo mock",
|
|
1076
|
+
"",
|
|
1077
|
+
].join("\n"));
|
|
1078
|
+
chmodSync(fakeCursor, 0o755);
|
|
1079
|
+
|
|
1080
|
+
withEnv({
|
|
1081
|
+
HOME: tmpHome,
|
|
1082
|
+
OPENAI_API_KEY: undefined,
|
|
1083
|
+
COPILOT_GITHUB_TOKEN: undefined,
|
|
1084
|
+
GH_TOKEN: undefined,
|
|
1085
|
+
GITHUB_TOKEN: undefined,
|
|
1086
|
+
PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`,
|
|
1087
|
+
}, () => {
|
|
1088
|
+
try {
|
|
1089
|
+
withCwd(repo, () => {
|
|
1090
|
+
const results = runProviderChecks();
|
|
1091
|
+
const openai = results.find(r => r.name === "openai");
|
|
1092
|
+
assert.ok(openai, "openai result should exist");
|
|
1093
|
+
assert.equal(openai!.status, "error", "binary-only cursor-agent must not satisfy OpenAI routing");
|
|
1094
|
+
assert.ok(openai!.detail?.includes("OPENAI_API_KEY"), "should still tell the user how to configure OpenAI");
|
|
1095
|
+
});
|
|
1096
|
+
} finally {
|
|
1097
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1098
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
test("runProviderChecks routes OpenAI via authenticated cursor-agent CLI", () => {
|
|
1104
|
+
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-auth-route-repo-")));
|
|
1105
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
1106
|
+
writeFileSync(
|
|
1107
|
+
join(repo, ".gsd", "PREFERENCES.md"),
|
|
1108
|
+
[
|
|
1109
|
+
"---",
|
|
1110
|
+
"models:",
|
|
1111
|
+
" execution: gpt-5.5",
|
|
1112
|
+
"---",
|
|
1113
|
+
"",
|
|
1114
|
+
].join("\n"),
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-auth-route-home-")));
|
|
1118
|
+
const binDir = join(tmpHome, "bin");
|
|
1119
|
+
mkdirSync(binDir, { recursive: true });
|
|
1120
|
+
const fakeCursor = join(binDir, "cursor-agent");
|
|
1121
|
+
writeFileSync(fakeCursor, [
|
|
1122
|
+
"#!/bin/sh",
|
|
1123
|
+
"if [ \"$1\" = \"--version\" ]; then echo cursor-agent 1.0; exit 0; fi",
|
|
1124
|
+
"if [ \"$1\" = \"status\" ]; then echo \"Authenticated as user@example.com\"; exit 0; fi",
|
|
1125
|
+
"echo mock",
|
|
1126
|
+
"",
|
|
1127
|
+
].join("\n"));
|
|
1128
|
+
chmodSync(fakeCursor, 0o755);
|
|
1129
|
+
|
|
1130
|
+
withEnv({
|
|
1131
|
+
HOME: tmpHome,
|
|
1132
|
+
OPENAI_API_KEY: undefined,
|
|
1133
|
+
COPILOT_GITHUB_TOKEN: undefined,
|
|
1134
|
+
GH_TOKEN: undefined,
|
|
1135
|
+
GITHUB_TOKEN: undefined,
|
|
1136
|
+
CURSOR_API_KEY: undefined,
|
|
1137
|
+
PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`,
|
|
1138
|
+
}, () => {
|
|
1139
|
+
try {
|
|
1140
|
+
withCwd(repo, () => {
|
|
1141
|
+
const results = runProviderChecks();
|
|
1142
|
+
const openai = results.find(r => r.name === "openai");
|
|
1143
|
+
assert.ok(openai, "openai result should exist");
|
|
1144
|
+
assert.equal(openai!.status, "ok", "authenticated cursor-agent should satisfy OpenAI routing");
|
|
1145
|
+
assert.ok(openai!.message.toLowerCase().includes("cursor"), "should mention cursor-agent as source");
|
|
1146
|
+
});
|
|
1147
|
+
} finally {
|
|
1148
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1149
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1017
1154
|
test("PROVIDER_ROUTES includes google-antigravity and google-gemini-cli as routes for google (#2922)", async () => {
|
|
1018
1155
|
const { readFileSync: readFS } = await import("node:fs");
|
|
1019
1156
|
const { dirname: dirn, join: joinPath } = await import("node:path");
|
|
@@ -1022,7 +1159,7 @@ test("PROVIDER_ROUTES includes google-antigravity and google-gemini-cli as route
|
|
|
1022
1159
|
const src = readFS(joinPath(__dir, "..", "doctor-providers.ts"), "utf-8");
|
|
1023
1160
|
|
|
1024
1161
|
assert.ok(
|
|
1025
|
-
src.includes('google: ["google-antigravity", "google-gemini-cli"]'),
|
|
1162
|
+
src.includes('google: ["google-antigravity", "google-gemini-cli", "cursor-agent"]'),
|
|
1026
1163
|
'PROVIDER_ROUTES must prefer "google-antigravity" over "google-gemini-cli" for google (#2922)',
|
|
1027
1164
|
);
|
|
1028
1165
|
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
insertMilestone,
|
|
8
8
|
insertSlice,
|
|
9
9
|
insertTask,
|
|
10
|
+
getTask,
|
|
10
11
|
openDatabase,
|
|
11
12
|
} from "../gsd-db.ts";
|
|
12
13
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -314,7 +315,7 @@ test("checkEngineHealth reports artifact rows whose files are missing on disk",
|
|
|
314
315
|
});
|
|
315
316
|
insertArtifact({
|
|
316
317
|
path: "milestones/M001/M001-MISSING.md",
|
|
317
|
-
artifact_type: "
|
|
318
|
+
artifact_type: "PLAN",
|
|
318
319
|
milestone_id: "M001",
|
|
319
320
|
slice_id: null,
|
|
320
321
|
task_id: null,
|
|
@@ -335,6 +336,398 @@ test("checkEngineHealth reports artifact rows whose files are missing on disk",
|
|
|
335
336
|
assert.equal(missing.fixable, false);
|
|
336
337
|
});
|
|
337
338
|
|
|
339
|
+
test("checkEngineHealth resolves escaped .gsd artifact rows against the project .gsd directory", async (t) => {
|
|
340
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-escaped-artifact-"));
|
|
341
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
342
|
+
|
|
343
|
+
const gsdDir = join(base, ".gsd");
|
|
344
|
+
const artifactPath = "phases/01-m001/01-01-ASSESSMENT.md";
|
|
345
|
+
mkdirSync(join(gsdDir, "phases", "01-m001"), { recursive: true });
|
|
346
|
+
writeFileSync(join(gsdDir, artifactPath), "# Assessment\n", "utf-8");
|
|
347
|
+
|
|
348
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
349
|
+
insertArtifact({
|
|
350
|
+
path: `../../../Documents/Projects/project/.gsd/${artifactPath}`,
|
|
351
|
+
artifact_type: "ASSESSMENT",
|
|
352
|
+
milestone_id: "M001",
|
|
353
|
+
slice_id: "S01",
|
|
354
|
+
task_id: null,
|
|
355
|
+
full_content: "# Assessment\n",
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const issues: any[] = [];
|
|
359
|
+
await checkEngineHealth(base, issues, []);
|
|
360
|
+
|
|
361
|
+
assert.equal(
|
|
362
|
+
issues.some((issue) => issue.code === "artifact_file_missing"),
|
|
363
|
+
false,
|
|
364
|
+
"escaped .gsd paths should resolve to the project .gsd artifact",
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("checkEngineHealth reports escaped missing artifact rows with .gsd-relative paths", async (t) => {
|
|
369
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-escaped-missing-artifact-"));
|
|
370
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
371
|
+
|
|
372
|
+
const gsdDir = join(base, ".gsd");
|
|
373
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
374
|
+
|
|
375
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
376
|
+
insertArtifact({
|
|
377
|
+
path: "../../../Documents/Projects/project/.gsd/phases/01-m001/01-01-PLAN.md",
|
|
378
|
+
artifact_type: "PLAN",
|
|
379
|
+
milestone_id: "M001",
|
|
380
|
+
slice_id: "S01",
|
|
381
|
+
task_id: null,
|
|
382
|
+
full_content: "# Plan\n",
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const issues: any[] = [];
|
|
386
|
+
await checkEngineHealth(base, issues, []);
|
|
387
|
+
|
|
388
|
+
const issue = issues.find((candidate) => candidate.code === "artifact_file_missing");
|
|
389
|
+
assert.ok(issue, "missing escaped artifact row should still be reported");
|
|
390
|
+
assert.equal(issue.file, "phases/01-m001/01-01-PLAN.md");
|
|
391
|
+
assert.doesNotMatch(issue.message, /\.\.\//);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("checkEngineHealth repair prunes stale phases artifact rows with present milestones files", async (t) => {
|
|
395
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-stale-phase-artifact-"));
|
|
396
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
397
|
+
|
|
398
|
+
const gsdDir = join(base, ".gsd");
|
|
399
|
+
const stalePath = "phases/01-m001/01-01-PLAN.md";
|
|
400
|
+
const replacementPath = "milestones/M001/slices/S01/S01-PLAN.md";
|
|
401
|
+
mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01"), { recursive: true });
|
|
402
|
+
writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
|
|
403
|
+
|
|
404
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
405
|
+
insertArtifact({
|
|
406
|
+
path: stalePath,
|
|
407
|
+
artifact_type: "PLAN",
|
|
408
|
+
milestone_id: "M001",
|
|
409
|
+
slice_id: "S01",
|
|
410
|
+
task_id: null,
|
|
411
|
+
full_content: "# stale plan\n",
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const issues: any[] = [];
|
|
415
|
+
const fixes: string[] = [];
|
|
416
|
+
await checkEngineHealth(base, issues, fixes, { repair: true });
|
|
417
|
+
|
|
418
|
+
assert.equal(
|
|
419
|
+
issues.some((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath),
|
|
420
|
+
false,
|
|
421
|
+
"repair should not report stale rows it pruned",
|
|
422
|
+
);
|
|
423
|
+
assert.ok(fixes.includes(`pruned stale flat-phase artifact row ${stalePath}`));
|
|
424
|
+
|
|
425
|
+
const rows = _getAdapter()!
|
|
426
|
+
.prepare("SELECT path FROM artifacts ORDER BY path")
|
|
427
|
+
.all() as Array<{ path: string }>;
|
|
428
|
+
assert.deepEqual(rows.map((row) => row.path), []);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("checkEngineHealth repair prunes stale phases task rows against tasks/<T>-<TYPE>.md replacements", async (t) => {
|
|
432
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-stale-task-phase-artifact-"));
|
|
433
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
434
|
+
|
|
435
|
+
const gsdDir = join(base, ".gsd");
|
|
436
|
+
const stalePath = "phases/01-m001/01-01-T01-SUMMARY.md";
|
|
437
|
+
// Canonical legacy task layout has no per-task subdirectory: the SUMMARY lives
|
|
438
|
+
// at tasks/<T>-<TYPE>.md, not tasks/<T>/<T>-<TYPE>.md. If the expected-path
|
|
439
|
+
// builder adds an extra tasks/<T>/ segment, task-scoped stale rows never match
|
|
440
|
+
// their on-disk replacement and leak unpruned.
|
|
441
|
+
const replacementPath = "milestones/M001/slices/S01/tasks/T01-SUMMARY.md";
|
|
442
|
+
mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
|
|
443
|
+
writeFileSync(join(gsdDir, replacementPath), "# Summary\n", "utf-8");
|
|
444
|
+
|
|
445
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
446
|
+
insertArtifact({
|
|
447
|
+
path: stalePath,
|
|
448
|
+
artifact_type: "SUMMARY",
|
|
449
|
+
milestone_id: "M001",
|
|
450
|
+
slice_id: "S01",
|
|
451
|
+
task_id: "T01",
|
|
452
|
+
full_content: "# stale summary\n",
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const issues: any[] = [];
|
|
456
|
+
const fixes: string[] = [];
|
|
457
|
+
await checkEngineHealth(base, issues, fixes, { repair: true });
|
|
458
|
+
|
|
459
|
+
assert.equal(
|
|
460
|
+
issues.some((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath),
|
|
461
|
+
false,
|
|
462
|
+
"task-scoped stale rows should prune against the tasks/<T>-<TYPE>.md replacement",
|
|
463
|
+
);
|
|
464
|
+
assert.ok(fixes.includes(`pruned stale flat-phase artifact row ${stalePath}`));
|
|
465
|
+
|
|
466
|
+
const rows = _getAdapter()!
|
|
467
|
+
.prepare("SELECT path FROM artifacts ORDER BY path")
|
|
468
|
+
.all() as Array<{ path: string }>;
|
|
469
|
+
assert.deepEqual(rows.map((row) => row.path), []);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test("checkEngineHealth repair prunes stale phases rows stored as escaped ../ paths", async (t) => {
|
|
473
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-escaped-phase-artifact-"));
|
|
474
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
475
|
+
|
|
476
|
+
const gsdDir = join(base, ".gsd");
|
|
477
|
+
// Leaked row: the DB stores an escaped, absolute-ish path rather than a clean
|
|
478
|
+
// `phases/…` value. The prune must gate on the .gsd-relative path so the row
|
|
479
|
+
// this repair targets is not skipped just because the raw value starts with `../`.
|
|
480
|
+
const stalePath = "../../../Documents/Projects/project/.gsd/phases/01-m001/01-01-PLAN.md";
|
|
481
|
+
const replacementPath = "milestones/M001/slices/S01/S01-PLAN.md";
|
|
482
|
+
mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01"), { recursive: true });
|
|
483
|
+
writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
|
|
484
|
+
|
|
485
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
486
|
+
insertArtifact({
|
|
487
|
+
path: stalePath,
|
|
488
|
+
artifact_type: "PLAN",
|
|
489
|
+
milestone_id: "M001",
|
|
490
|
+
slice_id: "S01",
|
|
491
|
+
task_id: null,
|
|
492
|
+
full_content: "# stale plan\n",
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const issues: any[] = [];
|
|
496
|
+
const fixes: string[] = [];
|
|
497
|
+
await checkEngineHealth(base, issues, fixes, { repair: true });
|
|
498
|
+
|
|
499
|
+
assert.equal(
|
|
500
|
+
issues.some((issue) => issue.code === "artifact_file_missing"),
|
|
501
|
+
false,
|
|
502
|
+
"escaped phases rows should prune against the milestones replacement, not be reported missing",
|
|
503
|
+
);
|
|
504
|
+
assert.ok(
|
|
505
|
+
fixes.some((fix) => fix.startsWith("pruned stale flat-phase artifact row") && fix.includes("phases/01-m001/01-01-PLAN.md")),
|
|
506
|
+
"repair should record a prune for the escaped stale phases row",
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const rows = _getAdapter()!
|
|
510
|
+
.prepare("SELECT path FROM artifacts ORDER BY path")
|
|
511
|
+
.all() as Array<{ path: string }>;
|
|
512
|
+
assert.deepEqual(rows.map((row) => row.path), [], "escaped stale phases row should be deleted from the DB");
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("checkEngineHealth marks escaped phases rows fixable when a milestones replacement exists", async (t) => {
|
|
516
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-escaped-phase-fixable-"));
|
|
517
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
518
|
+
|
|
519
|
+
const gsdDir = join(base, ".gsd");
|
|
520
|
+
const replacementPath = "milestones/M001/slices/S01/S01-PLAN.md";
|
|
521
|
+
mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01"), { recursive: true });
|
|
522
|
+
writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
|
|
523
|
+
|
|
524
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
525
|
+
insertArtifact({
|
|
526
|
+
path: "../../../Documents/Projects/project/.gsd/phases/01-m001/01-01-PLAN.md",
|
|
527
|
+
artifact_type: "PLAN",
|
|
528
|
+
milestone_id: "M001",
|
|
529
|
+
slice_id: "S01",
|
|
530
|
+
task_id: null,
|
|
531
|
+
full_content: "# stale plan\n",
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const issues: any[] = [];
|
|
535
|
+
await checkEngineHealth(base, issues, []);
|
|
536
|
+
|
|
537
|
+
const issue = issues.find((candidate) => candidate.code === "artifact_file_missing");
|
|
538
|
+
assert.ok(issue, "escaped stale row should still be reported when repair is off");
|
|
539
|
+
assert.equal(issue.file, "phases/01-m001/01-01-PLAN.md");
|
|
540
|
+
assert.equal(issue.fixable, true, "escaped phases rows with a milestones replacement must be marked fixable");
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
function taskSummary(id: string, verificationResult = "passed"): string {
|
|
544
|
+
return [
|
|
545
|
+
"---",
|
|
546
|
+
`id: ${id}`,
|
|
547
|
+
"parent: S01",
|
|
548
|
+
"milestone: M001",
|
|
549
|
+
"key_files:",
|
|
550
|
+
" - src/example.ts",
|
|
551
|
+
"key_decisions:",
|
|
552
|
+
" - Keep recovery conservative",
|
|
553
|
+
"duration: 5m",
|
|
554
|
+
`verification_result: ${verificationResult}`,
|
|
555
|
+
"completed_at: 2026-01-01T00:00:00.000Z",
|
|
556
|
+
"blocker_discovered: false",
|
|
557
|
+
"---",
|
|
558
|
+
"",
|
|
559
|
+
`# ${id}: Done`,
|
|
560
|
+
"",
|
|
561
|
+
"**Recovered task completion.**",
|
|
562
|
+
"",
|
|
563
|
+
"## What Happened",
|
|
564
|
+
"",
|
|
565
|
+
"The task finished and wrote its summary.",
|
|
566
|
+
"",
|
|
567
|
+
"## Deviations",
|
|
568
|
+
"",
|
|
569
|
+
"None.",
|
|
570
|
+
"",
|
|
571
|
+
"## Known Issues",
|
|
572
|
+
"",
|
|
573
|
+
"None.",
|
|
574
|
+
"",
|
|
575
|
+
].join("\n");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
test("checkEngineHealth marks valid task artifact DB divergence fixable and repairs it", async (t) => {
|
|
579
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-artifact-db-repair-"));
|
|
580
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
581
|
+
|
|
582
|
+
const gsdDir = join(base, ".gsd");
|
|
583
|
+
const tasksDir = join(gsdDir, "milestones", "M001", "slices", "S01", "tasks");
|
|
584
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
585
|
+
|
|
586
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
587
|
+
insertMilestone({ id: "M001", title: "Foundation", status: "active" });
|
|
588
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active", risk: "low", depends: [], sequence: 1 });
|
|
589
|
+
insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "Task", status: "pending", sequence: 1 });
|
|
590
|
+
|
|
591
|
+
const relSummaryPath = "milestones/M001/slices/S01/tasks/T01-SUMMARY.md";
|
|
592
|
+
const summary = taskSummary("T01");
|
|
593
|
+
writeFileSync(join(gsdDir, relSummaryPath), summary, "utf-8");
|
|
594
|
+
insertArtifact({
|
|
595
|
+
path: relSummaryPath,
|
|
596
|
+
artifact_type: "SUMMARY",
|
|
597
|
+
milestone_id: "M001",
|
|
598
|
+
slice_id: "S01",
|
|
599
|
+
task_id: "T01",
|
|
600
|
+
full_content: summary,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
const detectIssues: any[] = [];
|
|
604
|
+
await checkEngineHealth(base, detectIssues, []);
|
|
605
|
+
|
|
606
|
+
const divergence = detectIssues.find((issue) => issue.code === "artifact_db_status_divergence" && issue.unitId === "M001/S01/T01");
|
|
607
|
+
assert.ok(divergence, "doctor should report the artifact/DB divergence");
|
|
608
|
+
assert.equal(divergence.fixable, true);
|
|
609
|
+
|
|
610
|
+
const repairIssues: any[] = [];
|
|
611
|
+
const fixes: string[] = [];
|
|
612
|
+
await checkEngineHealth(base, repairIssues, fixes, { repair: true });
|
|
613
|
+
|
|
614
|
+
assert.ok(
|
|
615
|
+
fixes.includes("repaired task completion from SUMMARY artifact for M001/S01/T01"),
|
|
616
|
+
"repair mode should report the task completion repair",
|
|
617
|
+
);
|
|
618
|
+
assert.equal(
|
|
619
|
+
repairIssues.some((issue) => issue.code === "artifact_db_status_divergence" && issue.unitId === "M001/S01/T01"),
|
|
620
|
+
false,
|
|
621
|
+
"repaired divergence should not be reported in the same doctor run",
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
const task = getTask("M001", "S01", "T01");
|
|
625
|
+
assert.equal(task?.status, "complete");
|
|
626
|
+
assert.equal(task?.completed_at, "2026-01-01T00:00:00.000Z");
|
|
627
|
+
assert.equal(task?.verification_result, "passed");
|
|
628
|
+
assert.match(task?.full_summary_md ?? "", /# T01: Done/);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test("checkEngineHealth keeps failed and negated-pass summaries non-fixable", async (t) => {
|
|
632
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-artifact-db-failure-"));
|
|
633
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
634
|
+
|
|
635
|
+
const gsdDir = join(base, ".gsd");
|
|
636
|
+
const tasksDir = join(gsdDir, "milestones", "M001", "slices", "S01", "tasks");
|
|
637
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
638
|
+
|
|
639
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
640
|
+
insertMilestone({ id: "M001", title: "Foundation", status: "active" });
|
|
641
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active", risk: "low", depends: [], sequence: 1 });
|
|
642
|
+
const nonPassingResults = [
|
|
643
|
+
["T02", "failed"],
|
|
644
|
+
["T03", "not passed"],
|
|
645
|
+
["T04", "not passing"],
|
|
646
|
+
] as const;
|
|
647
|
+
for (const [taskId, verificationResult] of nonPassingResults) {
|
|
648
|
+
insertTask({ id: taskId, milestoneId: "M001", sliceId: "S01", title: "Task", status: "pending", sequence: 1 });
|
|
649
|
+
|
|
650
|
+
const relSummaryPath = `milestones/M001/slices/S01/tasks/${taskId}-SUMMARY.md`;
|
|
651
|
+
const summary = taskSummary(taskId, verificationResult);
|
|
652
|
+
writeFileSync(join(gsdDir, relSummaryPath), summary, "utf-8");
|
|
653
|
+
insertArtifact({
|
|
654
|
+
path: relSummaryPath,
|
|
655
|
+
artifact_type: "SUMMARY",
|
|
656
|
+
milestone_id: "M001",
|
|
657
|
+
slice_id: "S01",
|
|
658
|
+
task_id: taskId,
|
|
659
|
+
full_content: summary,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const issues: any[] = [];
|
|
664
|
+
await checkEngineHealth(base, issues, []);
|
|
665
|
+
|
|
666
|
+
for (const [taskId] of nonPassingResults) {
|
|
667
|
+
const divergence = issues.find((issue) => issue.code === "artifact_db_status_divergence" && issue.unitId === `M001/S01/${taskId}`);
|
|
668
|
+
assert.ok(divergence, "doctor should still report non-passing summary drift");
|
|
669
|
+
assert.equal(divergence.fixable, false);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("checkEngineHealth reports missing CONTEXT and RESEARCH artifacts as user-content warnings", async (t) => {
|
|
674
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-user-content-artifact-"));
|
|
675
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
676
|
+
|
|
677
|
+
const gsdDir = join(base, ".gsd");
|
|
678
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
679
|
+
|
|
680
|
+
openDatabase(join(gsdDir, "gsd.db"));
|
|
681
|
+
insertArtifact({
|
|
682
|
+
path: "milestones/M002/M002-CONTEXT.md",
|
|
683
|
+
artifact_type: "CONTEXT",
|
|
684
|
+
milestone_id: "M002",
|
|
685
|
+
slice_id: null,
|
|
686
|
+
task_id: null,
|
|
687
|
+
full_content: "# Context\n",
|
|
688
|
+
});
|
|
689
|
+
insertArtifact({
|
|
690
|
+
path: "milestones/M002/M002-RESEARCH.md",
|
|
691
|
+
artifact_type: "RESEARCH",
|
|
692
|
+
milestone_id: "M002",
|
|
693
|
+
slice_id: null,
|
|
694
|
+
task_id: null,
|
|
695
|
+
full_content: "# Research\n",
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const issues: any[] = [];
|
|
699
|
+
const fixes: string[] = [];
|
|
700
|
+
await checkEngineHealth(base, issues, fixes, { repair: true });
|
|
701
|
+
|
|
702
|
+
assert.equal(
|
|
703
|
+
issues.some((issue) => issue.code === "artifact_file_missing"),
|
|
704
|
+
false,
|
|
705
|
+
"missing user-authored artifacts should not be reported as blocking projection errors",
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
const contextIssue = issues.find((issue) => issue.code === "artifact_user_content_missing" && issue.file === "milestones/M002/M002-CONTEXT.md");
|
|
709
|
+
assert.ok(contextIssue, "missing CONTEXT should use the user-content code");
|
|
710
|
+
assert.equal(contextIssue.severity, "warning");
|
|
711
|
+
assert.equal(contextIssue.unitId, "M002");
|
|
712
|
+
assert.equal(
|
|
713
|
+
contextIssue.message,
|
|
714
|
+
"Artifact `milestones/M002/M002-CONTEXT.md` is a user-authored CONTEXT file recorded in the database but missing from disk. Re-run `/gsd discuss` in this milestone to regenerate it.",
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
const researchIssue = issues.find((issue) => issue.code === "artifact_user_content_missing" && issue.file === "milestones/M002/M002-RESEARCH.md");
|
|
718
|
+
assert.ok(researchIssue, "missing RESEARCH should use the user-content code");
|
|
719
|
+
assert.equal(researchIssue.severity, "warning");
|
|
720
|
+
|
|
721
|
+
assert.ok(
|
|
722
|
+
fixes.some((fix) => fix === "skipped user-authored CONTEXT artifact milestones/M002/M002-CONTEXT.md (content cannot be regenerated from the database)"),
|
|
723
|
+
"repair output should explain skipped CONTEXT content",
|
|
724
|
+
);
|
|
725
|
+
assert.ok(
|
|
726
|
+
fixes.some((fix) => fix === "skipped user-authored RESEARCH artifact milestones/M002/M002-RESEARCH.md (content cannot be regenerated from the database)"),
|
|
727
|
+
"repair output should explain skipped RESEARCH content",
|
|
728
|
+
);
|
|
729
|
+
});
|
|
730
|
+
|
|
338
731
|
test("checkEngineHealth clears artifact_file_missing after projection re-render recreates the file", async (t) => {
|
|
339
732
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-stale-missing-artifact-"));
|
|
340
733
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -381,8 +774,7 @@ test("checkEngineHealth clears artifact_file_missing after projection re-render
|
|
|
381
774
|
false,
|
|
382
775
|
"doctor should not report a missing artifact that projection repair recreated in the same run",
|
|
383
776
|
);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
);
|
|
777
|
+
const contextIssue = issues.find((issue) => issue.code === "artifact_user_content_missing" && issue.file === "phases/01-foundation/01-CONTEXT.md");
|
|
778
|
+
assert.ok(contextIssue, "doctor should still report missing user content that projection repair did not recreate");
|
|
779
|
+
assert.equal(contextIssue.severity, "warning");
|
|
388
780
|
});
|