@opengsd/gsd-pi 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/onboarding.js +11 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +20 -25
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
- package/dist/resources/extensions/cursor-cli/index.js +16 -0
- package/dist/resources/extensions/cursor-cli/models.js +63 -0
- package/dist/resources/extensions/cursor-cli/package.json +11 -0
- package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
- package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
- package/dist/resources/extensions/gsd/activity-log.js +4 -1
- package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
- package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
- package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
- package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
- package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
- package/dist/resources/extensions/gsd/auto-start.js +4 -3
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
- package/dist/resources/extensions/gsd/auto.js +23 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
- package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
- package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
- package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
- package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
- package/dist/resources/extensions/gsd/db/queries.js +39 -0
- package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
- package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
- package/dist/resources/extensions/gsd/doctor.js +93 -3
- package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
- package/dist/resources/extensions/gsd/git-service.js +14 -18
- package/dist/resources/extensions/gsd/gsd-db.js +52 -0
- package/dist/resources/extensions/gsd/guidance.js +1 -0
- package/dist/resources/extensions/gsd/guided-flow.js +40 -2
- package/dist/resources/extensions/gsd/key-manager.js +1 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
- package/dist/resources/extensions/gsd/migrate-external.js +24 -7
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
- package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
- package/dist/resources/extensions/gsd/preferences-models.js +6 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
- package/dist/resources/extensions/gsd/preferences.js +29 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
- package/dist/resources/extensions/gsd/repo-identity.js +16 -0
- package/dist/resources/extensions/gsd/repository-registry.js +6 -1
- package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
- package/dist/resources/extensions/gsd/safe-fs.js +23 -2
- package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
- package/dist/resources/extensions/gsd/unit-registry.js +27 -2
- package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
- package/dist/resources/extensions/gsd/workspace.js +26 -8
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +8 -1
- package/integrations/hermes/CONTEXT.md +47 -0
- package/integrations/hermes/README.md +21 -0
- package/integrations/hermes/__init__.py +31 -0
- package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
- package/integrations/hermes/docs/setup.md +4 -0
- package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
- package/integrations/hermes/open_gsd_hermes/config.py +2 -0
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
- package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
- package/integrations/hermes/plugin.yaml +4 -0
- package/integrations/hermes/tests/test_config.py +6 -0
- package/integrations/hermes/tests/test_directory_loader.py +37 -0
- package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
- package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
- package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
- package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
- package/integrations/hermes/tests/test_milestone_client.py +447 -0
- package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
- package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
- package/package.json +8 -8
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
- package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
- package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-runtime.js +25 -1
- package/packages/daemon/dist/cloud-runtime.js.map +1 -1
- package/packages/daemon/dist/cloud-runtime.test.js +51 -0
- package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
- package/packages/daemon/dist/message-batcher.d.ts +7 -0
- package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
- package/packages/daemon/dist/message-batcher.js +13 -0
- package/packages/daemon/dist/message-batcher.js.map +1 -1
- package/packages/daemon/dist/message-batcher.test.js +48 -0
- package/packages/daemon/dist/message-batcher.test.js.map +1 -1
- package/packages/daemon/dist/session-manager.js +1 -1
- package/packages/daemon/dist/session-manager.js.map +1 -1
- package/packages/daemon/package.json +5 -5
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
- package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/server.d.ts +18 -5
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +46 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +1 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +3 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/dist/rpc-client.js +1 -1
- package/packages/rpc-client/dist/rpc-client.js.map +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/link-workspace-packages.cjs +14 -4
- package/src/resources/GSD-WORKFLOW.md +20 -25
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
- package/src/resources/extensions/cursor-cli/index.ts +18 -0
- package/src/resources/extensions/cursor-cli/models.ts +65 -0
- package/src/resources/extensions/cursor-cli/package.json +11 -0
- package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
- package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
- package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
- package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
- package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
- package/src/resources/extensions/gsd/activity-log.ts +4 -1
- package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
- package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
- package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
- package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
- package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
- package/src/resources/extensions/gsd/auto-start.ts +4 -3
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
- package/src/resources/extensions/gsd/auto.ts +22 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
- package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
- package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
- package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
- package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
- package/src/resources/extensions/gsd/db/queries.ts +40 -0
- package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
- package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
- package/src/resources/extensions/gsd/doctor-types.ts +5 -1
- package/src/resources/extensions/gsd/doctor.ts +95 -3
- package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
- package/src/resources/extensions/gsd/git-service.ts +14 -17
- package/src/resources/extensions/gsd/gsd-db.ts +72 -0
- package/src/resources/extensions/gsd/guidance.ts +2 -0
- package/src/resources/extensions/gsd/guided-flow.ts +45 -2
- package/src/resources/extensions/gsd/key-manager.ts +1 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
- package/src/resources/extensions/gsd/migrate-external.ts +23 -6
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
- package/src/resources/extensions/gsd/model-router.ts +4 -5
- package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
- package/src/resources/extensions/gsd/preferences-models.ts +6 -3
- package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
- package/src/resources/extensions/gsd/preferences.ts +38 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
- package/src/resources/extensions/gsd/repo-identity.ts +17 -0
- package/src/resources/extensions/gsd/repository-registry.ts +7 -1
- package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
- package/src/resources/extensions/gsd/safe-fs.ts +22 -2
- package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
- package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
- package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
- package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
- package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
- package/src/resources/extensions/gsd/unit-registry.ts +34 -2
- package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
- package/src/resources/extensions/gsd/workspace.ts +42 -8
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
- package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
3
4
|
|
|
4
5
|
import { loadFile, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
5
6
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
@@ -16,6 +17,8 @@ import { isClosedStatus } from "./status-guards.js";
|
|
|
16
17
|
import type { DoctorIssue, DoctorIssueCode, DoctorReport } from "./doctor-types.js";
|
|
17
18
|
import { GLOBAL_STATE_CODES } from "./doctor-types.js";
|
|
18
19
|
import type { RoadmapSliceEntry } from "./types.js";
|
|
20
|
+
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
21
|
+
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
19
22
|
import { checkGitHealth, checkRuntimeHealth, checkGlobalHealth, checkEngineHealth } from "./doctor-checks.js";
|
|
20
23
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
21
24
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
@@ -308,6 +311,94 @@ export async function readDoctorHistory(basePath: string, lastN = 50): Promise<D
|
|
|
308
311
|
} catch { return []; }
|
|
309
312
|
}
|
|
310
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Resolve the git working-tree root for a path, or null if it is not a repo.
|
|
316
|
+
* Used by the workspace-repository probe to check that a declared child path is
|
|
317
|
+
* itself a git repository (not merely nested inside the parent's repo).
|
|
318
|
+
*/
|
|
319
|
+
function resolveGitToplevel(cwd: string): string | null {
|
|
320
|
+
try {
|
|
321
|
+
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
322
|
+
cwd,
|
|
323
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
324
|
+
encoding: "utf-8",
|
|
325
|
+
env: GIT_NO_PROMPT_ENV,
|
|
326
|
+
}).trim();
|
|
327
|
+
return out ? resolve(out) : null;
|
|
328
|
+
} catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** realpath that falls back to the resolved path if the link cannot be read. */
|
|
334
|
+
function realpathSafe(p: string): string {
|
|
335
|
+
try {
|
|
336
|
+
return realpathSync(p);
|
|
337
|
+
} catch {
|
|
338
|
+
return resolve(p);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Parent-workspace probe (#818): validate that every declared child repository
|
|
344
|
+
* exists on disk and is a git repository at its own root. The registry only
|
|
345
|
+
* checks paths stay inside the project root; it never checks existence or
|
|
346
|
+
* git-ness, so a typo'd path (e.g. `frontned`) would otherwise build cleanly
|
|
347
|
+
* and silently produce "no problems found." No-op for single-repo projects.
|
|
348
|
+
*/
|
|
349
|
+
function checkWorkspaceRepositoryHealth(
|
|
350
|
+
basePath: string,
|
|
351
|
+
prefs: GSDPreferences | undefined,
|
|
352
|
+
issues: DoctorIssue[],
|
|
353
|
+
): void {
|
|
354
|
+
if (prefs?.workspace?.mode !== "parent") return;
|
|
355
|
+
let registry;
|
|
356
|
+
try {
|
|
357
|
+
registry = createRepositoryRegistryFromPreferences(basePath, prefs);
|
|
358
|
+
} catch (err) {
|
|
359
|
+
issues.push({
|
|
360
|
+
severity: "error",
|
|
361
|
+
code: "invalid_preferences",
|
|
362
|
+
scope: "project",
|
|
363
|
+
unitId: "workspace",
|
|
364
|
+
message: `workspace registry failed to build: ${err instanceof Error ? err.message : String(err)}`,
|
|
365
|
+
fixable: false,
|
|
366
|
+
});
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
for (const repo of registry.repositories) {
|
|
370
|
+
if (repo.id === "project") continue;
|
|
371
|
+
if (!existsSync(repo.root)) {
|
|
372
|
+
issues.push({
|
|
373
|
+
severity: "error",
|
|
374
|
+
code: "workspace_repo_path_missing",
|
|
375
|
+
scope: "project",
|
|
376
|
+
unitId: `workspace.repositories.${repo.id}`,
|
|
377
|
+
message: `declared repository "${repo.id}" path does not exist on disk: ${repo.root}`,
|
|
378
|
+
fixable: false,
|
|
379
|
+
});
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
// Must be a repo at its OWN root — a plain dir nested in the parent repo
|
|
383
|
+
// would otherwise pass a bare `nativeIsRepo` check via the enclosing
|
|
384
|
+
// parent's .git. Compare on realpath so symlinked temp roots (macOS
|
|
385
|
+
// /var vs /private/var) don't cause a false mismatch.
|
|
386
|
+
const toplevel = resolveGitToplevel(repo.root);
|
|
387
|
+
const declaredReal = realpathSafe(repo.root);
|
|
388
|
+
const toplevelReal = toplevel ? realpathSafe(toplevel) : null;
|
|
389
|
+
if (toplevelReal !== declaredReal) {
|
|
390
|
+
issues.push({
|
|
391
|
+
severity: "warning",
|
|
392
|
+
code: "workspace_repo_not_a_repo",
|
|
393
|
+
scope: "project",
|
|
394
|
+
unitId: `workspace.repositories.${repo.id}`,
|
|
395
|
+
message: `declared repository "${repo.id}" path is not a git repository at its own root: ${repo.root}`,
|
|
396
|
+
fixable: false,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
311
402
|
export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; dryRun?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch"; includeBuild?: boolean; includeTests?: boolean }): Promise<DoctorReport> {
|
|
312
403
|
const issues: DoctorIssue[] = [];
|
|
313
404
|
const fixesApplied: string[] = [];
|
|
@@ -366,6 +457,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
366
457
|
(prefs?.preferences?.git?.isolation === "worktree" ? "worktree" :
|
|
367
458
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "none");
|
|
368
459
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode, dryRun);
|
|
460
|
+
checkWorkspaceRepositoryHealth(basePath, prefs?.preferences, issues);
|
|
369
461
|
const gitMs = Date.now() - t0git;
|
|
370
462
|
|
|
371
463
|
// Runtime health checks — timed
|
|
@@ -386,7 +478,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
386
478
|
const envMs = Date.now() - t0env;
|
|
387
479
|
|
|
388
480
|
// Engine health checks — DB constraints and projection drift
|
|
389
|
-
await checkEngineHealth(basePath, issues, fixesApplied);
|
|
481
|
+
await checkEngineHealth(basePath, issues, fixesApplied, { repair: fix && !dryRun });
|
|
390
482
|
|
|
391
483
|
const milestonesPath = milestonesDir(basePath);
|
|
392
484
|
const legacyMilestonesPath2 = legacyMilestonesDir(basePath);
|
|
@@ -13,6 +13,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
import { resolve } from "node:path";
|
|
15
15
|
import { getShellConfig, killProcessTree, SIGKILL_GRACE_MS, HARD_DEADLINE_MS } from "@gsd/pi-coding-agent";
|
|
16
|
+
import { redactSecrets } from "./redact-secrets.js";
|
|
16
17
|
|
|
17
18
|
export interface ExecSandboxRequest {
|
|
18
19
|
/** Interpreter to use. */
|
|
@@ -315,8 +316,11 @@ export function runExecSandbox(
|
|
|
315
316
|
const stderrBuf = Buffer.concat(stderrChunks);
|
|
316
317
|
const stdoutSuffix = stdoutTruncated ? "\n[truncated: stdout cap reached]\n" : "";
|
|
317
318
|
const stderrSuffix = stderrTruncated ? "\n[truncated: stderr cap reached]\n" : "";
|
|
318
|
-
|
|
319
|
-
|
|
319
|
+
// Redact secret-shaped substrings before persisting to .gsd/exec/ (skipped by
|
|
320
|
+
// the secret scanner). The returned digest is derived from the raw buffer
|
|
321
|
+
// below and is redacted downstream when the session is written to activity/.
|
|
322
|
+
writeFileSync(stdoutPath, redactSecrets(Buffer.concat([stdoutBuf, Buffer.from(stdoutSuffix, "utf-8")]).toString("utf-8")));
|
|
323
|
+
writeFileSync(stderrPath, redactSecrets(Buffer.concat([stderrBuf, Buffer.from(stderrSuffix, "utf-8")]).toString("utf-8")));
|
|
320
324
|
|
|
321
325
|
const digestBody = tail(stdoutBuf, opts.digest_chars);
|
|
322
326
|
const digest =
|
|
@@ -11,18 +11,84 @@ import { migrateFromMarkdown } from "./md-importer.js";
|
|
|
11
11
|
import { countDbHierarchy } from "./migration-auto-check.js";
|
|
12
12
|
import { logWarning } from "./workflow-logger.js";
|
|
13
13
|
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
canonicalPhaseDirName,
|
|
16
|
+
dirIsContentBearingLegacyMilestone,
|
|
17
|
+
milestonesDir,
|
|
18
|
+
resolveMilestonePath,
|
|
19
|
+
} from "./paths.js";
|
|
15
20
|
|
|
16
21
|
const LEGACY_MIGRATING_SEGMENT = "milestones.migrating";
|
|
22
|
+
const RETRYABLE_FS_ERROR_CODES = new Set(["EPERM", "EBUSY", "ENOTEMPTY"]);
|
|
23
|
+
const RM_RETRY_OPTIONS = { recursive: true, force: true, maxRetries: 5, retryDelay: 100 } as const;
|
|
24
|
+
|
|
25
|
+
type FlatPhaseMigrationFsOps = {
|
|
26
|
+
cpSync: typeof cpSync;
|
|
27
|
+
renameSync: typeof renameSync;
|
|
28
|
+
rmSync: typeof rmSync;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let fsOps: FlatPhaseMigrationFsOps = { cpSync, renameSync, rmSync };
|
|
32
|
+
|
|
33
|
+
export function _setFlatPhaseMigrationFsOpsForTest(
|
|
34
|
+
overrides: Partial<FlatPhaseMigrationFsOps>,
|
|
35
|
+
): () => void {
|
|
36
|
+
const previous = fsOps;
|
|
37
|
+
fsOps = { ...fsOps, ...overrides };
|
|
38
|
+
return () => {
|
|
39
|
+
fsOps = previous;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
17
42
|
|
|
18
43
|
function legacyMigratingPath(basePath: string): string {
|
|
19
44
|
return join(basePath, ".gsd", LEGACY_MIGRATING_SEGMENT);
|
|
20
45
|
}
|
|
21
46
|
|
|
47
|
+
function removePathWithRetries(path: string): void {
|
|
48
|
+
fsOps.rmSync(path, { recursive: true, force: true, maxRetries: RM_RETRY_OPTIONS.maxRetries, retryDelay: RM_RETRY_OPTIONS.retryDelay });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRetryableFsError(err: unknown): boolean {
|
|
52
|
+
const code = (err as { code?: unknown } | null)?.code;
|
|
53
|
+
return typeof code === "string" && RETRYABLE_FS_ERROR_CODES.has(code);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function movePathWithCopyDeleteFallback(src: string, dst: string): void {
|
|
57
|
+
try {
|
|
58
|
+
fsOps.renameSync(src, dst);
|
|
59
|
+
return;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (!isRetryableFsError(err)) throw err;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
fsOps.cpSync(src, dst, { recursive: true, force: true });
|
|
66
|
+
removePathWithRetries(src);
|
|
67
|
+
} catch (fallbackErr) {
|
|
68
|
+
if (existsSync(src) && existsSync(dst)) {
|
|
69
|
+
try {
|
|
70
|
+
removePathWithRetries(dst);
|
|
71
|
+
} catch {
|
|
72
|
+
// Leave the original error intact; the next run can pre-clean dst.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw fallbackErr;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function expectedPhaseDirs(basePath: string): string[] {
|
|
80
|
+
return getAllMilestones().map((milestone) =>
|
|
81
|
+
resolveMilestonePath(basePath, milestone.id) ??
|
|
82
|
+
join(milestonesDir(basePath), canonicalPhaseDirName(milestone.id, milestone.title)),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
22
86
|
function hasLegacyMilestoneSubdirs(dirPath: string): boolean {
|
|
23
87
|
if (!existsSync(dirPath)) return false;
|
|
24
88
|
try {
|
|
25
|
-
return readdirSync(dirPath).some(
|
|
89
|
+
return readdirSync(dirPath).some(
|
|
90
|
+
(e) => statSync(join(dirPath, e)).isDirectory() && dirIsContentBearingLegacyMilestone(join(dirPath, e)),
|
|
91
|
+
);
|
|
26
92
|
} catch {
|
|
27
93
|
return false;
|
|
28
94
|
}
|
|
@@ -34,19 +100,26 @@ function rollbackPartialMigration(
|
|
|
34
100
|
migratingPath?: string,
|
|
35
101
|
): void {
|
|
36
102
|
// Remove the partially-written phases/ dir.
|
|
37
|
-
|
|
103
|
+
try {
|
|
104
|
+
removePathWithRetries(join(basePath, ".gsd", LAYOUT_SEGMENTS.level1));
|
|
105
|
+
} catch (removeErr) {
|
|
106
|
+
logWarning(
|
|
107
|
+
"migration",
|
|
108
|
+
`rollback: could not remove partial ${LAYOUT_SEGMENTS.level1}/: ${(removeErr as Error).message}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
38
111
|
// Restore milestones/ — prefer renaming the preserved migrating copy back.
|
|
39
112
|
const milestonesPath = join(basePath, ".gsd", "milestones");
|
|
40
113
|
try {
|
|
41
114
|
if (migratingPath && existsSync(migratingPath) && !existsSync(milestonesPath)) {
|
|
42
|
-
|
|
115
|
+
movePathWithCopyDeleteFallback(migratingPath, milestonesPath);
|
|
43
116
|
return;
|
|
44
117
|
}
|
|
45
118
|
if (existsSync(backupDir)) {
|
|
46
119
|
cpSync(backupDir, milestonesPath, { recursive: true });
|
|
47
120
|
}
|
|
48
121
|
if (migratingPath && existsSync(migratingPath)) {
|
|
49
|
-
|
|
122
|
+
removePathWithRetries(migratingPath);
|
|
50
123
|
}
|
|
51
124
|
} catch (restoreErr) {
|
|
52
125
|
logWarning(
|
|
@@ -162,18 +235,29 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
|
|
|
162
235
|
throw err;
|
|
163
236
|
}
|
|
164
237
|
|
|
238
|
+
if (existsSync(migratingPath)) {
|
|
239
|
+
removePathWithRetries(migratingPath);
|
|
240
|
+
}
|
|
241
|
+
|
|
165
242
|
// 3. Rename legacy tree aside before rendering so path resolvers target
|
|
166
243
|
// phases/ instead of writing back into the nested milestones/ layout.
|
|
167
244
|
// Keep the full tree on disk until render+verify succeed (unlike rmSync).
|
|
168
245
|
try {
|
|
169
|
-
|
|
246
|
+
movePathWithCopyDeleteFallback(milestonesPath, migratingPath);
|
|
170
247
|
} catch (err) {
|
|
171
248
|
logWarning("migration", `failed to rename legacy milestones/ before render: ${(err as Error).message}`);
|
|
172
249
|
throw err;
|
|
173
250
|
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Clear any stale or partially-rendered flat projection before this run
|
|
254
|
+
// writes. Verification below checks the current DB render, not leftovers.
|
|
255
|
+
try {
|
|
256
|
+
removePathWithRetries(phasesPath);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
logWarning("migration", `failed to clear stale phases/ before render: ${(err as Error).message}`);
|
|
259
|
+
rollbackPartialMigration(basePath, backupDir, migratingPath);
|
|
260
|
+
throw err;
|
|
177
261
|
}
|
|
178
262
|
|
|
179
263
|
// 4. Render flat-phase from DB
|
|
@@ -211,22 +295,14 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
|
|
|
211
295
|
}
|
|
212
296
|
|
|
213
297
|
const db = countDbHierarchy();
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// /^\d+-/ matches any numeric prefix (01-, 10-, 100-) so M100+ milestones
|
|
217
|
-
// whose dirs start with "100-slug" are counted correctly.
|
|
218
|
-
renderedDirCount = readdirSync(phasesPath, { withFileTypes: true })
|
|
219
|
-
.filter((entry) => entry.isDirectory() && /^\d+-/.test(entry.name)).length;
|
|
220
|
-
} catch {
|
|
221
|
-
// phases/ doesn't exist or is unreadable — same as zero dirs
|
|
222
|
-
}
|
|
223
|
-
if (db.milestones > 0 && renderedDirCount !== db.milestones) {
|
|
298
|
+
const missingPhaseDirs = expectedPhaseDirs(basePath).filter((phaseDir) => !existsSync(phaseDir));
|
|
299
|
+
if (db.milestones > 0 && missingPhaseDirs.length > 0) {
|
|
224
300
|
logWarning(
|
|
225
301
|
"migration",
|
|
226
|
-
`
|
|
302
|
+
`flat-phase migration missing ${missingPhaseDirs.length} expected phase dir(s): ${missingPhaseDirs.join(", ")}`,
|
|
227
303
|
);
|
|
228
304
|
rollbackPartialMigration(basePath, backupDir, migratingPath);
|
|
229
|
-
throw new Error("flat-phase migration verification failed:
|
|
305
|
+
throw new Error("flat-phase migration verification failed: missing rendered phase directories");
|
|
230
306
|
}
|
|
231
307
|
if (db.slices > 0 && renderResult.rendered === 0) {
|
|
232
308
|
logWarning(
|
|
@@ -239,7 +315,7 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
|
|
|
239
315
|
|
|
240
316
|
// 6. Verified — remove the renamed legacy tree (backup already on disk).
|
|
241
317
|
try {
|
|
242
|
-
|
|
318
|
+
removePathWithRetries(migratingPath);
|
|
243
319
|
} catch (err) {
|
|
244
320
|
logWarning(
|
|
245
321
|
"migration",
|
|
@@ -18,7 +18,7 @@ import { gsdRoot } from "./paths.js";
|
|
|
18
18
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
19
19
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
20
20
|
import { logWarning } from "./workflow-logger.js";
|
|
21
|
-
import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
|
|
21
|
+
import { createRepositoryRegistryFromPreferences, defaultRepositoryTargets } from "./repository-registry.js";
|
|
22
22
|
import { isGsdGitignored } from "./gitignore.js";
|
|
23
23
|
|
|
24
24
|
|
|
@@ -1266,26 +1266,25 @@ export function handleTurnGitActionError(action: TurnGitActionMode, err: unknown
|
|
|
1266
1266
|
};
|
|
1267
1267
|
}
|
|
1268
1268
|
|
|
1269
|
+
export function isRepositoryDirty(repoRoot: string): boolean {
|
|
1270
|
+
try {
|
|
1271
|
+
return runGit(repoRoot, ["status", "--porcelain"]).length > 0;
|
|
1272
|
+
} catch {
|
|
1273
|
+
// Fallback preserves legacy behavior if explicit status probing fails.
|
|
1274
|
+
return nativeHasChanges(repoRoot);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1269
1278
|
function collectRepositoryDirtyStatus(basePath: string): Record<string, boolean> {
|
|
1270
1279
|
const preferences = loadEffectiveGSDPreferences(basePath)?.preferences;
|
|
1271
1280
|
const registry = createRepositoryRegistryFromPreferences(basePath, preferences);
|
|
1272
1281
|
const dirtyByRepository: Record<string, boolean> = {};
|
|
1273
1282
|
const registeredRoots = new Set(registry.repositories.map((repo) => resolve(repo.root)));
|
|
1274
1283
|
for (const repo of registry.repositories) {
|
|
1275
|
-
|
|
1276
|
-
dirtyByRepository[repo.id] = runGit(repo.root, ["status", "--porcelain"]).length > 0;
|
|
1277
|
-
} catch {
|
|
1278
|
-
// Fallback preserves legacy behavior if explicit status probing fails.
|
|
1279
|
-
dirtyByRepository[repo.id] = nativeHasChanges(repo.root);
|
|
1280
|
-
}
|
|
1284
|
+
dirtyByRepository[repo.id] = isRepositoryDirty(repo.root);
|
|
1281
1285
|
}
|
|
1282
1286
|
for (const repoRoot of findUndeclaredNestedGitRepositories(registry.projectRoot, registeredRoots)) {
|
|
1283
|
-
|
|
1284
|
-
try {
|
|
1285
|
-
dirty = runGit(repoRoot, ["status", "--porcelain"]).length > 0;
|
|
1286
|
-
} catch {
|
|
1287
|
-
dirty = nativeHasChanges(repoRoot);
|
|
1288
|
-
}
|
|
1287
|
+
const dirty = isRepositoryDirty(repoRoot);
|
|
1289
1288
|
if (!dirty) continue;
|
|
1290
1289
|
|
|
1291
1290
|
const relPath = relative(registry.projectRoot, repoRoot).split(sep).join("/") || repoRoot;
|
|
@@ -1357,7 +1356,7 @@ function runPerRepositoryCommitAction(args: {
|
|
|
1357
1356
|
} {
|
|
1358
1357
|
const preferences = loadEffectiveGSDPreferences(args.basePath)?.preferences;
|
|
1359
1358
|
const registry = createRepositoryRegistryFromPreferences(args.basePath, preferences);
|
|
1360
|
-
const repoIds = args.targetRepositories?.length ? args.targetRepositories :
|
|
1359
|
+
const repoIds = args.targetRepositories?.length ? args.targetRepositories : defaultRepositoryTargets(registry);
|
|
1361
1360
|
const gitPrefs = preferences?.git ?? {};
|
|
1362
1361
|
const commitMessages: Record<string, string> = {};
|
|
1363
1362
|
const commitErrors: Record<string, string> = {};
|
|
@@ -1445,9 +1444,7 @@ export function runTurnGitAction(args: {
|
|
|
1445
1444
|
};
|
|
1446
1445
|
}
|
|
1447
1446
|
|
|
1448
|
-
const primaryMessage =
|
|
1449
|
-
repoCommitResult.commitMessages[args.targetRepositories?.[0] ?? "project"]
|
|
1450
|
-
?? Object.values(repoCommitResult.commitMessages)[0];
|
|
1447
|
+
const primaryMessage = Object.values(repoCommitResult.commitMessages)[0];
|
|
1451
1448
|
return {
|
|
1452
1449
|
action: args.action,
|
|
1453
1450
|
status: "ok",
|
|
@@ -535,6 +535,78 @@ export function updateTaskStatus(milestoneId: string, sliceId: string, taskId: s
|
|
|
535
535
|
applyStatusTransition({ entity: "task", milestoneId, sliceId, taskId, status, completedAt });
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
+
export function repairTaskCompletionFromSummary(t: {
|
|
539
|
+
milestoneId: string;
|
|
540
|
+
sliceId: string;
|
|
541
|
+
taskId: string;
|
|
542
|
+
title?: string;
|
|
543
|
+
oneLiner?: string;
|
|
544
|
+
narrative?: string;
|
|
545
|
+
verificationResult: string;
|
|
546
|
+
duration?: string;
|
|
547
|
+
completedAt: string;
|
|
548
|
+
blockerDiscovered?: boolean;
|
|
549
|
+
deviations?: string;
|
|
550
|
+
knownIssues?: string;
|
|
551
|
+
keyFiles?: string[];
|
|
552
|
+
keyDecisions?: string[];
|
|
553
|
+
fullSummaryMd: string;
|
|
554
|
+
}): void {
|
|
555
|
+
const db = getDbOrNull();
|
|
556
|
+
if (!db) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
557
|
+
transaction(() => {
|
|
558
|
+
const seq = db.prepare(
|
|
559
|
+
`SELECT COALESCE(MAX(sequence), 0) + 1 AS next_sequence
|
|
560
|
+
FROM tasks
|
|
561
|
+
WHERE milestone_id = :mid AND slice_id = :sid`,
|
|
562
|
+
).get({ ":mid": t.milestoneId, ":sid": t.sliceId }) as { next_sequence?: number } | undefined;
|
|
563
|
+
db.prepare(
|
|
564
|
+
`INSERT INTO tasks (
|
|
565
|
+
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
566
|
+
verification_result, duration, completed_at, blocker_discovered,
|
|
567
|
+
deviations, known_issues, key_files, key_decisions, full_summary_md,
|
|
568
|
+
sequence
|
|
569
|
+
) VALUES (
|
|
570
|
+
:milestone_id, :slice_id, :id, :title, 'complete', :one_liner, :narrative,
|
|
571
|
+
:verification_result, :duration, :completed_at, :blocker_discovered,
|
|
572
|
+
:deviations, :known_issues, :key_files, :key_decisions, :full_summary_md,
|
|
573
|
+
:sequence
|
|
574
|
+
)
|
|
575
|
+
ON CONFLICT(milestone_id, slice_id, id) DO UPDATE SET
|
|
576
|
+
title = CASE WHEN NULLIF(:title, '') IS NOT NULL THEN :title ELSE tasks.title END,
|
|
577
|
+
status = 'complete',
|
|
578
|
+
one_liner = CASE WHEN NULLIF(:one_liner, '') IS NOT NULL THEN :one_liner ELSE tasks.one_liner END,
|
|
579
|
+
narrative = CASE WHEN NULLIF(:narrative, '') IS NOT NULL THEN :narrative ELSE tasks.narrative END,
|
|
580
|
+
verification_result = :verification_result,
|
|
581
|
+
duration = CASE WHEN NULLIF(:duration, '') IS NOT NULL THEN :duration ELSE tasks.duration END,
|
|
582
|
+
completed_at = :completed_at,
|
|
583
|
+
blocker_discovered = :blocker_discovered,
|
|
584
|
+
deviations = CASE WHEN NULLIF(:deviations, '') IS NOT NULL THEN :deviations ELSE tasks.deviations END,
|
|
585
|
+
known_issues = CASE WHEN NULLIF(:known_issues, '') IS NOT NULL THEN :known_issues ELSE tasks.known_issues END,
|
|
586
|
+
key_files = :key_files,
|
|
587
|
+
key_decisions = :key_decisions,
|
|
588
|
+
full_summary_md = :full_summary_md`,
|
|
589
|
+
).run({
|
|
590
|
+
":milestone_id": t.milestoneId,
|
|
591
|
+
":slice_id": t.sliceId,
|
|
592
|
+
":id": t.taskId,
|
|
593
|
+
":title": t.title ?? "",
|
|
594
|
+
":one_liner": t.oneLiner ?? "",
|
|
595
|
+
":narrative": t.narrative ?? "",
|
|
596
|
+
":verification_result": t.verificationResult,
|
|
597
|
+
":duration": t.duration ?? "",
|
|
598
|
+
":completed_at": t.completedAt,
|
|
599
|
+
":blocker_discovered": t.blockerDiscovered ? 1 : 0,
|
|
600
|
+
":deviations": t.deviations ?? "",
|
|
601
|
+
":known_issues": t.knownIssues ?? "",
|
|
602
|
+
":key_files": JSON.stringify(t.keyFiles ?? []),
|
|
603
|
+
":key_decisions": JSON.stringify(t.keyDecisions ?? []),
|
|
604
|
+
":full_summary_md": t.fullSummaryMd,
|
|
605
|
+
":sequence": seq?.next_sequence ?? 1,
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
538
610
|
export function setTaskBlockerDiscovered(milestoneId: string, sliceId: string, taskId: string, discovered: boolean): void {
|
|
539
611
|
if (!getDbOrNull()!) return;
|
|
540
612
|
getDbOrNull()!.prepare(
|
|
@@ -209,6 +209,8 @@ const DOCTOR_FIX_HINTS: Partial<Record<DoctorIssueCode, string>> = {
|
|
|
209
209
|
state_file_stale: "Run `/gsd doctor fix` to rebuild the projection from the database.",
|
|
210
210
|
state_file_missing: "Run `/gsd doctor fix` to rebuild the projection from the database.",
|
|
211
211
|
projection_drift: "Run `/gsd doctor fix` to rebuild markdown projections from the database (DB is the source of truth).",
|
|
212
|
+
artifact_user_content_missing:
|
|
213
|
+
"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.",
|
|
212
214
|
uat_retry_exhausted: "Review the failing UAT criteria via `/gsd status`, fix the issue, then re-run `/gsd auto`.",
|
|
213
215
|
};
|
|
214
216
|
|
|
@@ -42,7 +42,7 @@ import { gsdHome } from "./gsd-home.js";
|
|
|
42
42
|
import {
|
|
43
43
|
gsdRoot, milestonesDir, legacyMilestonesDir, resolveMilestoneFile,
|
|
44
44
|
resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
|
|
45
|
-
relMilestoneFile, relSliceFile,
|
|
45
|
+
relMilestoneFile, relSliceFile, clearPathCache,
|
|
46
46
|
} from "./paths.js";
|
|
47
47
|
import { join } from "node:path";
|
|
48
48
|
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
@@ -101,6 +101,7 @@ import {
|
|
|
101
101
|
formatPriorContextBrief,
|
|
102
102
|
} from "./preparation.js";
|
|
103
103
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
104
|
+
import { countPlanMilestoneRoadmapSlices } from "./artifact-verification.js";
|
|
104
105
|
import type { MilestoneScope } from "./workspace.js";
|
|
105
106
|
import { clearPendingGate, extractDepthVerificationMilestoneId, getPendingGate } from "./bootstrap/write-gate.js";
|
|
106
107
|
import {
|
|
@@ -164,7 +165,7 @@ export const _scheduleAutoStartAfterIdleForTest = scheduleAutoStartAfterIdle;
|
|
|
164
165
|
// These thin wrappers accept a MilestoneScope so callers that already hold a
|
|
165
166
|
// pinned scope never have to re-derive (basePath, milestoneId) separately.
|
|
166
167
|
// The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
|
|
167
|
-
// state.ts
|
|
168
|
+
// state.ts remain the default for non-scope-specific units.
|
|
168
169
|
|
|
169
170
|
/**
|
|
170
171
|
* Scope-based overload of verifyExpectedArtifact.
|
|
@@ -176,9 +177,47 @@ export function verifyExpectedArtifactForScope(
|
|
|
176
177
|
unitType: string,
|
|
177
178
|
unitId: string,
|
|
178
179
|
): boolean {
|
|
180
|
+
if (
|
|
181
|
+
unitId === scope.milestoneId &&
|
|
182
|
+
(unitType === "discuss-milestone" || unitType === "plan-milestone")
|
|
183
|
+
) {
|
|
184
|
+
// Layout-aware scope paths use resolveMilestoneFile; clear stale dir listings
|
|
185
|
+
// primed before discuss/plan wrote CONTEXT.md or ROADMAP.md (see checkAutoStartAfterDiscuss).
|
|
186
|
+
clearPathCache();
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
unitId === scope.milestoneId &&
|
|
190
|
+
unitType === "discuss-milestone"
|
|
191
|
+
) {
|
|
192
|
+
const path = resolveExpectedArtifactPathForScope(scope, unitType, unitId);
|
|
193
|
+
return path ? existsSync(path) : false;
|
|
194
|
+
}
|
|
195
|
+
if (unitId === scope.milestoneId && unitType === "plan-milestone") {
|
|
196
|
+
const path = resolveExpectedArtifactPathForScope(scope, unitType, unitId);
|
|
197
|
+
return verifyScopedPlanMilestoneArtifact(path, unitType, unitId);
|
|
198
|
+
}
|
|
179
199
|
return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
|
|
180
200
|
}
|
|
181
201
|
|
|
202
|
+
function verifyScopedPlanMilestoneArtifact(
|
|
203
|
+
path: string | null,
|
|
204
|
+
unitType: string,
|
|
205
|
+
unitId: string,
|
|
206
|
+
): boolean {
|
|
207
|
+
if (!path || !existsSync(path)) return false;
|
|
208
|
+
try {
|
|
209
|
+
const roadmapContent = readFileSync(path, "utf-8");
|
|
210
|
+
if (countPlanMilestoneRoadmapSlices(roadmapContent) === 0) {
|
|
211
|
+
logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap has zero slices at ${path}`);
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
logWarning("recovery", `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
182
221
|
/**
|
|
183
222
|
* Scope-based overload of resolveExpectedArtifactPath.
|
|
184
223
|
* Returns the canonical absolute path (or null) using the scope's projectRoot.
|
|
@@ -188,6 +227,10 @@ export function resolveExpectedArtifactPathForScope(
|
|
|
188
227
|
unitType: string,
|
|
189
228
|
unitId: string,
|
|
190
229
|
): string | null {
|
|
230
|
+
if (unitId === scope.milestoneId) {
|
|
231
|
+
if (unitType === "discuss-milestone") return scope.contextFile();
|
|
232
|
+
if (unitType === "plan-milestone") return scope.roadmapFile();
|
|
233
|
+
}
|
|
191
234
|
return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
|
|
192
235
|
}
|
|
193
236
|
|
|
@@ -43,6 +43,7 @@ export const PROVIDER_REGISTRY: ProviderInfo[] = [
|
|
|
43
43
|
// must be in the canonical registry so all consumers see the same catalog.
|
|
44
44
|
// See: https://github.com/open-gsd/gsd-pi/issues/4541
|
|
45
45
|
{ id: "claude-code", label: "Claude Code CLI", category: "llm", authMode: "externalCli" },
|
|
46
|
+
{ id: "cursor-agent", label: "Cursor Agent", category: "llm", envVar: "CURSOR_API_KEY", authMode: "externalCli" },
|
|
46
47
|
{ id: "openai", label: "OpenAI", category: "llm", envVar: "OPENAI_API_KEY", prefixes: ["sk-"], dashboardUrl: "platform.openai.com/api-keys" },
|
|
47
48
|
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", authMode: "browserOAuth" },
|
|
48
49
|
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)",category: "llm", authMode: "browserOAuth" },
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getSliceScopedArtifacts,
|
|
21
21
|
getMilestoneSlices,
|
|
22
22
|
getSliceTasks,
|
|
23
|
+
getTasksBySliceIds,
|
|
23
24
|
getTask,
|
|
24
25
|
getSlice,
|
|
25
26
|
insertArtifact,
|
|
@@ -136,6 +137,12 @@ function meaningfulSection(value: string | null | undefined): string {
|
|
|
136
137
|
return trimmed;
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
function renderGateFindings(gate: GateRow): string {
|
|
141
|
+
const findings = gate.findings.trim() || `- **Verdict:** ${gate.verdict}\n- **Rationale:** ${gate.rationale}`;
|
|
142
|
+
if (gate.verdict !== "flag") return findings;
|
|
143
|
+
return `> **Warning:** Verdict: flag - concerns recorded below\n\n${findings}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
139
146
|
function pushIndented(lines: string[], value: string, indent = " "): void {
|
|
140
147
|
for (const line of value.split("\n")) {
|
|
141
148
|
lines.push(`${indent}${line}`);
|
|
@@ -356,7 +363,7 @@ function renderTaskPlanMarkdown(task: TaskRow, taskGates: GateRow[] = []): strin
|
|
|
356
363
|
if (gate && gate.verdict !== "omitted") {
|
|
357
364
|
lines.push(`## ${label}`);
|
|
358
365
|
lines.push("");
|
|
359
|
-
lines.push(
|
|
366
|
+
lines.push(renderGateFindings(gate));
|
|
360
367
|
lines.push("");
|
|
361
368
|
}
|
|
362
369
|
}
|
|
@@ -390,7 +397,7 @@ function renderSlicePlanMarkdown(slice: SliceRow, tasks: TaskRow[], gates: GateR
|
|
|
390
397
|
if (q3 && q3.verdict !== "omitted") {
|
|
391
398
|
lines.push("## Threat Surface");
|
|
392
399
|
lines.push("");
|
|
393
|
-
lines.push(
|
|
400
|
+
lines.push(renderGateFindings(q3));
|
|
394
401
|
lines.push("");
|
|
395
402
|
}
|
|
396
403
|
|
|
@@ -398,7 +405,7 @@ function renderSlicePlanMarkdown(slice: SliceRow, tasks: TaskRow[], gates: GateR
|
|
|
398
405
|
if (q4 && q4.verdict !== "omitted") {
|
|
399
406
|
lines.push("## Requirement Impact");
|
|
400
407
|
lines.push("");
|
|
401
|
-
lines.push(
|
|
408
|
+
lines.push(renderGateFindings(q4));
|
|
402
409
|
lines.push("");
|
|
403
410
|
}
|
|
404
411
|
|
|
@@ -876,6 +883,17 @@ export async function renderAllFromDb(basePath: string): Promise<RenderAllResult
|
|
|
876
883
|
const result: RenderAllResult = { rendered: 0, skipped: 0, errors: [] };
|
|
877
884
|
const milestones = getAllMilestones();
|
|
878
885
|
|
|
886
|
+
// Pre-fetch slices once per milestone and batch-load all tasks in one query
|
|
887
|
+
// family, avoiding an N+1 (previously one task query per slice).
|
|
888
|
+
const slicesByMilestone = new Map<string, SliceRow[]>();
|
|
889
|
+
const slicePairs: Array<{ milestoneId: string; sliceId: string }> = [];
|
|
890
|
+
for (const milestone of milestones) {
|
|
891
|
+
const slices = getMilestoneSlices(milestone.id);
|
|
892
|
+
slicesByMilestone.set(milestone.id, slices);
|
|
893
|
+
for (const slice of slices) slicePairs.push({ milestoneId: milestone.id, sliceId: slice.id });
|
|
894
|
+
}
|
|
895
|
+
const tasksBySlice = getTasksBySliceIds(slicePairs);
|
|
896
|
+
|
|
879
897
|
for (const milestone of milestones) {
|
|
880
898
|
// Render roadmap checkboxes
|
|
881
899
|
try {
|
|
@@ -894,8 +912,8 @@ export async function renderAllFromDb(basePath: string): Promise<RenderAllResult
|
|
|
894
912
|
result.errors.push(`milestone artifacts ${milestone.id}: ${(err as Error).message}`);
|
|
895
913
|
}
|
|
896
914
|
|
|
897
|
-
// Iterate slices
|
|
898
|
-
const slices =
|
|
915
|
+
// Iterate slices (pre-fetched above)
|
|
916
|
+
const slices = slicesByMilestone.get(milestone.id) ?? [];
|
|
899
917
|
for (const slice of slices) {
|
|
900
918
|
// Preserve slice-scoped artifacts imported from disk, including real PLAN
|
|
901
919
|
// fallback content when task rows cannot regenerate it.
|
|
@@ -931,8 +949,8 @@ export async function renderAllFromDb(basePath: string): Promise<RenderAllResult
|
|
|
931
949
|
);
|
|
932
950
|
}
|
|
933
951
|
|
|
934
|
-
// Iterate tasks
|
|
935
|
-
const tasks =
|
|
952
|
+
// Iterate tasks (batched above)
|
|
953
|
+
const tasks = tasksBySlice.get(`${milestone.id}\0${slice.id}`) ?? [];
|
|
936
954
|
for (const task of tasks) {
|
|
937
955
|
try {
|
|
938
956
|
const ok = await renderTaskSummary(
|