@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
|
@@ -9,16 +9,62 @@ import { migrateFromMarkdown } from "./md-importer.js";
|
|
|
9
9
|
import { countDbHierarchy } from "./migration-auto-check.js";
|
|
10
10
|
import { logWarning } from "./workflow-logger.js";
|
|
11
11
|
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
12
|
-
import { canonicalPhaseDirName, milestonesDir, resolveMilestonePath } from "./paths.js";
|
|
12
|
+
import { canonicalPhaseDirName, dirIsContentBearingLegacyMilestone, milestonesDir, resolveMilestonePath, } from "./paths.js";
|
|
13
13
|
const LEGACY_MIGRATING_SEGMENT = "milestones.migrating";
|
|
14
|
+
const RETRYABLE_FS_ERROR_CODES = new Set(["EPERM", "EBUSY", "ENOTEMPTY"]);
|
|
15
|
+
const RM_RETRY_OPTIONS = { recursive: true, force: true, maxRetries: 5, retryDelay: 100 };
|
|
16
|
+
let fsOps = { cpSync, renameSync, rmSync };
|
|
17
|
+
export function _setFlatPhaseMigrationFsOpsForTest(overrides) {
|
|
18
|
+
const previous = fsOps;
|
|
19
|
+
fsOps = { ...fsOps, ...overrides };
|
|
20
|
+
return () => {
|
|
21
|
+
fsOps = previous;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
14
24
|
function legacyMigratingPath(basePath) {
|
|
15
25
|
return join(basePath, ".gsd", LEGACY_MIGRATING_SEGMENT);
|
|
16
26
|
}
|
|
27
|
+
function removePathWithRetries(path) {
|
|
28
|
+
fsOps.rmSync(path, { recursive: true, force: true, maxRetries: RM_RETRY_OPTIONS.maxRetries, retryDelay: RM_RETRY_OPTIONS.retryDelay });
|
|
29
|
+
}
|
|
30
|
+
function isRetryableFsError(err) {
|
|
31
|
+
const code = err?.code;
|
|
32
|
+
return typeof code === "string" && RETRYABLE_FS_ERROR_CODES.has(code);
|
|
33
|
+
}
|
|
34
|
+
function movePathWithCopyDeleteFallback(src, dst) {
|
|
35
|
+
try {
|
|
36
|
+
fsOps.renameSync(src, dst);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (!isRetryableFsError(err))
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
fsOps.cpSync(src, dst, { recursive: true, force: true });
|
|
45
|
+
removePathWithRetries(src);
|
|
46
|
+
}
|
|
47
|
+
catch (fallbackErr) {
|
|
48
|
+
if (existsSync(src) && existsSync(dst)) {
|
|
49
|
+
try {
|
|
50
|
+
removePathWithRetries(dst);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Leave the original error intact; the next run can pre-clean dst.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw fallbackErr;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function expectedPhaseDirs(basePath) {
|
|
60
|
+
return getAllMilestones().map((milestone) => resolveMilestonePath(basePath, milestone.id) ??
|
|
61
|
+
join(milestonesDir(basePath), canonicalPhaseDirName(milestone.id, milestone.title)));
|
|
62
|
+
}
|
|
17
63
|
function hasLegacyMilestoneSubdirs(dirPath) {
|
|
18
64
|
if (!existsSync(dirPath))
|
|
19
65
|
return false;
|
|
20
66
|
try {
|
|
21
|
-
return readdirSync(dirPath).some(e => statSync(join(dirPath, e)).isDirectory());
|
|
67
|
+
return readdirSync(dirPath).some((e) => statSync(join(dirPath, e)).isDirectory() && dirIsContentBearingLegacyMilestone(join(dirPath, e)));
|
|
22
68
|
}
|
|
23
69
|
catch {
|
|
24
70
|
return false;
|
|
@@ -26,19 +72,24 @@ function hasLegacyMilestoneSubdirs(dirPath) {
|
|
|
26
72
|
}
|
|
27
73
|
function rollbackPartialMigration(basePath, backupDir, migratingPath) {
|
|
28
74
|
// Remove the partially-written phases/ dir.
|
|
29
|
-
|
|
75
|
+
try {
|
|
76
|
+
removePathWithRetries(join(basePath, ".gsd", LAYOUT_SEGMENTS.level1));
|
|
77
|
+
}
|
|
78
|
+
catch (removeErr) {
|
|
79
|
+
logWarning("migration", `rollback: could not remove partial ${LAYOUT_SEGMENTS.level1}/: ${removeErr.message}`);
|
|
80
|
+
}
|
|
30
81
|
// Restore milestones/ — prefer renaming the preserved migrating copy back.
|
|
31
82
|
const milestonesPath = join(basePath, ".gsd", "milestones");
|
|
32
83
|
try {
|
|
33
84
|
if (migratingPath && existsSync(migratingPath) && !existsSync(milestonesPath)) {
|
|
34
|
-
|
|
85
|
+
movePathWithCopyDeleteFallback(migratingPath, milestonesPath);
|
|
35
86
|
return;
|
|
36
87
|
}
|
|
37
88
|
if (existsSync(backupDir)) {
|
|
38
89
|
cpSync(backupDir, milestonesPath, { recursive: true });
|
|
39
90
|
}
|
|
40
91
|
if (migratingPath && existsSync(migratingPath)) {
|
|
41
|
-
|
|
92
|
+
removePathWithRetries(migratingPath);
|
|
42
93
|
}
|
|
43
94
|
}
|
|
44
95
|
catch (restoreErr) {
|
|
@@ -146,20 +197,29 @@ export async function migrateToFlatPhase(basePath) {
|
|
|
146
197
|
logWarning("migration", `flat-phase migration backup failed: ${err.message}`);
|
|
147
198
|
throw err;
|
|
148
199
|
}
|
|
200
|
+
if (existsSync(migratingPath)) {
|
|
201
|
+
removePathWithRetries(migratingPath);
|
|
202
|
+
}
|
|
149
203
|
// 3. Rename legacy tree aside before rendering so path resolvers target
|
|
150
204
|
// phases/ instead of writing back into the nested milestones/ layout.
|
|
151
205
|
// Keep the full tree on disk until render+verify succeed (unlike rmSync).
|
|
152
206
|
try {
|
|
153
|
-
|
|
207
|
+
movePathWithCopyDeleteFallback(milestonesPath, migratingPath);
|
|
154
208
|
}
|
|
155
209
|
catch (err) {
|
|
156
210
|
logWarning("migration", `failed to rename legacy milestones/ before render: ${err.message}`);
|
|
157
211
|
throw err;
|
|
158
212
|
}
|
|
159
213
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
214
|
+
// Clear any stale or partially-rendered flat projection before this run
|
|
215
|
+
// writes. Verification below checks the current DB render, not leftovers.
|
|
216
|
+
try {
|
|
217
|
+
removePathWithRetries(phasesPath);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
logWarning("migration", `failed to clear stale phases/ before render: ${err.message}`);
|
|
221
|
+
rollbackPartialMigration(basePath, backupDir, migratingPath);
|
|
222
|
+
throw err;
|
|
163
223
|
}
|
|
164
224
|
// 4. Render flat-phase from DB
|
|
165
225
|
let renderResult;
|
|
@@ -191,20 +251,11 @@ export async function migrateToFlatPhase(basePath) {
|
|
|
191
251
|
throw new Error(`flat-phase migration render failed: ${renderResult.errors.slice(0, 3).join("; ")}`);
|
|
192
252
|
}
|
|
193
253
|
const db = countDbHierarchy();
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// whose dirs start with "100-slug" are counted correctly.
|
|
198
|
-
renderedDirCount = readdirSync(phasesPath, { withFileTypes: true })
|
|
199
|
-
.filter((entry) => entry.isDirectory() && /^\d+-/.test(entry.name)).length;
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
// phases/ doesn't exist or is unreadable — same as zero dirs
|
|
203
|
-
}
|
|
204
|
-
if (db.milestones > 0 && renderedDirCount !== db.milestones) {
|
|
205
|
-
logWarning("migration", `phases/ dir count mismatch: expected ${db.milestones}, found ${renderedDirCount}`);
|
|
254
|
+
const missingPhaseDirs = expectedPhaseDirs(basePath).filter((phaseDir) => !existsSync(phaseDir));
|
|
255
|
+
if (db.milestones > 0 && missingPhaseDirs.length > 0) {
|
|
256
|
+
logWarning("migration", `flat-phase migration missing ${missingPhaseDirs.length} expected phase dir(s): ${missingPhaseDirs.join(", ")}`);
|
|
206
257
|
rollbackPartialMigration(basePath, backupDir, migratingPath);
|
|
207
|
-
throw new Error("flat-phase migration verification failed:
|
|
258
|
+
throw new Error("flat-phase migration verification failed: missing rendered phase directories");
|
|
208
259
|
}
|
|
209
260
|
if (db.slices > 0 && renderResult.rendered === 0) {
|
|
210
261
|
logWarning("migration", "flat-phase migration verification failed: render produced no artifacts for populated DB");
|
|
@@ -213,7 +264,7 @@ export async function migrateToFlatPhase(basePath) {
|
|
|
213
264
|
}
|
|
214
265
|
// 6. Verified — remove the renamed legacy tree (backup already on disk).
|
|
215
266
|
try {
|
|
216
|
-
|
|
267
|
+
removePathWithRetries(migratingPath);
|
|
217
268
|
}
|
|
218
269
|
catch (err) {
|
|
219
270
|
logWarning("migration", `flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${err.message}`);
|
|
@@ -16,7 +16,7 @@ import { gsdRoot } from "./paths.js";
|
|
|
16
16
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
17
17
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
18
18
|
import { logWarning } from "./workflow-logger.js";
|
|
19
|
-
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
19
|
+
import { createRepositoryRegistryFromPreferences, defaultRepositoryTargets } from "./repository-registry.js";
|
|
20
20
|
import { isGsdGitignored } from "./gitignore.js";
|
|
21
21
|
import { detectWorktreeName, } from "./worktree.js";
|
|
22
22
|
import { SLICE_BRANCH_RE, QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
|
|
@@ -1024,28 +1024,25 @@ export function handleTurnGitActionError(action, err) {
|
|
|
1024
1024
|
error: errorWithStreams.stderr?.trim() || errorWithStreams.message || getErrorMessage(err),
|
|
1025
1025
|
};
|
|
1026
1026
|
}
|
|
1027
|
+
export function isRepositoryDirty(repoRoot) {
|
|
1028
|
+
try {
|
|
1029
|
+
return runGit(repoRoot, ["status", "--porcelain"]).length > 0;
|
|
1030
|
+
}
|
|
1031
|
+
catch {
|
|
1032
|
+
// Fallback preserves legacy behavior if explicit status probing fails.
|
|
1033
|
+
return nativeHasChanges(repoRoot);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1027
1036
|
function collectRepositoryDirtyStatus(basePath) {
|
|
1028
1037
|
const preferences = loadEffectiveGSDPreferences(basePath)?.preferences;
|
|
1029
1038
|
const registry = createRepositoryRegistryFromPreferences(basePath, preferences);
|
|
1030
1039
|
const dirtyByRepository = {};
|
|
1031
1040
|
const registeredRoots = new Set(registry.repositories.map((repo) => resolve(repo.root)));
|
|
1032
1041
|
for (const repo of registry.repositories) {
|
|
1033
|
-
|
|
1034
|
-
dirtyByRepository[repo.id] = runGit(repo.root, ["status", "--porcelain"]).length > 0;
|
|
1035
|
-
}
|
|
1036
|
-
catch {
|
|
1037
|
-
// Fallback preserves legacy behavior if explicit status probing fails.
|
|
1038
|
-
dirtyByRepository[repo.id] = nativeHasChanges(repo.root);
|
|
1039
|
-
}
|
|
1042
|
+
dirtyByRepository[repo.id] = isRepositoryDirty(repo.root);
|
|
1040
1043
|
}
|
|
1041
1044
|
for (const repoRoot of findUndeclaredNestedGitRepositories(registry.projectRoot, registeredRoots)) {
|
|
1042
|
-
|
|
1043
|
-
try {
|
|
1044
|
-
dirty = runGit(repoRoot, ["status", "--porcelain"]).length > 0;
|
|
1045
|
-
}
|
|
1046
|
-
catch {
|
|
1047
|
-
dirty = nativeHasChanges(repoRoot);
|
|
1048
|
-
}
|
|
1045
|
+
const dirty = isRepositoryDirty(repoRoot);
|
|
1049
1046
|
if (!dirty)
|
|
1050
1047
|
continue;
|
|
1051
1048
|
const relPath = relative(registry.projectRoot, repoRoot).split(sep).join("/") || repoRoot;
|
|
@@ -1098,7 +1095,7 @@ function findUndeclaredNestedGitRepositories(projectRoot, registeredRoots) {
|
|
|
1098
1095
|
function runPerRepositoryCommitAction(args) {
|
|
1099
1096
|
const preferences = loadEffectiveGSDPreferences(args.basePath)?.preferences;
|
|
1100
1097
|
const registry = createRepositoryRegistryFromPreferences(args.basePath, preferences);
|
|
1101
|
-
const repoIds = args.targetRepositories?.length ? args.targetRepositories :
|
|
1098
|
+
const repoIds = args.targetRepositories?.length ? args.targetRepositories : defaultRepositoryTargets(registry);
|
|
1102
1099
|
const gitPrefs = preferences?.git ?? {};
|
|
1103
1100
|
const commitMessages = {};
|
|
1104
1101
|
const commitErrors = {};
|
|
@@ -1166,8 +1163,7 @@ export function runTurnGitAction(args) {
|
|
|
1166
1163
|
skippedRepositories: repoCommitResult.skippedRepositories,
|
|
1167
1164
|
};
|
|
1168
1165
|
}
|
|
1169
|
-
const primaryMessage = repoCommitResult.commitMessages[
|
|
1170
|
-
?? Object.values(repoCommitResult.commitMessages)[0];
|
|
1166
|
+
const primaryMessage = Object.values(repoCommitResult.commitMessages)[0];
|
|
1171
1167
|
return {
|
|
1172
1168
|
action: args.action,
|
|
1173
1169
|
status: "ok",
|
|
@@ -410,6 +410,58 @@ export function insertTask(t) {
|
|
|
410
410
|
export function updateTaskStatus(milestoneId, sliceId, taskId, status, completedAt) {
|
|
411
411
|
applyStatusTransition({ entity: "task", milestoneId, sliceId, taskId, status, completedAt });
|
|
412
412
|
}
|
|
413
|
+
export function repairTaskCompletionFromSummary(t) {
|
|
414
|
+
const db = getDbOrNull();
|
|
415
|
+
if (!db)
|
|
416
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
417
|
+
transaction(() => {
|
|
418
|
+
const seq = db.prepare(`SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
|
|
419
|
+
FROM tasks
|
|
420
|
+
WHERE milestone_id = :mid AND slice_id = :sid`).get({ ":mid": t.milestoneId, ":sid": t.sliceId });
|
|
421
|
+
db.prepare(`INSERT INTO tasks (
|
|
422
|
+
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
423
|
+
verification_result, duration, completed_at, blocker_discovered,
|
|
424
|
+
deviations, known_issues, key_files, key_decisions, full_summary_md,
|
|
425
|
+
sequence
|
|
426
|
+
) VALUES (
|
|
427
|
+
:milestone_id, :slice_id, :id, :title, 'complete', :one_liner, :narrative,
|
|
428
|
+
:verification_result, :duration, :completed_at, :blocker_discovered,
|
|
429
|
+
:deviations, :known_issues, :key_files, :key_decisions, :full_summary_md,
|
|
430
|
+
:sequence
|
|
431
|
+
)
|
|
432
|
+
ON CONFLICT(milestone_id, slice_id, id) DO UPDATE SET
|
|
433
|
+
title = CASE WHEN NULLIF(:title, '') IS NOT NULL THEN :title ELSE tasks.title END,
|
|
434
|
+
status = 'complete',
|
|
435
|
+
one_liner = CASE WHEN NULLIF(:one_liner, '') IS NOT NULL THEN :one_liner ELSE tasks.one_liner END,
|
|
436
|
+
narrative = CASE WHEN NULLIF(:narrative, '') IS NOT NULL THEN :narrative ELSE tasks.narrative END,
|
|
437
|
+
verification_result = :verification_result,
|
|
438
|
+
duration = CASE WHEN NULLIF(:duration, '') IS NOT NULL THEN :duration ELSE tasks.duration END,
|
|
439
|
+
completed_at = :completed_at,
|
|
440
|
+
blocker_discovered = :blocker_discovered,
|
|
441
|
+
deviations = CASE WHEN NULLIF(:deviations, '') IS NOT NULL THEN :deviations ELSE tasks.deviations END,
|
|
442
|
+
known_issues = CASE WHEN NULLIF(:known_issues, '') IS NOT NULL THEN :known_issues ELSE tasks.known_issues END,
|
|
443
|
+
key_files = :key_files,
|
|
444
|
+
key_decisions = :key_decisions,
|
|
445
|
+
full_summary_md = :full_summary_md`).run({
|
|
446
|
+
":milestone_id": t.milestoneId,
|
|
447
|
+
":slice_id": t.sliceId,
|
|
448
|
+
":id": t.taskId,
|
|
449
|
+
":title": t.title ?? "",
|
|
450
|
+
":one_liner": t.oneLiner ?? "",
|
|
451
|
+
":narrative": t.narrative ?? "",
|
|
452
|
+
":verification_result": t.verificationResult,
|
|
453
|
+
":duration": t.duration ?? "",
|
|
454
|
+
":completed_at": t.completedAt,
|
|
455
|
+
":blocker_discovered": t.blockerDiscovered ? 1 : 0,
|
|
456
|
+
":deviations": t.deviations ?? "",
|
|
457
|
+
":known_issues": t.knownIssues ?? "",
|
|
458
|
+
":key_files": JSON.stringify(t.keyFiles ?? []),
|
|
459
|
+
":key_decisions": JSON.stringify(t.keyDecisions ?? []),
|
|
460
|
+
":full_summary_md": t.fullSummaryMd,
|
|
461
|
+
":sequence": seq?.next_sequence ?? 1,
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
}
|
|
413
465
|
export function setTaskBlockerDiscovered(milestoneId, sliceId, taskId, discovered) {
|
|
414
466
|
if (!getDbOrNull())
|
|
415
467
|
return;
|
|
@@ -151,6 +151,7 @@ const DOCTOR_FIX_HINTS = {
|
|
|
151
151
|
state_file_stale: "Run `/gsd doctor fix` to rebuild the projection from the database.",
|
|
152
152
|
state_file_missing: "Run `/gsd doctor fix` to rebuild the projection from the database.",
|
|
153
153
|
projection_drift: "Run `/gsd doctor fix` to rebuild markdown projections from the database (DB is the source of truth).",
|
|
154
|
+
artifact_user_content_missing: "Doctor cannot recreate user-authored content from the database. Re-run `/gsd discuss` for CONTEXT artifacts or `/gsd auto` for RESEARCH artifacts in that milestone.",
|
|
154
155
|
uat_retry_exhausted: "Review the failing UAT criteria via `/gsd status`, fix the issue, then re-run `/gsd auto`.",
|
|
155
156
|
};
|
|
156
157
|
export function doctorFixHint(code) {
|
|
@@ -20,7 +20,7 @@ import { assessInterruptedSession, formatInterruptedSessionRunningMessage, forma
|
|
|
20
20
|
import { listUnitRuntimeRecords, clearUnitRuntimeRecord, isInFlightRuntimePhase } from "./unit-runtime.js";
|
|
21
21
|
import { resolveExpectedArtifactPath } from "./auto.js";
|
|
22
22
|
import { gsdHome } from "./gsd-home.js";
|
|
23
|
-
import { gsdRoot, milestonesDir, legacyMilestonesDir, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, } from "./paths.js";
|
|
23
|
+
import { gsdRoot, milestonesDir, legacyMilestonesDir, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
26
26
|
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
@@ -53,6 +53,7 @@ import { isWorkflowToolSurfaceName } from "./workflow-tool-surface.js";
|
|
|
53
53
|
import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
|
|
54
54
|
import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, } from "./preparation.js";
|
|
55
55
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
56
|
+
import { countPlanMilestoneRoadmapSlices } from "./artifact-verification.js";
|
|
56
57
|
import { clearPendingGate, extractDepthVerificationMilestoneId, getPendingGate } from "./bootstrap/write-gate.js";
|
|
57
58
|
import { _getPendingAutoStart, deletePendingAutoStart, hasPendingAutoStart, setPendingAutoStart, } from "./pending-auto-start.js";
|
|
58
59
|
import { clearGuidedUnitContext, setGuidedUnitContext } from "./guided-unit-context.js";
|
|
@@ -78,20 +79,57 @@ export const _scheduleAutoStartAfterIdleForTest = scheduleAutoStartAfterIdle;
|
|
|
78
79
|
// These thin wrappers accept a MilestoneScope so callers that already hold a
|
|
79
80
|
// pinned scope never have to re-derive (basePath, milestoneId) separately.
|
|
80
81
|
// The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
|
|
81
|
-
// state.ts
|
|
82
|
+
// state.ts remain the default for non-scope-specific units.
|
|
82
83
|
/**
|
|
83
84
|
* Scope-based overload of verifyExpectedArtifact.
|
|
84
85
|
* Uses scope.workspace.projectRoot as the authoritative base path, making
|
|
85
86
|
* the check immune to cwd-drift and worktree-path divergence.
|
|
86
87
|
*/
|
|
87
88
|
export function verifyExpectedArtifactForScope(scope, unitType, unitId) {
|
|
89
|
+
if (unitId === scope.milestoneId &&
|
|
90
|
+
(unitType === "discuss-milestone" || unitType === "plan-milestone")) {
|
|
91
|
+
// Layout-aware scope paths use resolveMilestoneFile; clear stale dir listings
|
|
92
|
+
// primed before discuss/plan wrote CONTEXT.md or ROADMAP.md (see checkAutoStartAfterDiscuss).
|
|
93
|
+
clearPathCache();
|
|
94
|
+
}
|
|
95
|
+
if (unitId === scope.milestoneId &&
|
|
96
|
+
unitType === "discuss-milestone") {
|
|
97
|
+
const path = resolveExpectedArtifactPathForScope(scope, unitType, unitId);
|
|
98
|
+
return path ? existsSync(path) : false;
|
|
99
|
+
}
|
|
100
|
+
if (unitId === scope.milestoneId && unitType === "plan-milestone") {
|
|
101
|
+
const path = resolveExpectedArtifactPathForScope(scope, unitType, unitId);
|
|
102
|
+
return verifyScopedPlanMilestoneArtifact(path, unitType, unitId);
|
|
103
|
+
}
|
|
88
104
|
return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
|
|
89
105
|
}
|
|
106
|
+
function verifyScopedPlanMilestoneArtifact(path, unitType, unitId) {
|
|
107
|
+
if (!path || !existsSync(path))
|
|
108
|
+
return false;
|
|
109
|
+
try {
|
|
110
|
+
const roadmapContent = readFileSync(path, "utf-8");
|
|
111
|
+
if (countPlanMilestoneRoadmapSlices(roadmapContent) === 0) {
|
|
112
|
+
logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap has zero slices at ${path}`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
logWarning("recovery", `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
90
122
|
/**
|
|
91
123
|
* Scope-based overload of resolveExpectedArtifactPath.
|
|
92
124
|
* Returns the canonical absolute path (or null) using the scope's projectRoot.
|
|
93
125
|
*/
|
|
94
126
|
export function resolveExpectedArtifactPathForScope(scope, unitType, unitId) {
|
|
127
|
+
if (unitId === scope.milestoneId) {
|
|
128
|
+
if (unitType === "discuss-milestone")
|
|
129
|
+
return scope.contextFile();
|
|
130
|
+
if (unitType === "plan-milestone")
|
|
131
|
+
return scope.roadmapFile();
|
|
132
|
+
}
|
|
95
133
|
return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
|
|
96
134
|
}
|
|
97
135
|
async function runQuickTaskChoice(ctx, pi) {
|
|
@@ -19,6 +19,7 @@ export const PROVIDER_REGISTRY = [
|
|
|
19
19
|
// must be in the canonical registry so all consumers see the same catalog.
|
|
20
20
|
// See: https://github.com/open-gsd/gsd-pi/issues/4541
|
|
21
21
|
{ id: "claude-code", label: "Claude Code CLI", category: "llm", authMode: "externalCli" },
|
|
22
|
+
{ id: "cursor-agent", label: "Cursor Agent", category: "llm", envVar: "CURSOR_API_KEY", authMode: "externalCli" },
|
|
22
23
|
{ id: "openai", label: "OpenAI", category: "llm", envVar: "OPENAI_API_KEY", prefixes: ["sk-"], dashboardUrl: "platform.openai.com/api-keys" },
|
|
23
24
|
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", authMode: "browserOAuth" },
|
|
24
25
|
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)", category: "llm", authMode: "browserOAuth" },
|
|
@@ -12,7 +12,7 @@ import { readFileSync, existsSync, mkdirSync, statSync, unlinkSync } from "node:
|
|
|
12
12
|
import { logWarning } from "./workflow-logger.js";
|
|
13
13
|
import { isClosedStatus } from "./status-guards.js";
|
|
14
14
|
import { dirname, join, relative } from "node:path";
|
|
15
|
-
import { getAllMilestones, getMilestone, getMilestoneScopedArtifacts, getSliceScopedArtifacts, getMilestoneSlices, getSliceTasks, getTask, getSlice, insertArtifact, deleteArtifactByPath, getGateResults, isDbAvailable, } from "./gsd-db.js";
|
|
15
|
+
import { getAllMilestones, getMilestone, getMilestoneScopedArtifacts, getSliceScopedArtifacts, getMilestoneSlices, getSliceTasks, getTasksBySliceIds, getTask, getSlice, insertArtifact, deleteArtifactByPath, getGateResults, isDbAvailable, } from "./gsd-db.js";
|
|
16
16
|
import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, gsdProjectionRoot, gsdRoot, buildMilestoneFileName, buildTaskFileName, } from "./paths.js";
|
|
17
17
|
import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.js";
|
|
18
18
|
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
@@ -99,6 +99,12 @@ function meaningfulSection(value) {
|
|
|
99
99
|
return "";
|
|
100
100
|
return trimmed;
|
|
101
101
|
}
|
|
102
|
+
function renderGateFindings(gate) {
|
|
103
|
+
const findings = gate.findings.trim() || `- **Verdict:** ${gate.verdict}\n- **Rationale:** ${gate.rationale}`;
|
|
104
|
+
if (gate.verdict !== "flag")
|
|
105
|
+
return findings;
|
|
106
|
+
return `> **Warning:** Verdict: flag - concerns recorded below\n\n${findings}`;
|
|
107
|
+
}
|
|
102
108
|
function pushIndented(lines, value, indent = " ") {
|
|
103
109
|
for (const line of value.split("\n")) {
|
|
104
110
|
lines.push(`${indent}${line}`);
|
|
@@ -289,7 +295,7 @@ function renderTaskPlanMarkdown(task, taskGates = []) {
|
|
|
289
295
|
if (gate && gate.verdict !== "omitted") {
|
|
290
296
|
lines.push(`## ${label}`);
|
|
291
297
|
lines.push("");
|
|
292
|
-
lines.push(
|
|
298
|
+
lines.push(renderGateFindings(gate));
|
|
293
299
|
lines.push("");
|
|
294
300
|
}
|
|
295
301
|
}
|
|
@@ -319,14 +325,14 @@ function renderSlicePlanMarkdown(slice, tasks, gates = []) {
|
|
|
319
325
|
if (q3 && q3.verdict !== "omitted") {
|
|
320
326
|
lines.push("## Threat Surface");
|
|
321
327
|
lines.push("");
|
|
322
|
-
lines.push(
|
|
328
|
+
lines.push(renderGateFindings(q3));
|
|
323
329
|
lines.push("");
|
|
324
330
|
}
|
|
325
331
|
const q4 = gates.find(g => g.gate_id === "Q4" && g.status === "complete");
|
|
326
332
|
if (q4 && q4.verdict !== "omitted") {
|
|
327
333
|
lines.push("## Requirement Impact");
|
|
328
334
|
lines.push("");
|
|
329
|
-
lines.push(
|
|
335
|
+
lines.push(renderGateFindings(q4));
|
|
330
336
|
lines.push("");
|
|
331
337
|
}
|
|
332
338
|
const proofLevel = meaningfulSection(slice.proof_level);
|
|
@@ -701,6 +707,17 @@ export async function renderSliceSummary(basePath, milestoneId, sliceId) {
|
|
|
701
707
|
export async function renderAllFromDb(basePath) {
|
|
702
708
|
const result = { rendered: 0, skipped: 0, errors: [] };
|
|
703
709
|
const milestones = getAllMilestones();
|
|
710
|
+
// Pre-fetch slices once per milestone and batch-load all tasks in one query
|
|
711
|
+
// family, avoiding an N+1 (previously one task query per slice).
|
|
712
|
+
const slicesByMilestone = new Map();
|
|
713
|
+
const slicePairs = [];
|
|
714
|
+
for (const milestone of milestones) {
|
|
715
|
+
const slices = getMilestoneSlices(milestone.id);
|
|
716
|
+
slicesByMilestone.set(milestone.id, slices);
|
|
717
|
+
for (const slice of slices)
|
|
718
|
+
slicePairs.push({ milestoneId: milestone.id, sliceId: slice.id });
|
|
719
|
+
}
|
|
720
|
+
const tasksBySlice = getTasksBySliceIds(slicePairs);
|
|
704
721
|
for (const milestone of milestones) {
|
|
705
722
|
// Render roadmap checkboxes
|
|
706
723
|
try {
|
|
@@ -723,8 +740,8 @@ export async function renderAllFromDb(basePath) {
|
|
|
723
740
|
catch (err) {
|
|
724
741
|
result.errors.push(`milestone artifacts ${milestone.id}: ${err.message}`);
|
|
725
742
|
}
|
|
726
|
-
// Iterate slices
|
|
727
|
-
const slices =
|
|
743
|
+
// Iterate slices (pre-fetched above)
|
|
744
|
+
const slices = slicesByMilestone.get(milestone.id) ?? [];
|
|
728
745
|
for (const slice of slices) {
|
|
729
746
|
// Preserve slice-scoped artifacts imported from disk, including real PLAN
|
|
730
747
|
// fallback content when task rows cannot regenerate it.
|
|
@@ -760,8 +777,8 @@ export async function renderAllFromDb(basePath) {
|
|
|
760
777
|
catch (err) {
|
|
761
778
|
result.errors.push(`slice summary ${milestone.id}/${slice.id}: ${err.message}`);
|
|
762
779
|
}
|
|
763
|
-
// Iterate tasks
|
|
764
|
-
const tasks =
|
|
780
|
+
// Iterate tasks (batched above)
|
|
781
|
+
const tasks = tasksBySlice.get(`${milestone.id}\0${slice.id}`) ?? [];
|
|
765
782
|
for (const task of tasks) {
|
|
766
783
|
try {
|
|
767
784
|
const ok = await renderTaskSummary(basePath, milestone.id, slice.id, task.id);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { execFileSync } from "node:child_process";
|
|
9
9
|
import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, statSync, symlinkSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
-
import { externalGsdRoot, isInsideWorktree } from "./repo-identity.js";
|
|
11
|
+
import { externalGsdRoot, externalStateAlreadyExistsForProject, isInsideWorktree } from "./repo-identity.js";
|
|
12
12
|
import { getErrorMessage } from "./error-utils.js";
|
|
13
13
|
import { hasGitTrackedGsdFiles } from "./gitignore.js";
|
|
14
14
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
@@ -74,6 +74,15 @@ export function migrateToExternalState(basePath) {
|
|
|
74
74
|
return { migrated: false };
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
// If external state already contains project data, this is not a legacy
|
|
78
|
+
// migration source. It is likely an already-migrated project whose symlink
|
|
79
|
+
// was replaced by a real directory, so copying would risk data loss.
|
|
80
|
+
if (externalStateAlreadyExistsForProject(basePath)) {
|
|
81
|
+
return {
|
|
82
|
+
migrated: false,
|
|
83
|
+
error: "External state already exists for this project; leaving local .gsd directory untouched to avoid overwriting authoritative state",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
77
86
|
const externalPath = externalGsdRoot(basePath);
|
|
78
87
|
const migratingPath = join(basePath, ".gsd.migrating");
|
|
79
88
|
try {
|
|
@@ -81,13 +90,13 @@ export function migrateToExternalState(basePath) {
|
|
|
81
90
|
mkdirSync(externalPath, { recursive: true });
|
|
82
91
|
// Rename .gsd -> .gsd.migrating (atomic lock).
|
|
83
92
|
// On Windows, NTFS may reject rename with EPERM if file descriptors are
|
|
84
|
-
// open (VS Code watchers, antivirus on-access scan).
|
|
85
|
-
// copy+delete (#1292).
|
|
93
|
+
// open (VS Code watchers, antivirus on-access scan). WSL/DrvFs can report
|
|
94
|
+
// the same transient lock as EACCES. Fall back to copy+delete (#1292).
|
|
86
95
|
try {
|
|
87
96
|
renameSync(localGsd, migratingPath);
|
|
88
97
|
}
|
|
89
98
|
catch (renameErr) {
|
|
90
|
-
if (renameErr?.code === "EPERM" || renameErr?.code === "EBUSY") {
|
|
99
|
+
if (renameErr?.code === "EPERM" || renameErr?.code === "EBUSY" || renameErr?.code === "EACCES") {
|
|
91
100
|
try {
|
|
92
101
|
cpSync(localGsd, migratingPath, { recursive: true, force: true });
|
|
93
102
|
rmSync(localGsd, { recursive: true, force: true });
|
|
@@ -239,7 +248,7 @@ function isLocalGsdExternalStateJunction(basePath, localGsd) {
|
|
|
239
248
|
* Recover from a failed migration (`.gsd.migrating` exists).
|
|
240
249
|
* Moves `.gsd.migrating` back to `.gsd` if `.gsd` doesn't exist, or removes an
|
|
241
250
|
* orphaned staging directory when `.gsd` is already a verified external state
|
|
242
|
-
* junction with intact current state.
|
|
251
|
+
* junction, or an intact real directory, with intact current state.
|
|
243
252
|
*/
|
|
244
253
|
export function recoverFailedMigration(basePath) {
|
|
245
254
|
const localGsd = join(basePath, ".gsd");
|
|
@@ -247,10 +256,18 @@ export function recoverFailedMigration(basePath) {
|
|
|
247
256
|
if (!existsSync(migratingPath))
|
|
248
257
|
return false;
|
|
249
258
|
if (existsSync(localGsd)) {
|
|
250
|
-
if (!isLocalGsdExternalStateJunction(basePath, localGsd))
|
|
251
|
-
return false;
|
|
252
259
|
if (!isCurrentGsdStateIntactForMigratingCleanup(basePath))
|
|
253
260
|
return false;
|
|
261
|
+
if (!isLocalGsdExternalStateJunction(basePath, localGsd)) {
|
|
262
|
+
try {
|
|
263
|
+
const stat = lstatSync(localGsd);
|
|
264
|
+
if (!stat.isDirectory())
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
254
271
|
try {
|
|
255
272
|
rmSync(migratingPath, { recursive: true, force: true });
|
|
256
273
|
return true;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Process-level transaction wrapper for milestone merge closeout.
|
|
3
3
|
export function runMilestoneMergeTransaction(deps, input) {
|
|
4
|
-
return deps.
|
|
4
|
+
return deps.mergeMilestone(input.basePath, input.milestoneId, input.roadmapContent);
|
|
5
5
|
}
|
|
6
|
-
export function createMilestoneMergeTransaction(
|
|
6
|
+
export function createMilestoneMergeTransaction(mergeMilestone) {
|
|
7
7
|
return function mergeMilestoneTransaction(basePath, milestoneId, roadmapContent) {
|
|
8
|
-
return runMilestoneMergeTransaction({
|
|
8
|
+
return runMilestoneMergeTransaction({ mergeMilestone }, { basePath, milestoneId, roadmapContent });
|
|
9
9
|
};
|
|
10
10
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { loadGlobalGSDPreferences, loadProjectGSDPreferences, } from "./preferences.js";
|
|
1
|
+
import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, loadProjectGSDPreferences, } from "./preferences.js";
|
|
2
|
+
import { crossAxisPreferenceWarnings } from "./preferences-validation.js";
|
|
2
3
|
const notifiedPreferenceDiagnostics = new Set();
|
|
3
4
|
export function collectPreferenceDiagnostics(basePath) {
|
|
4
5
|
const diagnostics = [
|
|
@@ -14,6 +15,28 @@ export function collectPreferenceDiagnostics(basePath) {
|
|
|
14
15
|
seen.add(signature);
|
|
15
16
|
unique.push(diagnostic);
|
|
16
17
|
}
|
|
18
|
+
const effective = loadEffectiveGSDPreferences(basePath);
|
|
19
|
+
if (effective) {
|
|
20
|
+
const existingMessages = new Set(unique.map((diagnostic) => diagnostic.message));
|
|
21
|
+
for (const message of crossAxisPreferenceWarnings(effective.preferences)) {
|
|
22
|
+
if (existingMessages.has(message))
|
|
23
|
+
continue;
|
|
24
|
+
const diagnostic = {
|
|
25
|
+
path: effective.path,
|
|
26
|
+
scope: effective.scope,
|
|
27
|
+
severity: "warning",
|
|
28
|
+
kind: "validation",
|
|
29
|
+
message,
|
|
30
|
+
sanitized: true,
|
|
31
|
+
};
|
|
32
|
+
const signature = preferenceDiagnosticSignature(diagnostic);
|
|
33
|
+
if (seen.has(signature))
|
|
34
|
+
continue;
|
|
35
|
+
seen.add(signature);
|
|
36
|
+
existingMessages.add(message);
|
|
37
|
+
unique.push(diagnostic);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
17
40
|
return unique;
|
|
18
41
|
}
|
|
19
42
|
export function formatPreferenceDiagnostic(diagnostic) {
|
|
@@ -74,10 +74,12 @@ function resolveWinningPhase(models, chain) {
|
|
|
74
74
|
* - Legacy: `planning: claude-opus-4-6`
|
|
75
75
|
* - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
|
|
76
76
|
*/
|
|
77
|
-
export function resolveModelWithFallbacksForUnit(unitType, basePath, availableModelIds, preferredModelId) {
|
|
78
|
-
const loadOpts =
|
|
79
|
-
? { availableModelIds
|
|
80
|
-
:
|
|
77
|
+
export function resolveModelWithFallbacksForUnit(unitType, basePath, availableModelIds, preferredModelId, skipProfileDefaults = false) {
|
|
78
|
+
const loadOpts = {
|
|
79
|
+
...(availableModelIds !== undefined ? { availableModelIds } : {}),
|
|
80
|
+
...(preferredModelId !== undefined ? { preferredModelId } : {}),
|
|
81
|
+
...(skipProfileDefaults ? { skipProfileDefaults: true } : {}),
|
|
82
|
+
};
|
|
81
83
|
const prefs = loadEffectiveGSDPreferences(basePath, loadOpts);
|
|
82
84
|
const chain = phaseChainForUnit(unitType);
|
|
83
85
|
if (!chain)
|