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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Critical invariant: generated markdown must round-trip through
|
|
9
9
|
// parseDecisionsTable() and parseRequirementsSections() with field fidelity.
|
|
10
10
|
|
|
11
|
-
import { join, resolve } from 'node:path';
|
|
11
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
12
12
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
13
13
|
import type { Decision, Requirement } from './types.js';
|
|
14
14
|
import { resolveGsdRootFile } from './paths.js';
|
|
@@ -18,6 +18,8 @@ import { logWarning, logError } from './workflow-logger.js';
|
|
|
18
18
|
import { invalidateStateCache } from './state.js';
|
|
19
19
|
import { clearPathCache } from './paths.js';
|
|
20
20
|
import { clearParseCache } from './files.js';
|
|
21
|
+
import type { MilestoneScope, GsdWorkspace } from './workspace.js';
|
|
22
|
+
import { createWorkspace, scopeMilestone } from './workspace.js';
|
|
21
23
|
|
|
22
24
|
// ─── Freeform Detection ───────────────────────────────────────────────────
|
|
23
25
|
|
|
@@ -318,23 +320,6 @@ export async function saveRequirementToDb(
|
|
|
318
320
|
LIMIT 1`,
|
|
319
321
|
)
|
|
320
322
|
.get({ ':description': fields.description });
|
|
321
|
-
const previousRow: Requirement | null = existingRow
|
|
322
|
-
? {
|
|
323
|
-
id: existingRow['id'] as string,
|
|
324
|
-
class: existingRow['class'] as string,
|
|
325
|
-
status: existingRow['status'] as string,
|
|
326
|
-
description: existingRow['description'] as string,
|
|
327
|
-
why: existingRow['why'] as string,
|
|
328
|
-
source: existingRow['source'] as string,
|
|
329
|
-
primary_owner: existingRow['primary_owner'] as string,
|
|
330
|
-
supporting_slices: existingRow['supporting_slices'] as string,
|
|
331
|
-
validation: existingRow['validation'] as string,
|
|
332
|
-
notes: existingRow['notes'] as string,
|
|
333
|
-
full_content: existingRow['full_content'] as string,
|
|
334
|
-
superseded_by: (existingRow['superseded_by'] as string) ?? null,
|
|
335
|
-
}
|
|
336
|
-
: null;
|
|
337
|
-
|
|
338
323
|
const row = adapter
|
|
339
324
|
.prepare('SELECT MAX(CAST(SUBSTR(id, 2) AS INTEGER)) as max_num FROM requirements')
|
|
340
325
|
.get();
|
|
@@ -361,9 +346,9 @@ export async function saveRequirementToDb(
|
|
|
361
346
|
};
|
|
362
347
|
|
|
363
348
|
db.upsertRequirement(requirement);
|
|
364
|
-
return { id: nextId
|
|
349
|
+
return { id: nextId };
|
|
365
350
|
});
|
|
366
|
-
const { id
|
|
351
|
+
const { id } = txResult;
|
|
367
352
|
|
|
368
353
|
// Fetch all requirements for full file regeneration
|
|
369
354
|
const adapter = db._getAdapter();
|
|
@@ -392,17 +377,7 @@ export async function saveRequirementToDb(
|
|
|
392
377
|
try {
|
|
393
378
|
await saveFile(filePath, md);
|
|
394
379
|
} catch (diskErr) {
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
if (isNew) {
|
|
398
|
-
db.deleteRequirementById(id);
|
|
399
|
-
} else if (previousRow) {
|
|
400
|
-
db.upsertRequirement(previousRow);
|
|
401
|
-
}
|
|
402
|
-
} catch (rollbackErr) {
|
|
403
|
-
logError('manifest', 'SPLIT BRAIN: disk write failed AND DB rollback failed — DB has orphaned row', { fn: 'saveRequirementToDb', id, error: String((rollbackErr as Error).message) });
|
|
404
|
-
}
|
|
405
|
-
throw diskErr;
|
|
380
|
+
logWarning('projection', 'REQUIREMENTS.md projection write failed; DB requirement remains committed', { fn: 'saveRequirementToDb', id, error: String((diskErr as Error).message) });
|
|
406
381
|
}
|
|
407
382
|
invalidateStateCache();
|
|
408
383
|
clearPathCache();
|
|
@@ -538,13 +513,7 @@ export async function saveDecisionToDb(
|
|
|
538
513
|
try {
|
|
539
514
|
await saveFile(filePath, md);
|
|
540
515
|
} catch (diskErr) {
|
|
541
|
-
|
|
542
|
-
try {
|
|
543
|
-
db.deleteDecisionById(id);
|
|
544
|
-
} catch (rollbackErr) {
|
|
545
|
-
logError('manifest', 'SPLIT BRAIN: disk write failed AND DB rollback failed — DB has orphaned row', { fn: 'saveDecisionToDb', id, error: String((rollbackErr as Error).message) });
|
|
546
|
-
}
|
|
547
|
-
throw diskErr;
|
|
516
|
+
logWarning('projection', 'DECISIONS.md projection write failed; DB decision remains committed', { fn: 'saveDecisionToDb', id, error: String((diskErr as Error).message) });
|
|
548
517
|
}
|
|
549
518
|
// #2661: When a decision defers a slice, update the slice status in the DB
|
|
550
519
|
// so the dispatcher skips it. Without this, STATE.md and DECISIONS.md are
|
|
@@ -667,34 +636,7 @@ export async function updateRequirementInDb(
|
|
|
667
636
|
try {
|
|
668
637
|
const db = await import('./gsd-db.js');
|
|
669
638
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
// If requirement doesn't exist in DB, seed the entire requirements table
|
|
673
|
-
// from REQUIREMENTS.md first (#3346). This handles the standard workflow
|
|
674
|
-
// where requirements are authored in markdown during discussion but never
|
|
675
|
-
// imported into the database — making gsd_requirement_update always fail
|
|
676
|
-
// with "not_found" at milestone completion.
|
|
677
|
-
if (!existing) {
|
|
678
|
-
const reqFilePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
|
|
679
|
-
try {
|
|
680
|
-
const content = readFileSync(reqFilePath, 'utf-8');
|
|
681
|
-
const { parseRequirementsSections } = await import('./md-importer.js');
|
|
682
|
-
const parsed = parseRequirementsSections(content);
|
|
683
|
-
if (parsed.length > 0) {
|
|
684
|
-
logWarning('manifest', `Seeding ${parsed.length} requirements from REQUIREMENTS.md into DB (first update triggers import)`, { fn: 'updateRequirementInDb' });
|
|
685
|
-
for (const req of parsed) {
|
|
686
|
-
// Only seed if not already in DB (avoid overwriting concurrent inserts)
|
|
687
|
-
if (!db.getRequirementById(req.id)) {
|
|
688
|
-
db.upsertRequirement(req);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
// Re-check after seeding
|
|
692
|
-
existing = db.getRequirementById(id);
|
|
693
|
-
}
|
|
694
|
-
} catch {
|
|
695
|
-
// REQUIREMENTS.md missing or unparseable — fall through to skeleton
|
|
696
|
-
}
|
|
697
|
-
}
|
|
639
|
+
const existing = db.getRequirementById(id);
|
|
698
640
|
|
|
699
641
|
const base: Requirement = existing ?? {
|
|
700
642
|
id,
|
|
@@ -750,11 +692,7 @@ export async function updateRequirementInDb(
|
|
|
750
692
|
try {
|
|
751
693
|
await saveFile(filePath, md);
|
|
752
694
|
} catch (diskErr) {
|
|
753
|
-
|
|
754
|
-
if (existing) {
|
|
755
|
-
db.upsertRequirement(existing);
|
|
756
|
-
}
|
|
757
|
-
throw diskErr;
|
|
695
|
+
logWarning('projection', 'REQUIREMENTS.md projection write failed; DB requirement update remains committed', { fn: 'updateRequirementInDb', id, error: String((diskErr as Error).message) });
|
|
758
696
|
}
|
|
759
697
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
760
698
|
// Do NOT clear the artifacts table — we just wrote to it intentionally.
|
|
@@ -779,46 +717,118 @@ export interface SaveArtifactOpts {
|
|
|
779
717
|
}
|
|
780
718
|
|
|
781
719
|
/**
|
|
782
|
-
* Save
|
|
720
|
+
* Save a root-level artifact (no milestone) to DB and write to disk,
|
|
721
|
+
* routing path construction through workspace.contract.projectGsd directly.
|
|
722
|
+
* Use this instead of saveArtifactToDbByScope when milestone_id is absent.
|
|
723
|
+
*/
|
|
724
|
+
export async function saveArtifactToDbForWorkspace(
|
|
725
|
+
workspace: GsdWorkspace,
|
|
726
|
+
opts: SaveArtifactOpts,
|
|
727
|
+
): Promise<void> {
|
|
728
|
+
try {
|
|
729
|
+
const db = await import('./gsd-db.js');
|
|
730
|
+
|
|
731
|
+
const gsdDir = workspace.contract.projectGsd;
|
|
732
|
+
const fullPath = resolve(gsdDir, opts.path);
|
|
733
|
+
|
|
734
|
+
const rel0 = relative(gsdDir, fullPath);
|
|
735
|
+
if (rel0.startsWith('..') || isAbsolute(rel0)) {
|
|
736
|
+
throw new GSDError(GSD_IO_ERROR, `saveArtifactToDbForWorkspace: path escapes .gsd/ directory: ${opts.path}`);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
let contentToPersist = opts.content;
|
|
740
|
+
if (opts.artifact_type === 'REQUIREMENTS' && opts.path === 'REQUIREMENTS.md') {
|
|
741
|
+
const activeRequirements = db.getActiveRequirements();
|
|
742
|
+
if (activeRequirements.length === 0) {
|
|
743
|
+
throw new GSDError(GSD_STALE_STATE, 'saveArtifactToDbForWorkspace: REQUIREMENTS final save requires active DB-backed requirements');
|
|
744
|
+
}
|
|
745
|
+
contentToPersist = generateRequirementsMd(activeRequirements);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
let skipDiskWrite = false;
|
|
749
|
+
if (!isRootCanonicalArtifact(opts) && existsSync(fullPath)) {
|
|
750
|
+
const existingSize = statSync(fullPath).size;
|
|
751
|
+
const newSize = Buffer.byteLength(contentToPersist, 'utf-8');
|
|
752
|
+
if (existingSize > 0 && newSize < existingSize * 0.5) {
|
|
753
|
+
logWarning('projection', `new content (${newSize}B) is <50% of existing projection (${existingSize}B), preserving disk file while DB remains authoritative`, { fn: 'saveArtifactToDbForWorkspace', path: opts.path });
|
|
754
|
+
skipDiskWrite = true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
db.insertArtifact({
|
|
759
|
+
path: opts.path,
|
|
760
|
+
artifact_type: opts.artifact_type,
|
|
761
|
+
milestone_id: null,
|
|
762
|
+
slice_id: null,
|
|
763
|
+
task_id: null,
|
|
764
|
+
full_content: contentToPersist,
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
if (!skipDiskWrite) {
|
|
768
|
+
try {
|
|
769
|
+
await saveFile(fullPath, contentToPersist);
|
|
770
|
+
} catch (diskErr) {
|
|
771
|
+
logWarning('projection', 'artifact projection write failed; DB artifact remains committed', { fn: 'saveArtifactToDbForWorkspace', path: opts.path, error: String((diskErr as Error).message) });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
invalidateStateCache();
|
|
775
|
+
clearPathCache();
|
|
776
|
+
clearParseCache();
|
|
777
|
+
} catch (err) {
|
|
778
|
+
logError('manifest', 'saveArtifactToDbForWorkspace failed', { fn: 'saveArtifactToDbForWorkspace', error: String((err as Error).message) });
|
|
779
|
+
throw err;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Save an artifact to DB and write the corresponding markdown file to disk,
|
|
785
|
+
* routing all path construction through the workspace contract.
|
|
786
|
+
*
|
|
783
787
|
* The path is relative to .gsd/ (e.g. "milestones/M001/slices/S06/tasks/T01-SUMMARY.md").
|
|
784
|
-
* The full file path is computed as
|
|
788
|
+
* The full file path is computed as scope.workspace.contract.projectGsd + '/' + path.
|
|
785
789
|
*/
|
|
786
|
-
export async function
|
|
790
|
+
export async function saveArtifactToDbByScope(
|
|
791
|
+
scope: MilestoneScope,
|
|
787
792
|
opts: SaveArtifactOpts,
|
|
788
|
-
basePath: string,
|
|
789
793
|
): Promise<void> {
|
|
794
|
+
// Guard: an empty milestoneId produces malformed paths (milestoneDir = join(gsd, "milestones", "")).
|
|
795
|
+
// Callers that have no milestone should use saveArtifactToDbForWorkspace instead.
|
|
796
|
+
if (!scope.milestoneId) {
|
|
797
|
+
throw new GSDError(GSD_IO_ERROR, `saveArtifactToDbByScope: milestoneId is empty — use saveArtifactToDbForWorkspace for root artifacts`);
|
|
798
|
+
}
|
|
799
|
+
|
|
790
800
|
try {
|
|
791
801
|
const db = await import('./gsd-db.js');
|
|
792
802
|
|
|
803
|
+
// Use contract.projectGsd as the canonical .gsd directory — never a hand-rolled basePath join.
|
|
804
|
+
const gsdDir = scope.workspace.contract.projectGsd;
|
|
805
|
+
const fullPath = resolve(gsdDir, opts.path);
|
|
806
|
+
|
|
793
807
|
// Guard against path traversal before any reads/writes
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
throw new GSDError(GSD_IO_ERROR, `saveArtifactToDb: path escapes .gsd/ directory: ${opts.path}`);
|
|
808
|
+
const rel1 = relative(gsdDir, fullPath);
|
|
809
|
+
if (rel1.startsWith('..') || isAbsolute(rel1)) {
|
|
810
|
+
throw new GSDError(GSD_IO_ERROR, `saveArtifactToDbByScope: path escapes .gsd/ directory: ${opts.path}`);
|
|
798
811
|
}
|
|
812
|
+
|
|
799
813
|
let contentToPersist = opts.content;
|
|
800
814
|
if (opts.artifact_type === 'REQUIREMENTS' && opts.path === 'REQUIREMENTS.md') {
|
|
801
815
|
const activeRequirements = db.getActiveRequirements();
|
|
802
816
|
if (activeRequirements.length === 0) {
|
|
803
|
-
throw new GSDError(GSD_STALE_STATE, '
|
|
817
|
+
throw new GSDError(GSD_STALE_STATE, 'saveArtifactToDbByScope: REQUIREMENTS final save requires active DB-backed requirements');
|
|
804
818
|
}
|
|
805
819
|
contentToPersist = generateRequirementsMd(activeRequirements);
|
|
806
820
|
}
|
|
807
821
|
|
|
808
|
-
// Shrinkage guard: if the file already exists and the new
|
|
809
|
-
// significantly smaller (<50%), preserve the richer file on
|
|
810
|
-
//
|
|
811
|
-
// canonical artifacts are exempt
|
|
812
|
-
// canonical DB state, and cleanup/consolidation is often intentionally much
|
|
813
|
-
// smaller than a malformed accumulated file.
|
|
814
|
-
let dbContent = contentToPersist;
|
|
822
|
+
// Shrinkage guard: if the projection file already exists and the new
|
|
823
|
+
// content is significantly smaller (<50%), preserve the richer file on
|
|
824
|
+
// disk, but keep the DB row authoritative with the caller-provided content.
|
|
825
|
+
// Root canonical artifacts are exempt (rendered from canonical DB state).
|
|
815
826
|
let skipDiskWrite = false;
|
|
816
827
|
if (!isRootCanonicalArtifact(opts) && existsSync(fullPath)) {
|
|
817
828
|
const existingSize = statSync(fullPath).size;
|
|
818
829
|
const newSize = Buffer.byteLength(contentToPersist, 'utf-8');
|
|
819
830
|
if (existingSize > 0 && newSize < existingSize * 0.5) {
|
|
820
|
-
logWarning('
|
|
821
|
-
dbContent = readFileSync(fullPath, 'utf-8');
|
|
831
|
+
logWarning('projection', `new content (${newSize}B) is <50% of existing projection (${existingSize}B), preserving disk file while DB remains authoritative`, { fn: 'saveArtifactToDbByScope', path: opts.path });
|
|
822
832
|
skipDiskWrite = true;
|
|
823
833
|
}
|
|
824
834
|
}
|
|
@@ -829,7 +839,7 @@ export async function saveArtifactToDb(
|
|
|
829
839
|
milestone_id: opts.milestone_id ?? null,
|
|
830
840
|
slice_id: opts.slice_id ?? null,
|
|
831
841
|
task_id: opts.task_id ?? null,
|
|
832
|
-
full_content:
|
|
842
|
+
full_content: contentToPersist,
|
|
833
843
|
});
|
|
834
844
|
|
|
835
845
|
// Write the file to disk (only if we're not preserving a richer existing file)
|
|
@@ -837,9 +847,7 @@ export async function saveArtifactToDb(
|
|
|
837
847
|
try {
|
|
838
848
|
await saveFile(fullPath, contentToPersist);
|
|
839
849
|
} catch (diskErr) {
|
|
840
|
-
|
|
841
|
-
db.deleteArtifactByPath(opts.path);
|
|
842
|
-
throw diskErr;
|
|
850
|
+
logWarning('projection', 'artifact projection write failed; DB artifact remains committed', { fn: 'saveArtifactToDbByScope', path: opts.path, error: String((diskErr as Error).message) });
|
|
843
851
|
}
|
|
844
852
|
}
|
|
845
853
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
@@ -848,7 +856,28 @@ export async function saveArtifactToDb(
|
|
|
848
856
|
clearPathCache();
|
|
849
857
|
clearParseCache();
|
|
850
858
|
} catch (err) {
|
|
851
|
-
logError('manifest', '
|
|
859
|
+
logError('manifest', 'saveArtifactToDbByScope failed', { fn: 'saveArtifactToDbByScope', error: String((err as Error).message) });
|
|
852
860
|
throw err;
|
|
853
861
|
}
|
|
854
862
|
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Save an artifact to DB and write the corresponding markdown file to disk.
|
|
866
|
+
* The path is relative to .gsd/ (e.g. "milestones/M001/slices/S06/tasks/T01-SUMMARY.md").
|
|
867
|
+
* The full file path is computed as basePath + '.gsd/' + path.
|
|
868
|
+
*
|
|
869
|
+
* @deprecated Use saveArtifactToDbByScope instead, which routes through the
|
|
870
|
+
* workspace contract for canonical path resolution.
|
|
871
|
+
* TODO(C-future): remove this legacy wrapper once all callers are migrated.
|
|
872
|
+
*/
|
|
873
|
+
export async function saveArtifactToDb(
|
|
874
|
+
opts: SaveArtifactOpts,
|
|
875
|
+
basePath: string,
|
|
876
|
+
): Promise<void> {
|
|
877
|
+
const workspace = createWorkspace(basePath);
|
|
878
|
+
const milestoneId = opts.milestone_id;
|
|
879
|
+
if (milestoneId) {
|
|
880
|
+
return saveArtifactToDbByScope(scopeMilestone(workspace, milestoneId), opts);
|
|
881
|
+
}
|
|
882
|
+
return saveArtifactToDbForWorkspace(workspace, opts);
|
|
883
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Delegation policy — codifies which GSD MCP tools are safe to run as
|
|
2
|
+
// background sub-agents while the foreground /gsd flow continues. Verdicts
|
|
3
|
+
// are derived from the round-1 and round-2 evaluations recorded in this
|
|
4
|
+
// branch's PR description; the rationale field on each entry preserves
|
|
5
|
+
// the reason so future changes have to revisit the analysis explicitly.
|
|
6
|
+
//
|
|
7
|
+
// Default-deny: unknown tools are never backgroundable.
|
|
8
|
+
//
|
|
9
|
+
// ─── Tool-name vs unit-type namespaces ───────────────────────────────────
|
|
10
|
+
// Entries are keyed by canonical MCP tool name (`gsd_*`). The optional
|
|
11
|
+
// `unitType` field is a *secondary* index for the dispatcher's convenience
|
|
12
|
+
// — it bridges this policy to `auto-dispatch.ts`' `DispatchAction.unitType`
|
|
13
|
+
// values. The two namespaces are not 1:1:
|
|
14
|
+
//
|
|
15
|
+
// - Some tools have no corresponding unit type (e.g. `gsd_doctor`,
|
|
16
|
+
// `gsd_plan_task`) and intentionally omit `unitType`.
|
|
17
|
+
// - Some unit types share a tool — e.g. `execute-task`, `execute-task-simple`,
|
|
18
|
+
// and `reactive-execute` all invoke `gsd_execute`. The current shape
|
|
19
|
+
// allows only one `unitType` per entry, so those units fall through to
|
|
20
|
+
// `getVerdictByUnitType() === null` (→ `backgroundable: false`) even
|
|
21
|
+
// though `gsd_execute` itself is GOOD. This is the intended default-deny
|
|
22
|
+
// posture until a future PR wires actual background dispatch and
|
|
23
|
+
// decides whether each unit-level orchestration is safe — the unit
|
|
24
|
+
// wraps a prompt, harness setup, and post-processing on top of the
|
|
25
|
+
// tool, and the tool's safety doesn't transfer automatically.
|
|
26
|
+
//
|
|
27
|
+
// Auto-dispatch produces 20 distinct unit types; only 5 are explicitly
|
|
28
|
+
// classified here. The other 15 default-deny:
|
|
29
|
+
// complete-milestone, complete-slice, discuss-milestone, discuss-project,
|
|
30
|
+
// discuss-requirements, execute-task, execute-task-simple, gate-evaluate,
|
|
31
|
+
// reactive-execute, refine-slice, research-decision, research-milestone,
|
|
32
|
+
// research-project, research-slice, rewrite-docs, run-uat
|
|
33
|
+
//
|
|
34
|
+
// Adding a `unitType` mapping (or a future `unitTypes: string[]`) to an
|
|
35
|
+
// existing entry is the place to lift any of these out of default-deny
|
|
36
|
+
// when the analysis has been done.
|
|
37
|
+
|
|
38
|
+
export type BackgroundabilityVerdict = "good" | "risky" | "no";
|
|
39
|
+
|
|
40
|
+
export interface DelegationPolicyEntry {
|
|
41
|
+
/** Canonical MCP tool name (the verb_object form, e.g. `gsd_plan_slice`). */
|
|
42
|
+
toolName: string;
|
|
43
|
+
/** Workflow unit type from auto-dispatch.ts, when one exists. */
|
|
44
|
+
unitType?: string;
|
|
45
|
+
verdict: BackgroundabilityVerdict;
|
|
46
|
+
/** One-line justification grounded in the evaluation findings. */
|
|
47
|
+
rationale: string;
|
|
48
|
+
/**
|
|
49
|
+
* Constraints the caller MUST satisfy when dispatching this unit in the
|
|
50
|
+
* background. Only populated for `good` and conditional `risky` entries.
|
|
51
|
+
*/
|
|
52
|
+
constraints?: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const POLICY: Record<string, DelegationPolicyEntry> = {
|
|
56
|
+
gsd_plan_slice: {
|
|
57
|
+
toolName: "gsd_plan_slice",
|
|
58
|
+
unitType: "plan-slice",
|
|
59
|
+
verdict: "good",
|
|
60
|
+
rationale:
|
|
61
|
+
"Self-contained, no user prompts, atomic DB tx; existing slice-parallel-orchestrator pattern transfers cleanly.",
|
|
62
|
+
constraints: [
|
|
63
|
+
"Lock the slice from further user discussion once dispatched (context is frozen at dispatch time).",
|
|
64
|
+
"Foreground must not derive state for that slice while the transaction is in flight.",
|
|
65
|
+
"Foreground must await background completion before any tool reads the planned tasks/gates.",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
gsd_execute: {
|
|
69
|
+
toolName: "gsd_execute",
|
|
70
|
+
// No `unitType` set on purpose — the underlying tool is safe, but the
|
|
71
|
+
// unit-level orchestrations that invoke it (`execute-task`,
|
|
72
|
+
// `execute-task-simple`, `reactive-execute`) wrap additional prompt and
|
|
73
|
+
// harness work whose safety is a separate analysis. Default-deny those
|
|
74
|
+
// units until that analysis is recorded; adding `unitType` here would
|
|
75
|
+
// promote them silently.
|
|
76
|
+
verdict: "good",
|
|
77
|
+
rationale:
|
|
78
|
+
"No DB writes; UUID-isolated stdout/stderr/meta files; existing reactive-execute parallel-subagent precedent.",
|
|
79
|
+
},
|
|
80
|
+
gsd_validate_milestone: {
|
|
81
|
+
toolName: "gsd_validate_milestone",
|
|
82
|
+
unitType: "validate-milestone",
|
|
83
|
+
verdict: "good",
|
|
84
|
+
rationale:
|
|
85
|
+
"Verdict pre-computed by parallel reviewers; atomic DB tx plus isolated VALIDATION.md write; no user interaction.",
|
|
86
|
+
},
|
|
87
|
+
gsd_reassess_roadmap: {
|
|
88
|
+
toolName: "gsd_reassess_roadmap",
|
|
89
|
+
unitType: "reassess-roadmap",
|
|
90
|
+
verdict: "good",
|
|
91
|
+
rationale:
|
|
92
|
+
"Narrower mutation scope than plan_milestone; structural guards prevent modification of completed slices.",
|
|
93
|
+
},
|
|
94
|
+
gsd_doctor: {
|
|
95
|
+
toolName: "gsd_doctor",
|
|
96
|
+
verdict: "risky",
|
|
97
|
+
rationale:
|
|
98
|
+
"Diagnostic-only mode (fix=false) is safe to background; fix=true writes STATE.md/ROADMAP.md without session-lock coordination and can race the foreground flow.",
|
|
99
|
+
constraints: [
|
|
100
|
+
"Background only with fix=false (diagnostic-only).",
|
|
101
|
+
"Apply fixes synchronously, only when no foreground unit is dispatched.",
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
gsd_plan_milestone: {
|
|
105
|
+
toolName: "gsd_plan_milestone",
|
|
106
|
+
unitType: "plan-milestone",
|
|
107
|
+
verdict: "risky",
|
|
108
|
+
rationale:
|
|
109
|
+
"Inputs require CONTEXT.md from discuss-milestone, so initial questioning is already done by the time it can start; TOCTOU guards and projection coherence make concurrency unsafe.",
|
|
110
|
+
},
|
|
111
|
+
gsd_replan_slice: {
|
|
112
|
+
toolName: "gsd_replan_slice",
|
|
113
|
+
unitType: "replan-slice",
|
|
114
|
+
verdict: "risky",
|
|
115
|
+
rationale:
|
|
116
|
+
"Blocks the replanning→executing state transition on a gate that waits for S##-REPLAN.md; background failure leaves the flow stuck.",
|
|
117
|
+
},
|
|
118
|
+
gsd_plan_task: {
|
|
119
|
+
toolName: "gsd_plan_task",
|
|
120
|
+
verdict: "no",
|
|
121
|
+
rationale:
|
|
122
|
+
"plan-slice prompt explicitly forbids calling gsd_plan_task separately; per-task granularity multiplies manifest writes and projection re-renders with no payoff.",
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Alias map keyed on the secondary name; resolves to the canonical entry above.
|
|
127
|
+
// Sourced from packages/mcp-server/src/workflow-tools.ts alias registrations
|
|
128
|
+
// (gsd_milestone_validate, gsd_roadmap_reassess, gsd_slice_replan, gsd_task_plan).
|
|
129
|
+
const ALIASES: Record<string, string> = {
|
|
130
|
+
gsd_milestone_validate: "gsd_validate_milestone",
|
|
131
|
+
gsd_roadmap_reassess: "gsd_reassess_roadmap",
|
|
132
|
+
gsd_slice_replan: "gsd_replan_slice",
|
|
133
|
+
gsd_task_plan: "gsd_plan_task",
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
function resolveCanonical(name: string): string {
|
|
137
|
+
return ALIASES[name] ?? name;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function getDelegationVerdict(toolName: string): DelegationPolicyEntry | null {
|
|
141
|
+
return POLICY[resolveCanonical(toolName)] ?? null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function isBackgroundable(toolName: string): boolean {
|
|
145
|
+
const entry = getDelegationVerdict(toolName);
|
|
146
|
+
return entry?.verdict === "good";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function listBackgroundableTools(): string[] {
|
|
150
|
+
return Object.values(POLICY)
|
|
151
|
+
.filter((entry) => entry.verdict === "good")
|
|
152
|
+
.map((entry) => entry.toolName)
|
|
153
|
+
.sort();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getVerdictByUnitType(unitType: string): DelegationPolicyEntry | null {
|
|
157
|
+
for (const entry of Object.values(POLICY)) {
|
|
158
|
+
if (entry.unitType === unitType) return entry;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Minimal shape of a dispatch action that the annotator needs to operate on.
|
|
165
|
+
* Matches the `dispatch` and non-dispatch variants of auto-dispatch.ts'
|
|
166
|
+
* DispatchAction without depending on it (so this module stays free of
|
|
167
|
+
* workspace-package transitive imports).
|
|
168
|
+
*/
|
|
169
|
+
export type AnnotatableDispatchAction =
|
|
170
|
+
| { action: "dispatch"; unitType: string; backgroundable?: boolean; [k: string]: unknown }
|
|
171
|
+
| { action: "stop"; [k: string]: unknown }
|
|
172
|
+
| { action: "skip"; [k: string]: unknown };
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Annotates a dispatch action in place with `backgroundable: true` when its
|
|
176
|
+
* unitType has a `good` verdict in the policy. Stop/skip actions pass through
|
|
177
|
+
* unchanged. Default-deny: unknown unit types resolve to `false`.
|
|
178
|
+
*
|
|
179
|
+
* **Mutation contract.** The `backgroundable` field is written directly onto
|
|
180
|
+
* the passed action object. This is intentional — every dispatch path in
|
|
181
|
+
* `auto-dispatch.ts` constructs a fresh action object per `where(ctx)` /
|
|
182
|
+
* `evaluateDispatch(ctx)` invocation, so in-place mutation cannot leak across
|
|
183
|
+
* dispatch cycles. Future dispatch rules MUST follow that convention: never
|
|
184
|
+
* cache or share `DispatchAction` objects across calls. If you need to cache,
|
|
185
|
+
* either freeze the cached object (`Object.freeze`) and clone on read, or
|
|
186
|
+
* stop calling `annotateBackgroundable` on the shared instance. The annotator
|
|
187
|
+
* always recomputes from the policy on every call (no internal cache), so
|
|
188
|
+
* repeated invocations on the same object will overwrite stale values
|
|
189
|
+
* deterministically — see the `annotateBackgroundable recomputes on each call`
|
|
190
|
+
* test for the contract pin.
|
|
191
|
+
*/
|
|
192
|
+
export function annotateBackgroundable<T extends AnnotatableDispatchAction>(action: T): T {
|
|
193
|
+
if (action.action !== "dispatch") return action;
|
|
194
|
+
const verdict = getVerdictByUnitType(action.unitType);
|
|
195
|
+
action.backgroundable = verdict?.verdict === "good";
|
|
196
|
+
return action;
|
|
197
|
+
}
|
|
@@ -54,21 +54,16 @@ export function getPriorSliceCompletionBlocker(
|
|
|
54
54
|
// completion, which is wrong when the SUMMARY is a failure-path report
|
|
55
55
|
// (verification FAILED, blocker placeholder, etc.). Resolve as follows:
|
|
56
56
|
// 1. When DB is available and status is closed → skip (authoritative).
|
|
57
|
-
// 2. When
|
|
58
|
-
//
|
|
59
|
-
// the guard can still block dependents of an active milestone.
|
|
60
|
-
// 3. Otherwise (SUMMARY without failure markers) → skip. Preserves
|
|
61
|
-
// the #1716 contract where a completed milestone with unchecked
|
|
62
|
-
// remediation slices is still treated as done.
|
|
63
|
-
const summaryPath = resolveMilestoneFile(base, mid, "SUMMARY");
|
|
57
|
+
// 2. When DB is unavailable, legacy SUMMARY.md fallback may skip.
|
|
58
|
+
// DB-backed projects must not treat SUMMARY.md as authoritative.
|
|
64
59
|
if (isDbAvailable()) {
|
|
65
60
|
const milestoneRow = getMilestone(mid);
|
|
66
61
|
if (milestoneRow && isClosedStatus(milestoneRow.status)) continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
62
|
+
} else {
|
|
63
|
+
const summaryPath = resolveMilestoneFile(base, mid, "SUMMARY");
|
|
69
64
|
let summaryContent: string | null = null;
|
|
70
|
-
try { summaryContent = readFileSync(summaryPath, "utf-8"); } catch { /* ignore */ }
|
|
71
|
-
if (
|
|
65
|
+
try { summaryContent = summaryPath ? readFileSync(summaryPath, "utf-8") : null; } catch { /* ignore */ }
|
|
66
|
+
if (summaryContent && classifyMilestoneSummaryContent(summaryContent) !== "failure") {
|
|
72
67
|
continue;
|
|
73
68
|
}
|
|
74
69
|
}
|
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import type { DoctorIssue } from "./doctor-types.js";
|
|
5
5
|
import { isDbAvailable, _getAdapter } from "./gsd-db.js";
|
|
6
|
-
import { resolveMilestoneFile } from "./paths.js";
|
|
6
|
+
import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
|
|
7
7
|
import { deriveState } from "./state.js";
|
|
8
8
|
import { readEvents } from "./workflow-events.js";
|
|
9
9
|
import { renderAllProjections } from "./workflow-projections.js";
|
|
@@ -13,7 +13,7 @@ export async function checkEngineHealth(
|
|
|
13
13
|
issues: DoctorIssue[],
|
|
14
14
|
fixesApplied: string[],
|
|
15
15
|
): Promise<void> {
|
|
16
|
-
const dbPath =
|
|
16
|
+
const dbPath = resolveGsdPathContract(basePath).projectDb;
|
|
17
17
|
|
|
18
18
|
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
19
19
|
issues.push({
|