gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.d8826a445
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 +8 -5
- package/dist/headless-recover.d.ts +23 -0
- package/dist/headless-recover.js +93 -0
- package/dist/headless.js +9 -0
- package/dist/help-text.js +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
- package/dist/resources/extensions/gsd/auto/phases.js +7 -2
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +7 -58
- package/dist/resources/extensions/gsd/auto-post-unit.js +14 -28
- package/dist/resources/extensions/gsd/auto-start.js +1 -8
- package/dist/resources/extensions/gsd/auto-worktree.js +244 -216
- package/dist/resources/extensions/gsd/auto.js +86 -7
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
- package/dist/resources/extensions/gsd/commands-logs.js +2 -2
- package/dist/resources/extensions/gsd/commands-scan.js +2 -2
- package/dist/resources/extensions/gsd/commands-ship.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
- package/dist/resources/extensions/gsd/db-writer.js +106 -95
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
- package/dist/resources/extensions/gsd/gsd-db.js +268 -8
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +141 -32
- package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
- package/dist/resources/extensions/gsd/paths.js +114 -9
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/dist/resources/extensions/gsd/queue-order.js +6 -1
- package/dist/resources/extensions/gsd/rethink.js +2 -2
- package/dist/resources/extensions/gsd/state.js +91 -372
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
- package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-command.js +4 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- 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 +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- 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/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +12 -12
- package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
- package/dist/web/standalone/.next/server/chunks/6897.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/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +56 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +62 -3
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
- package/src/resources/extensions/gsd/auto/phases.ts +8 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +14 -62
- package/src/resources/extensions/gsd/auto-post-unit.ts +15 -27
- package/src/resources/extensions/gsd/auto-start.ts +1 -8
- package/src/resources/extensions/gsd/auto-worktree.ts +286 -251
- package/src/resources/extensions/gsd/auto.ts +102 -7
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
- package/src/resources/extensions/gsd/commands-logs.ts +2 -2
- package/src/resources/extensions/gsd/commands-scan.ts +2 -2
- package/src/resources/extensions/gsd/commands-ship.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
- package/src/resources/extensions/gsd/db-writer.ts +123 -94
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
- package/src/resources/extensions/gsd/gsd-db.ts +269 -8
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +181 -32
- package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
- package/src/resources/extensions/gsd/paths.ts +122 -9
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/src/resources/extensions/gsd/queue-order.ts +6 -1
- package/src/resources/extensions/gsd/rethink.ts +2 -2
- package/src/resources/extensions/gsd/state.ts +91 -389
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
- package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +26 -3
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
- package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
- package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-command.ts +4 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
|
@@ -56,11 +56,13 @@ import { getErrorMessage } from "./error-utils.js";
|
|
|
56
56
|
import { recoverFailedMigration } from "./migrate-external.js";
|
|
57
57
|
import { initRegistry, convertDispatchRules } from "./rule-registry.js";
|
|
58
58
|
import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
|
|
59
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
59
60
|
import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, } from "./auto-dashboard.js";
|
|
60
61
|
import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
|
|
61
62
|
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
62
63
|
import { countPendingCaptures } from "./captures.js";
|
|
63
64
|
import { CMUX_CHANNELS } from "../shared/cmux-events.js";
|
|
65
|
+
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
64
66
|
function makeCmuxEmitters(pi) {
|
|
65
67
|
return {
|
|
66
68
|
syncCmuxSidebar: (preferences, state) => pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences, state }),
|
|
@@ -84,6 +86,7 @@ import { reorderForCaching } from "./prompt-ordering.js";
|
|
|
84
86
|
export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
|
|
85
87
|
import { autoSession as s } from "./auto-runtime-state.js";
|
|
86
88
|
import { gsdHome } from "./gsd-home.js";
|
|
89
|
+
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
87
90
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
88
91
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
89
92
|
// This file must NOT declare module-level `let` or `var` variables for state.
|
|
@@ -145,6 +148,32 @@ function restoreMilestoneLockEnv() {
|
|
|
145
148
|
s.hadMilestoneLockEnv = false;
|
|
146
149
|
s.milestoneLockEnvCaptured = false;
|
|
147
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
|
|
153
|
+
*
|
|
154
|
+
* Pass the worktree path as rawPath when entering a worktree so createWorkspace
|
|
155
|
+
* can detect the worktree layout and set mode="worktree". When no worktree is
|
|
156
|
+
* active, rawPath should equal the project root.
|
|
157
|
+
*
|
|
158
|
+
* Clears s.scope when milestoneId is absent — scope is only meaningful when a
|
|
159
|
+
* milestone is active.
|
|
160
|
+
*
|
|
161
|
+
* TODO(C8): remove basePath/originalBasePath once all readers use s.scope.
|
|
162
|
+
*/
|
|
163
|
+
function rebuildScope(rawPath, milestoneId) {
|
|
164
|
+
if (!milestoneId) {
|
|
165
|
+
s.scope = null;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const workspace = createWorkspace(rawPath);
|
|
170
|
+
s.scope = scopeMilestone(workspace, milestoneId);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Non-fatal — scope is additive. Existing readers still use basePath.
|
|
174
|
+
s.scope = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
148
177
|
function normalizeSessionFilePath(raw) {
|
|
149
178
|
if (typeof raw !== "string")
|
|
150
179
|
return null;
|
|
@@ -282,6 +311,18 @@ export function isAutoActive() {
|
|
|
282
311
|
export function _setAutoActiveForTest(active) {
|
|
283
312
|
s.active = active;
|
|
284
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Test-only seam: emit the missing-worktree warning exactly as the resume path
|
|
316
|
+
* does. Allows unit tests to verify the warning is produced without
|
|
317
|
+
* bootstrapping the full auto-mode entry point. Do not use in production code.
|
|
318
|
+
*/
|
|
319
|
+
export function _warnIfWorktreeMissingForTest(worktreePath, milestoneId) {
|
|
320
|
+
if (worktreePath && !existsSync(worktreePath)) {
|
|
321
|
+
logWarning("session", `Worktree was expected at ${worktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId });
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
285
326
|
export function isAutoPaused() {
|
|
286
327
|
return s.paused;
|
|
287
328
|
}
|
|
@@ -1154,15 +1195,31 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1154
1195
|
|| !!freshStartAssessment.lock);
|
|
1155
1196
|
if (shouldResumePausedSession) {
|
|
1156
1197
|
// Validate the milestone still exists and isn't already complete (#1664).
|
|
1198
|
+
// DB status is authoritative when available; SUMMARY.md is a legacy
|
|
1199
|
+
// fallback only for unmigrated/offline projects.
|
|
1157
1200
|
const mDir = resolveMilestonePath(base, meta.milestoneId);
|
|
1158
|
-
const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
|
|
1159
1201
|
let summaryIsTerminal = false;
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1202
|
+
let dbAvailable = isDbAvailable();
|
|
1203
|
+
let milestoneRow = dbAvailable ? getMilestone(meta.milestoneId) : null;
|
|
1204
|
+
if (!milestoneRow) {
|
|
1205
|
+
const opened = await ensureDbOpen(base);
|
|
1206
|
+
dbAvailable = opened || isDbAvailable();
|
|
1207
|
+
if (dbAvailable) {
|
|
1208
|
+
milestoneRow = getMilestone(meta.milestoneId);
|
|
1163
1209
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1210
|
+
}
|
|
1211
|
+
if (dbAvailable) {
|
|
1212
|
+
summaryIsTerminal = !!milestoneRow && isClosedStatus(milestoneRow.status);
|
|
1213
|
+
}
|
|
1214
|
+
else {
|
|
1215
|
+
const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY");
|
|
1216
|
+
if (summaryFile) {
|
|
1217
|
+
try {
|
|
1218
|
+
summaryIsTerminal = classifyMilestoneSummaryContent(readFileSync(summaryFile, "utf-8")) !== "failure";
|
|
1219
|
+
}
|
|
1220
|
+
catch {
|
|
1221
|
+
summaryIsTerminal = false;
|
|
1222
|
+
}
|
|
1166
1223
|
}
|
|
1167
1224
|
}
|
|
1168
1225
|
if (!mDir || summaryIsTerminal) {
|
|
@@ -1186,6 +1243,18 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1186
1243
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
1187
1244
|
s.sessionMilestoneLock = meta.milestoneLock ?? null;
|
|
1188
1245
|
s.paused = true;
|
|
1246
|
+
// Build scope from persisted state. Use worktreePath when present and
|
|
1247
|
+
// still on disk so mode is detected correctly; fall back to project root.
|
|
1248
|
+
{
|
|
1249
|
+
const persistedWorktreePath = meta.worktreePath ?? null;
|
|
1250
|
+
if (persistedWorktreePath && !existsSync(persistedWorktreePath)) {
|
|
1251
|
+
logWarning("session", `Worktree was expected at ${persistedWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: meta.milestoneId ?? "" });
|
|
1252
|
+
}
|
|
1253
|
+
const rawForScope = (persistedWorktreePath && existsSync(persistedWorktreePath))
|
|
1254
|
+
? persistedWorktreePath
|
|
1255
|
+
: (s.originalBasePath || base);
|
|
1256
|
+
rebuildScope(rawForScope, s.currentMilestoneId);
|
|
1257
|
+
}
|
|
1189
1258
|
try {
|
|
1190
1259
|
unlinkSync(pausedPath);
|
|
1191
1260
|
}
|
|
@@ -1267,10 +1336,15 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1267
1336
|
// session (e.g. isolation mode changed, detectWorktreeName differs across
|
|
1268
1337
|
// process restarts). We guard with existsSync so a stale or deleted
|
|
1269
1338
|
// worktree directory safely falls back to the project root.
|
|
1270
|
-
const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath;
|
|
1339
|
+
const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath ?? null;
|
|
1340
|
+
if (resumeWorktreePath && !existsSync(resumeWorktreePath)) {
|
|
1341
|
+
logWarning("session", `Worktree was expected at ${resumeWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: s.currentMilestoneId ?? "" });
|
|
1342
|
+
}
|
|
1271
1343
|
if (resumeWorktreePath && existsSync(resumeWorktreePath)) {
|
|
1272
1344
|
s.basePath = resumeWorktreePath;
|
|
1273
1345
|
}
|
|
1346
|
+
// Rebuild scope now that s.basePath reflects the actual worktree (or project root).
|
|
1347
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1274
1348
|
// Ensure the workflow-logger audit log is pinned to the project root
|
|
1275
1349
|
// even when auto-mode is entered via a path that bypasses the
|
|
1276
1350
|
// bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
|
|
@@ -1297,6 +1371,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1297
1371
|
buildResolver().enterMilestone(s.currentMilestoneId, {
|
|
1298
1372
|
notify: ctx.ui.notify.bind(ctx.ui),
|
|
1299
1373
|
});
|
|
1374
|
+
// s.basePath may have been updated to a worktree path by enterMilestone.
|
|
1375
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1300
1376
|
}
|
|
1301
1377
|
registerSigtermHandler(lockBase());
|
|
1302
1378
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
@@ -1375,6 +1451,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1375
1451
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
1376
1452
|
if (!ready)
|
|
1377
1453
|
return;
|
|
1454
|
+
// Build scope after bootstrap has populated s.basePath / s.originalBasePath /
|
|
1455
|
+
// s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
|
|
1456
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1378
1457
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1379
1458
|
try {
|
|
1380
1459
|
pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
|
|
@@ -86,7 +86,7 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
86
86
|
logWarning("bootstrap", `checkDeepProjectSetupAfterTurn failed: ${message}`);
|
|
87
87
|
}
|
|
88
88
|
if (checkAutoStartAfterDiscuss()) {
|
|
89
|
-
clearDiscussionFlowState();
|
|
89
|
+
clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
// #4573 — When the LLM emits "Milestone X ready." but the required files
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
3
|
import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
|
|
4
4
|
import { DEFAULT_BASH_TIMEOUT_SECS } from "../constants.js";
|
|
5
5
|
import { setLogBasePath, logWarning } from "../workflow-logger.js";
|
|
6
|
+
import { resolveGsdPathContract } from "../paths.js";
|
|
6
7
|
/**
|
|
7
8
|
* Resolve the correct DB path for the current working directory.
|
|
8
9
|
* If `basePath` is inside a `.gsd/worktrees/<MID>/` directory, returns
|
|
@@ -10,67 +11,15 @@ import { setLogBasePath, logWarning } from "../workflow-logger.js";
|
|
|
10
11
|
* returns `<basePath>/.gsd/gsd.db`.
|
|
11
12
|
*/
|
|
12
13
|
export function resolveProjectRootDbPath(basePath) {
|
|
13
|
-
|
|
14
|
-
// A worktree path looks like: /project/root/.gsd/worktrees/M001/...
|
|
15
|
-
// We need to resolve back to /project/root/.gsd/gsd.db
|
|
16
|
-
const marker = `${sep}.gsd${sep}worktrees${sep}`;
|
|
17
|
-
const idx = basePath.indexOf(marker);
|
|
18
|
-
if (idx !== -1) {
|
|
19
|
-
const projectRoot = basePath.slice(0, idx);
|
|
20
|
-
return join(projectRoot, ".gsd", "gsd.db");
|
|
21
|
-
}
|
|
22
|
-
// Also handle forward-slash paths on all platforms
|
|
23
|
-
const fwdMarker = "/.gsd/worktrees/";
|
|
24
|
-
const fwdIdx = basePath.indexOf(fwdMarker);
|
|
25
|
-
if (fwdIdx !== -1) {
|
|
26
|
-
const projectRoot = basePath.slice(0, fwdIdx);
|
|
27
|
-
return join(projectRoot, ".gsd", "gsd.db");
|
|
28
|
-
}
|
|
29
|
-
// External-state layout: ~/.gsd/projects/<hash>/worktrees/<MID>/...
|
|
30
|
-
// Resolve to ~/.gsd/projects/<hash>/gsd.db (the canonical project DB) (#2952).
|
|
31
|
-
// Must be checked before the generic symlink-resolved handler: both match
|
|
32
|
-
// /.gsd/projects/<hash>/worktrees/ but require different resolution targets.
|
|
33
|
-
const extRe = /[/\\]\.gsd[/\\]projects[/\\][a-f0-9]+[/\\]worktrees(?:[/\\]|$)/;
|
|
34
|
-
const extMatch = extRe.exec(basePath);
|
|
35
|
-
if (extMatch) {
|
|
36
|
-
const matchStr = extMatch[0];
|
|
37
|
-
// Find the "/worktrees" portion within the match and slice up to it
|
|
38
|
-
const wtIdx = matchStr.search(/[/\\]worktrees(?:[/\\]|$)/);
|
|
39
|
-
const projectStateRoot = basePath.slice(0, extMatch.index + wtIdx);
|
|
40
|
-
return join(projectStateRoot, "gsd.db");
|
|
41
|
-
}
|
|
42
|
-
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/M001/...
|
|
43
|
-
// The project root is everything before /.gsd/projects/ (#2517)
|
|
44
|
-
const symlinkMarker = `${sep}.gsd${sep}projects${sep}`;
|
|
45
|
-
const symlinkIdx = basePath.indexOf(symlinkMarker);
|
|
46
|
-
if (symlinkIdx !== -1) {
|
|
47
|
-
const afterProjects = basePath.slice(symlinkIdx + symlinkMarker.length);
|
|
48
|
-
// Expect: <hash>/worktrees/...
|
|
49
|
-
const worktreeSeg = `${sep}worktrees${sep}`;
|
|
50
|
-
if (afterProjects.includes(worktreeSeg)) {
|
|
51
|
-
const projectRoot = basePath.slice(0, symlinkIdx);
|
|
52
|
-
return join(projectRoot, ".gsd", "gsd.db");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// Forward-slash variant for symlink-resolved layout
|
|
56
|
-
const fwdSymlinkMarker = "/.gsd/projects/";
|
|
57
|
-
const fwdSymlinkIdx = basePath.indexOf(fwdSymlinkMarker);
|
|
58
|
-
if (fwdSymlinkIdx !== -1) {
|
|
59
|
-
const afterProjects = basePath.slice(fwdSymlinkIdx + fwdSymlinkMarker.length);
|
|
60
|
-
if (afterProjects.includes("/worktrees/")) {
|
|
61
|
-
const projectRoot = basePath.slice(0, fwdSymlinkIdx);
|
|
62
|
-
return join(projectRoot, ".gsd", "gsd.db");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return join(basePath, ".gsd", "gsd.db");
|
|
14
|
+
return resolveGsdPathContract(basePath).projectDb;
|
|
66
15
|
}
|
|
67
16
|
export async function ensureDbOpen(basePath = process.cwd()) {
|
|
68
17
|
try {
|
|
69
18
|
const db = await import("../gsd-db.js");
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const projectRoot =
|
|
19
|
+
const contract = resolveGsdPathContract(basePath);
|
|
20
|
+
const dbPath = contract.projectDb;
|
|
21
|
+
const gsdDir = contract.projectGsd;
|
|
22
|
+
const projectRoot = dirname(dirname(dbPath));
|
|
74
23
|
// Open existing DB file (may be at project root for worktrees)
|
|
75
24
|
if (existsSync(dbPath)) {
|
|
76
25
|
const opened = db.openDatabase(dbPath);
|
|
@@ -78,26 +27,9 @@ export async function ensureDbOpen(basePath = process.cwd()) {
|
|
|
78
27
|
setLogBasePath(projectRoot);
|
|
79
28
|
return opened;
|
|
80
29
|
}
|
|
81
|
-
// No DB file — create
|
|
30
|
+
// No DB file — create an empty authoritative DB. Markdown migration is
|
|
31
|
+
// explicit-only; runtime startup must not import projections into state.
|
|
82
32
|
if (existsSync(gsdDir)) {
|
|
83
|
-
const hasDecisions = existsSync(join(gsdDir, "DECISIONS.md"));
|
|
84
|
-
const hasRequirements = existsSync(join(gsdDir, "REQUIREMENTS.md"));
|
|
85
|
-
const hasMilestones = existsSync(join(gsdDir, "milestones"));
|
|
86
|
-
if (hasDecisions || hasRequirements || hasMilestones) {
|
|
87
|
-
const opened = db.openDatabase(dbPath);
|
|
88
|
-
if (opened) {
|
|
89
|
-
setLogBasePath(projectRoot);
|
|
90
|
-
try {
|
|
91
|
-
const { migrateFromMarkdown } = await import("../md-importer.js");
|
|
92
|
-
migrateFromMarkdown(basePath);
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
logWarning("bootstrap", `ensureDbOpen auto-migration failed: ${err.message}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return opened;
|
|
99
|
-
}
|
|
100
|
-
// .gsd/ exists but has no Markdown content (fresh project) — create empty DB
|
|
101
33
|
const opened = db.openDatabase(dbPath);
|
|
102
34
|
if (opened)
|
|
103
35
|
setLogBasePath(projectRoot);
|
|
@@ -60,7 +60,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
60
60
|
const { initHealthWidget } = await import("../health-widget.js");
|
|
61
61
|
initHealthWidget(ctx);
|
|
62
62
|
}
|
|
63
|
-
resetWriteGateState();
|
|
63
|
+
resetWriteGateState(process.cwd());
|
|
64
64
|
resetToolCallLoopGuard();
|
|
65
65
|
approvalQuestionAbortInFlight = false;
|
|
66
66
|
await resetAskUserQuestionsTurnCache();
|
|
@@ -109,10 +109,10 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
109
109
|
pi.on("session_switch", async (_event, ctx) => {
|
|
110
110
|
initNotificationStore(process.cwd());
|
|
111
111
|
installNotifyInterceptor(ctx);
|
|
112
|
-
resetWriteGateState();
|
|
112
|
+
resetWriteGateState(process.cwd());
|
|
113
113
|
resetToolCallLoopGuard();
|
|
114
114
|
await resetAskUserQuestionsTurnCache();
|
|
115
|
-
clearDiscussionFlowState();
|
|
115
|
+
clearDiscussionFlowState(process.cwd());
|
|
116
116
|
await syncServiceTierStatus(ctx);
|
|
117
117
|
await applyDisabledModelProviderPolicy(ctx);
|
|
118
118
|
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
@@ -137,13 +137,14 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
137
137
|
// Wait for ecosystem loader to finish (no-op after first turn).
|
|
138
138
|
const { getEcosystemReadyPromise } = await import("../ecosystem/loader.js");
|
|
139
139
|
await getEcosystemReadyPromise();
|
|
140
|
+
const beforeAgentBasePath = process.cwd();
|
|
140
141
|
const pendingApprovalGate = getPendingGate();
|
|
141
142
|
if (pendingApprovalGate && isExplicitApprovalResponse(event.prompt, pendingApprovalGate)) {
|
|
142
|
-
markApprovalGateVerified(pendingApprovalGate);
|
|
143
|
+
markApprovalGateVerified(pendingApprovalGate, beforeAgentBasePath);
|
|
143
144
|
const milestoneId = extractDepthVerificationMilestoneId(pendingApprovalGate);
|
|
144
145
|
if (milestoneId)
|
|
145
|
-
markDepthVerified(milestoneId);
|
|
146
|
-
clearPendingGate();
|
|
146
|
+
markDepthVerified(milestoneId, beforeAgentBasePath);
|
|
147
|
+
clearPendingGate(beforeAgentBasePath);
|
|
147
148
|
}
|
|
148
149
|
// GSD's own context injection (existing behavior — unchanged).
|
|
149
150
|
const { buildBeforeAgentStartResult } = await import("./system-context.js");
|
|
@@ -318,7 +319,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
318
319
|
return;
|
|
319
320
|
const gateId = approvalGateIdForUnit(unitType, unitId);
|
|
320
321
|
if (gateId)
|
|
321
|
-
setPendingGate(gateId);
|
|
322
|
+
setPendingGate(gateId, process.cwd());
|
|
322
323
|
approvalQuestionAbortInFlight = true;
|
|
323
324
|
ctx.ui.notify(`${unitType}${unitId ? ` ${unitId}` : ""} is waiting for your approval - pausing before more tool calls run.`, "info");
|
|
324
325
|
// The pending gate set above blocks subsequent non-read-only tool calls
|
|
@@ -360,7 +361,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
360
361
|
const questions = event.input?.questions ?? [];
|
|
361
362
|
const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
|
|
362
363
|
if (typeof questionId === "string") {
|
|
363
|
-
setPendingGate(questionId);
|
|
364
|
+
setPendingGate(questionId, discussionBasePath);
|
|
364
365
|
}
|
|
365
366
|
}
|
|
366
367
|
// ── Discussion gate enforcement: block tool calls while gate is pending ──
|
|
@@ -501,7 +502,8 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
501
502
|
const toolName = canonicalToolName(event.toolName);
|
|
502
503
|
if (toolName !== "ask_user_questions")
|
|
503
504
|
return;
|
|
504
|
-
const
|
|
505
|
+
const basePath = process.cwd();
|
|
506
|
+
const milestoneId = await getDiscussionMilestoneIdFor(basePath);
|
|
505
507
|
const queueActive = isQueuePhaseActive();
|
|
506
508
|
const details = event.details;
|
|
507
509
|
// ── Discussion gate enforcement: handle gate question responses ──
|
|
@@ -533,11 +535,11 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
533
535
|
if (pendingQuestion) {
|
|
534
536
|
const answer = details.response?.answers?.[currentPendingGate];
|
|
535
537
|
if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
|
|
536
|
-
markApprovalGateVerified(currentPendingGate);
|
|
538
|
+
markApprovalGateVerified(currentPendingGate, basePath);
|
|
537
539
|
const milestoneIdFromGate = extractDepthVerificationMilestoneId(currentPendingGate);
|
|
538
540
|
if (milestoneIdFromGate)
|
|
539
|
-
markDepthVerified(milestoneIdFromGate);
|
|
540
|
-
clearPendingGate();
|
|
541
|
+
markDepthVerified(milestoneIdFromGate, basePath);
|
|
542
|
+
clearPendingGate(basePath);
|
|
541
543
|
}
|
|
542
544
|
}
|
|
543
545
|
}
|
|
@@ -553,9 +555,9 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
553
555
|
if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
|
|
554
556
|
if (currentPendingGate && question.id !== currentPendingGate)
|
|
555
557
|
break;
|
|
556
|
-
markApprovalGateVerified(question.id);
|
|
557
|
-
markDepthVerified(inferredMilestoneId);
|
|
558
|
-
clearPendingGate();
|
|
558
|
+
markApprovalGateVerified(question.id, basePath);
|
|
559
|
+
markDepthVerified(inferredMilestoneId, basePath);
|
|
560
|
+
clearPendingGate(basePath);
|
|
559
561
|
}
|
|
560
562
|
break;
|
|
561
563
|
}
|
|
@@ -564,7 +566,6 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
564
566
|
return;
|
|
565
567
|
if (!milestoneId)
|
|
566
568
|
return;
|
|
567
|
-
const basePath = process.cwd();
|
|
568
569
|
const milestoneDir = resolveMilestonePath(basePath, milestoneId);
|
|
569
570
|
if (!milestoneDir)
|
|
570
571
|
return;
|
|
@@ -55,19 +55,31 @@ const QUEUE_SAFE_TOOLS = new Set([
|
|
|
55
55
|
* true / false — shell no-ops / test exit codes
|
|
56
56
|
*/
|
|
57
57
|
const BASH_READ_ONLY_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut|tee\s+-a\s+\/dev\/null|git\s+(log|show|diff|status|branch|tag|remote|rev-parse|ls-files|blame|shortlog|describe|stash\s+list|config\s+--get|cat-file)|gh\s+(issue|pr|api|repo|release)\s+(view|list|diff|status|checks)|mkdir\s+-p\s+\.gsd|rtk\s|npm\s+run\s+(test|test:\w+|lint|lint:\w+|typecheck|type-check|type-check:\w+|check|verify|audit|outdated|format:check|ci|validate)\b|npm\s+(ls|list|info|view|show|outdated|audit|explain|doctor|ping|--version|-v)\b|npx\s|tsx\s|node\s+(--print|--version|-v\b)|python[23]?\s+(-c\s+'[^']*'|--version|-V\b|-m\s+(pip\s+show|pip\s+list|site))|pip[23]?\s+(show|list|freeze|check|index\s+versions)\b|jq\s|yq\s|curl\s+(-s\b|--silent\b)(?!\s+[^|>]*\s-[oO]\b)(?!\s+[^|>]*\s--output\b)[^|>]*$|openssl\s+(version|x509|s_client)|env\b|printenv\b|true\b|false\b)/;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
function createEmptyWriteGateState() {
|
|
59
|
+
return {
|
|
60
|
+
verifiedDepthMilestones: new Set(),
|
|
61
|
+
verifiedApprovalGates: new Set(),
|
|
62
|
+
activeQueuePhase: false,
|
|
63
|
+
pendingGateId: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const writeGateStatesByBasePath = new Map();
|
|
67
|
+
function writeGateStateKey(basePath) {
|
|
68
|
+
return resolve(basePath);
|
|
69
|
+
}
|
|
70
|
+
function getWriteGateState(basePath = process.cwd()) {
|
|
71
|
+
const key = writeGateStateKey(basePath);
|
|
72
|
+
let state = writeGateStatesByBasePath.get(key);
|
|
73
|
+
if (!state) {
|
|
74
|
+
state = createEmptyWriteGateState();
|
|
75
|
+
writeGateStatesByBasePath.set(key, state);
|
|
76
|
+
}
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
61
79
|
/**
|
|
62
|
-
* Discussion gate enforcement state
|
|
63
|
-
*
|
|
64
|
-
* When ask_user_questions is called with a recognized gate question ID,
|
|
65
|
-
* we track the pending gate. Until the gate is confirmed (user selects the
|
|
66
|
-
* first/recommended option), all non-read-only tool calls are blocked.
|
|
67
|
-
* This mechanically prevents the model from rationalizing past failed or
|
|
68
|
-
* cancelled gate questions.
|
|
80
|
+
* Discussion gate enforcement state is scoped per basePath so multiple
|
|
81
|
+
* workspaces can coexist in the same process without sharing gate state.
|
|
69
82
|
*/
|
|
70
|
-
let pendingGateId = null;
|
|
71
83
|
/**
|
|
72
84
|
* Recognized gate question ID patterns.
|
|
73
85
|
* These appear in discuss.md (depth/requirements/roadmap).
|
|
@@ -99,24 +111,25 @@ function shouldPersistWriteGateSnapshot(env = process.env) {
|
|
|
99
111
|
const v = env.GSD_PERSIST_WRITE_GATE_STATE;
|
|
100
112
|
return v !== "0" && v !== "false";
|
|
101
113
|
}
|
|
102
|
-
function writeGateSnapshotPath(basePath
|
|
114
|
+
function writeGateSnapshotPath(basePath) {
|
|
103
115
|
return join(basePath, ".gsd", "runtime", "write-gate-state.json");
|
|
104
116
|
}
|
|
105
|
-
function currentWriteGateSnapshot() {
|
|
117
|
+
function currentWriteGateSnapshot(basePath = process.cwd()) {
|
|
118
|
+
const state = getWriteGateState(basePath);
|
|
106
119
|
return {
|
|
107
|
-
verifiedDepthMilestones: [...verifiedDepthMilestones].sort(),
|
|
108
|
-
verifiedApprovalGates: [...verifiedApprovalGates].sort(),
|
|
109
|
-
activeQueuePhase,
|
|
110
|
-
pendingGateId,
|
|
120
|
+
verifiedDepthMilestones: [...state.verifiedDepthMilestones].sort(),
|
|
121
|
+
verifiedApprovalGates: [...state.verifiedApprovalGates].sort(),
|
|
122
|
+
activeQueuePhase: state.activeQueuePhase,
|
|
123
|
+
pendingGateId: state.pendingGateId,
|
|
111
124
|
};
|
|
112
125
|
}
|
|
113
|
-
function persistWriteGateSnapshot(basePath
|
|
126
|
+
function persistWriteGateSnapshot(basePath) {
|
|
114
127
|
if (!shouldPersistWriteGateSnapshot())
|
|
115
128
|
return;
|
|
116
129
|
const path = writeGateSnapshotPath(basePath);
|
|
117
130
|
mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
|
|
118
131
|
const tempPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
119
|
-
writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(), null, 2), "utf-8");
|
|
132
|
+
writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(basePath), null, 2), "utf-8");
|
|
120
133
|
try {
|
|
121
134
|
renameSync(tempPath, path);
|
|
122
135
|
}
|
|
@@ -132,7 +145,7 @@ function persistWriteGateSnapshot(basePath = process.cwd()) {
|
|
|
132
145
|
}
|
|
133
146
|
}
|
|
134
147
|
}
|
|
135
|
-
function clearPersistedWriteGateSnapshot(basePath
|
|
148
|
+
function clearPersistedWriteGateSnapshot(basePath) {
|
|
136
149
|
if (!shouldPersistWriteGateSnapshot())
|
|
137
150
|
return;
|
|
138
151
|
const path = writeGateSnapshotPath(basePath);
|
|
@@ -164,7 +177,7 @@ const EMPTY_SNAPSHOT = {
|
|
|
164
177
|
activeQueuePhase: false,
|
|
165
178
|
pendingGateId: null,
|
|
166
179
|
};
|
|
167
|
-
export function loadWriteGateSnapshot(basePath
|
|
180
|
+
export function loadWriteGateSnapshot(basePath) {
|
|
168
181
|
const path = writeGateSnapshotPath(basePath);
|
|
169
182
|
if (!existsSync(path)) {
|
|
170
183
|
// When persist mode is active and the file has been deleted, treat it as a
|
|
@@ -172,61 +185,59 @@ export function loadWriteGateSnapshot(basePath = process.cwd()) {
|
|
|
172
185
|
// In non-persist mode the file is never written, so fall back to in-memory.
|
|
173
186
|
if (shouldPersistWriteGateSnapshot())
|
|
174
187
|
return EMPTY_SNAPSHOT;
|
|
175
|
-
return currentWriteGateSnapshot();
|
|
188
|
+
return currentWriteGateSnapshot(basePath);
|
|
176
189
|
}
|
|
177
190
|
try {
|
|
178
191
|
return normalizeWriteGateSnapshot(JSON.parse(readFileSync(path, "utf-8")));
|
|
179
192
|
}
|
|
180
193
|
catch {
|
|
181
|
-
return currentWriteGateSnapshot();
|
|
194
|
+
return currentWriteGateSnapshot(basePath);
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
|
-
export function isDepthVerified() {
|
|
185
|
-
return verifiedDepthMilestones.size > 0;
|
|
197
|
+
export function isDepthVerified(basePath = process.cwd()) {
|
|
198
|
+
return getWriteGateState(basePath).verifiedDepthMilestones.size > 0;
|
|
186
199
|
}
|
|
187
200
|
/**
|
|
188
201
|
* Check whether a specific milestone has passed depth verification.
|
|
189
202
|
*/
|
|
190
|
-
export function isMilestoneDepthVerified(milestoneId) {
|
|
203
|
+
export function isMilestoneDepthVerified(milestoneId, basePath = process.cwd()) {
|
|
191
204
|
if (!milestoneId)
|
|
192
205
|
return false;
|
|
193
|
-
return verifiedDepthMilestones.has(milestoneId);
|
|
206
|
+
return getWriteGateState(basePath).verifiedDepthMilestones.has(milestoneId);
|
|
194
207
|
}
|
|
195
208
|
export function isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId) {
|
|
196
209
|
if (!milestoneId)
|
|
197
210
|
return false;
|
|
198
211
|
return snapshot.verifiedDepthMilestones.includes(milestoneId);
|
|
199
212
|
}
|
|
200
|
-
export function isQueuePhaseActive() {
|
|
201
|
-
return activeQueuePhase;
|
|
213
|
+
export function isQueuePhaseActive(basePath = process.cwd()) {
|
|
214
|
+
return getWriteGateState(basePath).activeQueuePhase;
|
|
202
215
|
}
|
|
203
|
-
export function setQueuePhaseActive(active) {
|
|
204
|
-
activeQueuePhase = active;
|
|
205
|
-
persistWriteGateSnapshot();
|
|
216
|
+
export function setQueuePhaseActive(active, basePath) {
|
|
217
|
+
getWriteGateState(basePath).activeQueuePhase = active;
|
|
218
|
+
persistWriteGateSnapshot(basePath);
|
|
206
219
|
}
|
|
207
|
-
export function resetWriteGateState() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
export function resetWriteGateState(basePath) {
|
|
221
|
+
const state = getWriteGateState(basePath);
|
|
222
|
+
state.verifiedDepthMilestones.clear();
|
|
223
|
+
state.verifiedApprovalGates.clear();
|
|
224
|
+
state.pendingGateId = null;
|
|
225
|
+
persistWriteGateSnapshot(basePath);
|
|
212
226
|
}
|
|
213
|
-
export function clearDiscussionFlowState() {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
activeQueuePhase = false;
|
|
217
|
-
pendingGateId = null;
|
|
218
|
-
clearPersistedWriteGateSnapshot();
|
|
227
|
+
export function clearDiscussionFlowState(basePath) {
|
|
228
|
+
writeGateStatesByBasePath.delete(writeGateStateKey(basePath));
|
|
229
|
+
clearPersistedWriteGateSnapshot(basePath);
|
|
219
230
|
}
|
|
220
231
|
export function markDepthVerified(milestoneId, basePath = process.cwd()) {
|
|
221
232
|
if (!milestoneId)
|
|
222
233
|
return;
|
|
223
|
-
verifiedDepthMilestones.add(milestoneId);
|
|
234
|
+
getWriteGateState(basePath).verifiedDepthMilestones.add(milestoneId);
|
|
224
235
|
persistWriteGateSnapshot(basePath);
|
|
225
236
|
}
|
|
226
237
|
export function markApprovalGateVerified(gateId, basePath = process.cwd()) {
|
|
227
238
|
if (!gateId)
|
|
228
239
|
return;
|
|
229
|
-
verifiedApprovalGates.add(gateId);
|
|
240
|
+
getWriteGateState(basePath).verifiedApprovalGates.add(gateId);
|
|
230
241
|
persistWriteGateSnapshot(basePath);
|
|
231
242
|
}
|
|
232
243
|
export function isApprovalGateVerifiedInSnapshot(snapshot, gateId) {
|
|
@@ -258,26 +269,27 @@ function extractContextMilestoneId(inputPath) {
|
|
|
258
269
|
/**
|
|
259
270
|
* Mark a gate as pending (called when ask_user_questions is invoked with a gate ID).
|
|
260
271
|
*/
|
|
261
|
-
export function setPendingGate(gateId) {
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
export function setPendingGate(gateId, basePath) {
|
|
273
|
+
const state = getWriteGateState(basePath);
|
|
274
|
+
state.pendingGateId = gateId;
|
|
275
|
+
state.verifiedApprovalGates.delete(gateId);
|
|
264
276
|
const milestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
265
277
|
if (milestoneId)
|
|
266
|
-
verifiedDepthMilestones.delete(milestoneId);
|
|
267
|
-
persistWriteGateSnapshot();
|
|
278
|
+
state.verifiedDepthMilestones.delete(milestoneId);
|
|
279
|
+
persistWriteGateSnapshot(basePath);
|
|
268
280
|
}
|
|
269
281
|
/**
|
|
270
282
|
* Clear the pending gate (called when the user confirms).
|
|
271
283
|
*/
|
|
272
|
-
export function clearPendingGate() {
|
|
273
|
-
pendingGateId = null;
|
|
274
|
-
persistWriteGateSnapshot();
|
|
284
|
+
export function clearPendingGate(basePath) {
|
|
285
|
+
getWriteGateState(basePath).pendingGateId = null;
|
|
286
|
+
persistWriteGateSnapshot(basePath);
|
|
275
287
|
}
|
|
276
288
|
/**
|
|
277
289
|
* Get the currently pending gate, if any.
|
|
278
290
|
*/
|
|
279
|
-
export function getPendingGate() {
|
|
280
|
-
return pendingGateId;
|
|
291
|
+
export function getPendingGate(basePath = process.cwd()) {
|
|
292
|
+
return getWriteGateState(basePath).pendingGateId;
|
|
281
293
|
}
|
|
282
294
|
/**
|
|
283
295
|
* Check whether a tool call should be blocked because a discussion gate
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { generateCodebaseMap, updateCodebaseMap, writeCodebaseMap, getCodebaseMapStats, readCodebaseMap, } from "./codebase-generator.js";
|
|
8
8
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
9
|
-
import {
|
|
9
|
+
import { currentDirectoryRoot } from "./commands/context.js";
|
|
10
10
|
const USAGE = "Usage: /gsd codebase [generate|update|stats]\n\n" +
|
|
11
11
|
" generate [--max-files N] [--collapse-threshold N] — Generate or regenerate CODEBASE.md\n" +
|
|
12
12
|
" update [--max-files N] [--collapse-threshold N] — Refresh the CODEBASE.md cache immediately\n" +
|
|
@@ -20,7 +20,7 @@ const USAGE = "Usage: /gsd codebase [generate|update|stats]\n\n" +
|
|
|
20
20
|
" max_files: 1000\n" +
|
|
21
21
|
" collapse_threshold: 15";
|
|
22
22
|
export async function handleCodebase(args, ctx, _pi) {
|
|
23
|
-
const basePath =
|
|
23
|
+
const basePath = currentDirectoryRoot();
|
|
24
24
|
const parts = args.trim().split(/\s+/);
|
|
25
25
|
const sub = parts[0] ?? "";
|
|
26
26
|
switch (sub) {
|