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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// GSD Extension — State Derivation
|
|
2
|
-
// DB-
|
|
2
|
+
// DB-authoritative runtime derivation with explicit legacy filesystem fallback.
|
|
3
3
|
// Pure TypeScript, zero Pi dependencies.
|
|
4
4
|
import { parseRoadmap, parsePlan, } from './parsers-legacy.js';
|
|
5
5
|
import { parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn, } from './files.js';
|
|
@@ -9,14 +9,13 @@ import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
|
|
|
9
9
|
import { isClosedStatus, isDeferredStatus } from './status-guards.js';
|
|
10
10
|
import { nativeBatchParseGsdFiles } from './native-parser-bridge.js';
|
|
11
11
|
import { join, resolve } from 'path';
|
|
12
|
-
import { existsSync, readdirSync
|
|
12
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
13
13
|
import { debugCount, debugTime } from './debug-logger.js';
|
|
14
|
-
import { logWarning
|
|
14
|
+
import { logWarning } from './workflow-logger.js';
|
|
15
15
|
import { extractVerdict } from './verdict-parser.js';
|
|
16
|
-
import { loadEffectiveGSDPreferences } from './preferences.js';
|
|
17
16
|
import { detectPendingEscalation } from './escalation.js';
|
|
18
17
|
import { isTerminalMilestoneSummaryContent } from './milestone-summary-classifier.js';
|
|
19
|
-
import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice,
|
|
18
|
+
import { isDbAvailable, wasDbOpenAttempted, getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getReplanHistory, getSlice, getRequirementCounts, getLatestAssessmentByScope, getPendingGateCountForTurn, } from './gsd-db.js';
|
|
20
19
|
/**
|
|
21
20
|
* A "ghost" milestone directory contains only META.json (and no substantive
|
|
22
21
|
* files like CONTEXT, CONTEXT-DRAFT, ROADMAP, or SUMMARY). These appear when
|
|
@@ -145,9 +144,16 @@ export function invalidateStateCache() {
|
|
|
145
144
|
* Returns the ID of the first incomplete milestone, or null if all are complete.
|
|
146
145
|
*/
|
|
147
146
|
export async function getActiveMilestoneId(basePath) {
|
|
148
|
-
// Parallel worker isolation
|
|
149
|
-
|
|
147
|
+
// Parallel worker isolation. Normal DB state derivation remains DB-only;
|
|
148
|
+
// lock env vars are execution routing for explicit worker processes.
|
|
149
|
+
const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
|
|
150
150
|
if (milestoneLock) {
|
|
151
|
+
if (isDbAvailable()) {
|
|
152
|
+
const locked = getAllMilestones().find(m => m.id === milestoneLock);
|
|
153
|
+
if (!locked || isClosedStatus(locked.status) || locked.status === "parked")
|
|
154
|
+
return null;
|
|
155
|
+
return locked.id;
|
|
156
|
+
}
|
|
151
157
|
const milestoneIds = findMilestoneIds(basePath);
|
|
152
158
|
if (!milestoneIds.includes(milestoneLock))
|
|
153
159
|
return null;
|
|
@@ -160,14 +166,7 @@ export async function getActiveMilestoneId(basePath) {
|
|
|
160
166
|
if (isDbAvailable()) {
|
|
161
167
|
const allMilestones = getAllMilestones();
|
|
162
168
|
if (allMilestones.length > 0) {
|
|
163
|
-
|
|
164
|
-
// Without this, the DB path uses lexicographic sort while the dispatch
|
|
165
|
-
// guard uses queue order — causing a deadlock.
|
|
166
|
-
const customOrder = loadQueueOrder(basePath);
|
|
167
|
-
const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
|
|
168
|
-
const byId = new Map(allMilestones.map(m => [m.id, m]));
|
|
169
|
-
for (const id of sortedIds) {
|
|
170
|
-
const m = byId.get(id);
|
|
169
|
+
for (const m of allMilestones) {
|
|
171
170
|
if (isClosedStatus(m.status) || m.status === "parked")
|
|
172
171
|
continue;
|
|
173
172
|
return m.id;
|
|
@@ -202,12 +201,12 @@ export async function getActiveMilestoneId(basePath) {
|
|
|
202
201
|
return null;
|
|
203
202
|
}
|
|
204
203
|
/**
|
|
205
|
-
* Reconstruct GSD state from
|
|
204
|
+
* Reconstruct GSD state from the authoritative DB.
|
|
206
205
|
* STATE.md is a rendered cache of this output.
|
|
207
206
|
*
|
|
208
207
|
* When DB is available, queries milestone/slice/task tables directly.
|
|
209
|
-
*
|
|
210
|
-
*
|
|
208
|
+
* Legacy filesystem parsing is available only through an explicit opt-in for
|
|
209
|
+
* tests/recovery flows; runtime must not silently infer state from markdown.
|
|
211
210
|
*/
|
|
212
211
|
export async function deriveState(basePath) {
|
|
213
212
|
// Return cached result if within the TTL window for the same basePath
|
|
@@ -218,47 +217,39 @@ export async function deriveState(basePath) {
|
|
|
218
217
|
}
|
|
219
218
|
const stopTimer = debugTime("derive-state-impl");
|
|
220
219
|
let result;
|
|
221
|
-
//
|
|
220
|
+
// DB-backed derivation is authoritative whenever the DB is open.
|
|
221
|
+
// Markdown fallback is explicit-only; runtime degrade must not infer state
|
|
222
|
+
// from ROADMAP.md, PLAN.md, SUMMARY.md, REQUIREMENTS.md, or flag files.
|
|
222
223
|
if (isDbAvailable()) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
// disk milestones exist but haven't been migrated yet.
|
|
228
|
-
if (dbMilestones.length === 0) {
|
|
229
|
-
const diskIds = findMilestoneIds(basePath);
|
|
230
|
-
let synced = false;
|
|
231
|
-
for (const diskId of diskIds) {
|
|
232
|
-
if (!isGhostMilestone(basePath, diskId)) {
|
|
233
|
-
insertMilestone(diskMilestoneInsert(basePath, diskId));
|
|
234
|
-
synced = true;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (synced)
|
|
238
|
-
dbMilestones = getAllMilestones();
|
|
239
|
-
}
|
|
240
|
-
if (dbMilestones.length > 0) {
|
|
241
|
-
const stopDbTimer = debugTime("derive-state-db");
|
|
242
|
-
result = await deriveStateFromDb(basePath);
|
|
243
|
-
stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
244
|
-
_telemetry.dbDeriveCount++;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
// DB open but no milestones on disk either — use filesystem path
|
|
248
|
-
result = await _deriveStateImpl(basePath);
|
|
249
|
-
_telemetry.markdownDeriveCount++;
|
|
250
|
-
}
|
|
224
|
+
const stopDbTimer = debugTime("derive-state-db");
|
|
225
|
+
result = await deriveStateFromDb(basePath);
|
|
226
|
+
stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
227
|
+
_telemetry.dbDeriveCount++;
|
|
251
228
|
}
|
|
252
|
-
else {
|
|
253
|
-
// Only warn when DB initialization was attempted and failed — not when
|
|
254
|
-
// the DB simply hasn't been opened yet (e.g. during before_agent_start
|
|
255
|
-
// context injection which runs before any tool invocation opens the DB).
|
|
229
|
+
else if (process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK === "1") {
|
|
256
230
|
if (wasDbOpenAttempted()) {
|
|
257
|
-
logWarning("state", "DB unavailable — using filesystem state derivation
|
|
231
|
+
logWarning("state", "DB unavailable — using explicit legacy filesystem state derivation");
|
|
258
232
|
}
|
|
259
233
|
result = await _deriveStateImpl(basePath);
|
|
260
234
|
_telemetry.markdownDeriveCount++;
|
|
261
235
|
}
|
|
236
|
+
else {
|
|
237
|
+
if (wasDbOpenAttempted()) {
|
|
238
|
+
logWarning("state", "DB unavailable — refusing implicit markdown state derivation");
|
|
239
|
+
}
|
|
240
|
+
result = {
|
|
241
|
+
activeMilestone: null,
|
|
242
|
+
activeSlice: null,
|
|
243
|
+
activeTask: null,
|
|
244
|
+
phase: "pre-planning",
|
|
245
|
+
recentDecisions: [],
|
|
246
|
+
blockers: ["DB unavailable — runtime markdown state derivation is disabled"],
|
|
247
|
+
nextAction: "Open or create the canonical GSD database before deriving workflow state.",
|
|
248
|
+
registry: [],
|
|
249
|
+
requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
|
|
250
|
+
progress: { milestones: { done: 0, total: 0 } },
|
|
251
|
+
};
|
|
252
|
+
}
|
|
262
253
|
stopTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
263
254
|
debugCount("deriveStateCalls");
|
|
264
255
|
_stateCache = { basePath, result, timestamp: Date.now() };
|
|
@@ -288,111 +279,11 @@ function extractContextTitle(content, fallback) {
|
|
|
288
279
|
// isStatusDone replaced by isClosedStatus from status-guards.ts (single source of truth).
|
|
289
280
|
// Alias kept for backward compatibility within this file.
|
|
290
281
|
const isStatusDone = isClosedStatus;
|
|
291
|
-
function loadSync(path) {
|
|
292
|
-
if (!path)
|
|
293
|
-
return null;
|
|
294
|
-
try {
|
|
295
|
-
return readFileSync(path, "utf-8");
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
function diskMilestoneInsert(basePath, mid) {
|
|
302
|
-
const contextContent = loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT"));
|
|
303
|
-
const draftContent = !contextContent ? loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT")) : null;
|
|
304
|
-
const roadmapContent = loadSync(resolveMilestoneFile(basePath, mid, "ROADMAP"));
|
|
305
|
-
const summaryContent = loadSync(resolveMilestoneFile(basePath, mid, "SUMMARY"));
|
|
306
|
-
const roadmap = roadmapContent ? parseRoadmap(roadmapContent) : null;
|
|
307
|
-
const summary = summaryContent ? parseSummary(summaryContent) : null;
|
|
308
|
-
const summaryTerminal = summaryContent != null && isTerminalMilestoneSummaryContent(summaryContent);
|
|
309
|
-
const parked = resolveMilestoneFile(basePath, mid, "PARKED") !== null;
|
|
310
|
-
return {
|
|
311
|
-
id: mid,
|
|
312
|
-
title: roadmap
|
|
313
|
-
? stripMilestonePrefix(roadmap.title)
|
|
314
|
-
: (contextContent || draftContent)
|
|
315
|
-
? extractContextTitle(contextContent || draftContent, mid)
|
|
316
|
-
: (summary?.title || mid),
|
|
317
|
-
status: parked ? "parked" : summaryTerminal ? "complete" : "active",
|
|
318
|
-
depends_on: parseContextDependsOn(contextContent ?? draftContent),
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
282
|
/**
|
|
322
283
|
* Derive GSD state from the milestones/slices/tasks DB tables.
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
* Requirements also stay file-based via parseRequirementCounts().
|
|
326
|
-
*
|
|
327
|
-
* Must produce field-identical GSDState to _deriveStateImpl() for the same project.
|
|
284
|
+
* Markdown files are projections only in this path; they are never imported,
|
|
285
|
+
* reconciled, or used as completion signals.
|
|
328
286
|
*/
|
|
329
|
-
function reconcileDiskToDb(basePath) {
|
|
330
|
-
let allMilestones = getAllMilestones();
|
|
331
|
-
const dbIdSet = new Set(allMilestones.map(m => m.id));
|
|
332
|
-
const diskIds = findMilestoneIds(basePath);
|
|
333
|
-
let synced = false;
|
|
334
|
-
for (const diskId of diskIds) {
|
|
335
|
-
if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
|
|
336
|
-
insertMilestone(diskMilestoneInsert(basePath, diskId));
|
|
337
|
-
synced = true;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (synced)
|
|
341
|
-
allMilestones = getAllMilestones();
|
|
342
|
-
for (const mid of diskIds) {
|
|
343
|
-
if (isGhostMilestone(basePath, mid))
|
|
344
|
-
continue;
|
|
345
|
-
const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
346
|
-
if (!roadmapPath)
|
|
347
|
-
continue;
|
|
348
|
-
const dbSlices = getMilestoneSlices(mid);
|
|
349
|
-
const dbSliceIds = new Set(dbSlices.map(s => s.id));
|
|
350
|
-
let roadmapContent;
|
|
351
|
-
try {
|
|
352
|
-
roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
353
|
-
}
|
|
354
|
-
catch (err) {
|
|
355
|
-
logWarning("state", "reconcileDiskToDb: roadmap read failed, skipping milestone", {
|
|
356
|
-
mid,
|
|
357
|
-
error: err.message,
|
|
358
|
-
});
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
const parsed = parseRoadmap(roadmapContent);
|
|
362
|
-
for (const s of parsed.slices) {
|
|
363
|
-
if (dbSliceIds.has(s.id))
|
|
364
|
-
continue;
|
|
365
|
-
const summaryPath = resolveSliceFile(basePath, mid, s.id, "SUMMARY");
|
|
366
|
-
const sliceStatus = (s.done || summaryPath) ? "complete" : "pending";
|
|
367
|
-
insertSlice({
|
|
368
|
-
id: s.id, milestoneId: mid, title: s.title,
|
|
369
|
-
status: sliceStatus, risk: s.risk,
|
|
370
|
-
depends: s.depends, demo: s.demo,
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
// Reconcile stale *existing* slice rows (#3599): a slice row may exist in
|
|
374
|
-
// the DB with status "pending" even though disk artifacts (SUMMARY) prove
|
|
375
|
-
// completion — the same class of desync that task-level reconciliation
|
|
376
|
-
// (further below) already handles. Without this, the dependency resolver
|
|
377
|
-
// builds doneSliceIds from stale DB rows and downstream slices stay blocked
|
|
378
|
-
// forever with "No slice eligible".
|
|
379
|
-
for (const dbSlice of dbSlices) {
|
|
380
|
-
if (isStatusDone(dbSlice.status))
|
|
381
|
-
continue;
|
|
382
|
-
const summaryPath = resolveSliceFile(basePath, mid, dbSlice.id, "SUMMARY");
|
|
383
|
-
if (summaryPath) {
|
|
384
|
-
try {
|
|
385
|
-
updateSliceStatus(mid, dbSlice.id, "complete");
|
|
386
|
-
logWarning("reconcile", `slice ${mid}/${dbSlice.id} status reconciled from "${dbSlice.status}" to "complete" (#3599)`, { mid, sid: dbSlice.id });
|
|
387
|
-
}
|
|
388
|
-
catch (e) {
|
|
389
|
-
logError("reconcile", `failed to update slice ${dbSlice.id}`, { sid: dbSlice.id, error: e.message });
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return allMilestones;
|
|
395
|
-
}
|
|
396
287
|
function buildCompletenessSet(basePath, milestones) {
|
|
397
288
|
const completeMilestoneIds = new Set();
|
|
398
289
|
const parkedMilestoneIds = new Set();
|
|
@@ -401,8 +292,7 @@ function buildCompletenessSet(basePath, milestones) {
|
|
|
401
292
|
// (crashed complete-milestone turn, partial merge, manual edit) must not
|
|
402
293
|
// flip derived state to complete and cascade into a false auto-merge (#4179).
|
|
403
294
|
for (const m of milestones) {
|
|
404
|
-
|
|
405
|
-
if (parkedFile || m.status === 'parked') {
|
|
295
|
+
if (m.status === 'parked') {
|
|
406
296
|
parkedMilestoneIds.add(m.id);
|
|
407
297
|
continue;
|
|
408
298
|
}
|
|
@@ -426,37 +316,16 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
|
|
|
426
316
|
continue;
|
|
427
317
|
}
|
|
428
318
|
const slices = getMilestoneSlices(m.id);
|
|
429
|
-
if (slices.length === 0 && !isStatusDone(m.status) && m.status !== 'queued') {
|
|
430
|
-
if (isGhostMilestone(basePath, m.id))
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
319
|
// DB-authoritative completeness (#4179): only trust completeMilestoneIds,
|
|
434
320
|
// which is itself derived from DB status. SUMMARY-file presence alone must
|
|
435
|
-
// not imply completion.
|
|
436
|
-
// title source for legitimately-complete milestones whose DB row has no title.
|
|
321
|
+
// not imply completion.
|
|
437
322
|
if (completeMilestoneIds.has(m.id)) {
|
|
438
|
-
|
|
439
|
-
if (!m.title) {
|
|
440
|
-
const summaryFile = resolveMilestoneFile(basePath, m.id, "SUMMARY");
|
|
441
|
-
if (summaryFile) {
|
|
442
|
-
const summaryContent = await loadFile(summaryFile);
|
|
443
|
-
if (summaryContent) {
|
|
444
|
-
title = parseSummary(summaryContent).title || m.id;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
323
|
+
const title = stripMilestonePrefix(m.title) || m.id;
|
|
448
324
|
registry.push({ id: m.id, title, status: 'complete' });
|
|
449
325
|
continue;
|
|
450
326
|
}
|
|
451
327
|
const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
|
|
452
|
-
|
|
453
|
-
if (title === m.id) {
|
|
454
|
-
const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
|
|
455
|
-
const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
456
|
-
const contextContent = contextFile ? await loadFile(contextFile) : null;
|
|
457
|
-
const draftContent = draftFile && !contextContent ? await loadFile(draftFile) : null;
|
|
458
|
-
title = extractContextTitle(contextContent || draftContent, m.id);
|
|
459
|
-
}
|
|
328
|
+
const title = stripMilestonePrefix(m.title) || m.id;
|
|
460
329
|
if (!activeMilestoneFound) {
|
|
461
330
|
const deps = m.depends_on;
|
|
462
331
|
const depsUnmet = deps.some(dep => !completeMilestoneIds.has(dep));
|
|
@@ -465,38 +334,20 @@ async function buildRegistryAndFindActive(basePath, milestones, completeMileston
|
|
|
465
334
|
continue;
|
|
466
335
|
}
|
|
467
336
|
if (m.status === 'queued' && slices.length === 0) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (!contextFile && !draftFile) {
|
|
471
|
-
if (!firstDeferredQueuedShell) {
|
|
472
|
-
firstDeferredQueuedShell = { id: m.id, title, deps };
|
|
473
|
-
}
|
|
474
|
-
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
475
|
-
continue;
|
|
337
|
+
if (!firstDeferredQueuedShell) {
|
|
338
|
+
firstDeferredQueuedShell = { id: m.id, title, deps };
|
|
476
339
|
}
|
|
340
|
+
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
341
|
+
continue;
|
|
477
342
|
}
|
|
478
343
|
if (allSlicesDone) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// the milestone is NOT complete — so any SUMMARY file on disk is an
|
|
485
|
-
// orphan (crashed complete-milestone, partial merge, manual edit) and
|
|
486
|
-
// must not short-circuit this path. When validation is terminal, fall
|
|
487
|
-
// through to the default active-push below so `complete-milestone` can
|
|
488
|
-
// re-run idempotently.
|
|
489
|
-
if (!validationTerminal) {
|
|
490
|
-
activeMilestone = { id: m.id, title };
|
|
491
|
-
activeMilestoneSlices = slices;
|
|
492
|
-
activeMilestoneFound = true;
|
|
493
|
-
registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
344
|
+
activeMilestone = { id: m.id, title };
|
|
345
|
+
activeMilestoneSlices = slices;
|
|
346
|
+
activeMilestoneFound = true;
|
|
347
|
+
registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
348
|
+
continue;
|
|
496
349
|
}
|
|
497
|
-
|
|
498
|
-
const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
499
|
-
if (!contextFile && draftFile)
|
|
350
|
+
if (m.status === 'needs-discussion')
|
|
500
351
|
activeMilestoneHasDraft = true;
|
|
501
352
|
activeMilestone = { id: m.id, title };
|
|
502
353
|
activeMilestoneSlices = slices;
|
|
@@ -575,10 +426,9 @@ function handleNoActiveMilestone(registry, requirements, milestoneProgress) {
|
|
|
575
426
|
};
|
|
576
427
|
}
|
|
577
428
|
async function handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress) {
|
|
578
|
-
const
|
|
579
|
-
const
|
|
580
|
-
const validationTerminal =
|
|
581
|
-
const verdict = validationContent ? extractVerdict(validationContent) : undefined;
|
|
429
|
+
const validation = getLatestAssessmentByScope(activeMilestone.id, "milestone-validation");
|
|
430
|
+
const verdict = typeof validation?.status === "string" ? validation.status : undefined;
|
|
431
|
+
const validationTerminal = verdict != null && verdict !== "";
|
|
582
432
|
if (!validationTerminal) {
|
|
583
433
|
return {
|
|
584
434
|
activeMilestone, activeSlice: null, activeTask: null,
|
|
@@ -617,7 +467,7 @@ async function handleAllSlicesDone(basePath, activeMilestone, registry, requirem
|
|
|
617
467
|
}
|
|
618
468
|
function resolveSliceDependencies(activeMilestoneSlices) {
|
|
619
469
|
const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id));
|
|
620
|
-
const sliceLock = process.env.GSD_SLICE_LOCK;
|
|
470
|
+
const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
621
471
|
if (sliceLock) {
|
|
622
472
|
const lockedSlice = activeMilestoneSlices.find(s => s.id === sliceLock);
|
|
623
473
|
if (lockedSlice) {
|
|
@@ -639,111 +489,23 @@ function resolveSliceDependencies(activeMilestoneSlices) {
|
|
|
639
489
|
}
|
|
640
490
|
return { activeSlice: null, activeSliceRow: null };
|
|
641
491
|
}
|
|
642
|
-
async function reconcileSliceTasks(basePath, milestoneId, sliceId, planFile) {
|
|
643
|
-
let tasks = getSliceTasks(milestoneId, sliceId);
|
|
644
|
-
// #3600/#4974: import missing plan-file tasks even when the DB already has
|
|
645
|
-
// a partial task set. Existing DB task statuses stay authoritative.
|
|
646
|
-
if (planFile) {
|
|
647
|
-
try {
|
|
648
|
-
const planContent = await loadFile(planFile);
|
|
649
|
-
if (planContent) {
|
|
650
|
-
const diskPlan = parsePlan(planContent);
|
|
651
|
-
if (diskPlan.tasks.length > 0) {
|
|
652
|
-
const dbTaskIds = new Set(tasks.map(t => t.id));
|
|
653
|
-
let inserted = 0;
|
|
654
|
-
for (let i = 0; i < diskPlan.tasks.length; i++) {
|
|
655
|
-
const t = diskPlan.tasks[i];
|
|
656
|
-
if (dbTaskIds.has(t.id))
|
|
657
|
-
continue;
|
|
658
|
-
try {
|
|
659
|
-
insertTask({
|
|
660
|
-
id: t.id,
|
|
661
|
-
sliceId,
|
|
662
|
-
milestoneId,
|
|
663
|
-
title: t.title,
|
|
664
|
-
status: t.done ? 'complete' : 'pending',
|
|
665
|
-
sequence: i + 1,
|
|
666
|
-
});
|
|
667
|
-
inserted++;
|
|
668
|
-
}
|
|
669
|
-
catch (insertErr) {
|
|
670
|
-
logWarning("reconcile", `failed to insert task ${t.id} from plan file: ${insertErr instanceof Error ? insertErr.message : String(insertErr)}`);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
if (inserted > 0) {
|
|
674
|
-
tasks = getSliceTasks(milestoneId, sliceId);
|
|
675
|
-
logWarning("reconcile", `imported ${inserted} missing task(s) from plan file for ${milestoneId}/${sliceId}`, { mid: milestoneId, sid: sliceId });
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
catch (err) {
|
|
681
|
-
logError("reconcile", `plan-file task import failed for ${milestoneId}/${sliceId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
let reconciled = false;
|
|
685
|
-
for (const t of tasks) {
|
|
686
|
-
if (isStatusDone(t.status))
|
|
687
|
-
continue;
|
|
688
|
-
const summaryPath = resolveTaskFile(basePath, milestoneId, sliceId, t.id, "SUMMARY");
|
|
689
|
-
if (summaryPath && existsSync(summaryPath)) {
|
|
690
|
-
try {
|
|
691
|
-
updateTaskStatus(milestoneId, sliceId, t.id, "complete", new Date().toISOString());
|
|
692
|
-
logWarning("reconcile", `task ${milestoneId}/${sliceId}/${t.id} status reconciled from "${t.status}" to "complete" (#2514)`, { mid: milestoneId, sid: sliceId, tid: t.id });
|
|
693
|
-
reconciled = true;
|
|
694
|
-
}
|
|
695
|
-
catch (e) {
|
|
696
|
-
logError("reconcile", `failed to update task ${t.id}`, { tid: t.id, error: e.message });
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
if (reconciled) {
|
|
701
|
-
tasks = getSliceTasks(milestoneId, sliceId);
|
|
702
|
-
}
|
|
703
|
-
return tasks;
|
|
704
|
-
}
|
|
705
492
|
async function detectBlockers(basePath, milestoneId, sliceId, tasks) {
|
|
706
493
|
const completedTasks = tasks.filter(t => isStatusDone(t.status));
|
|
707
494
|
for (const ct of completedTasks) {
|
|
708
495
|
if (ct.blocker_discovered) {
|
|
709
496
|
return ct.id;
|
|
710
497
|
}
|
|
711
|
-
const summaryFile = resolveTaskFile(basePath, milestoneId, sliceId, ct.id, "SUMMARY");
|
|
712
|
-
if (!summaryFile)
|
|
713
|
-
continue;
|
|
714
|
-
const summaryContent = await loadFile(summaryFile);
|
|
715
|
-
if (!summaryContent)
|
|
716
|
-
continue;
|
|
717
|
-
const summary = parseSummary(summaryContent);
|
|
718
|
-
if (summary.frontmatter.blocker_discovered) {
|
|
719
|
-
return ct.id;
|
|
720
|
-
}
|
|
721
498
|
}
|
|
722
499
|
return null;
|
|
723
500
|
}
|
|
724
501
|
function checkReplanTrigger(basePath, milestoneId, sliceId) {
|
|
725
502
|
const sliceRow = getSlice(milestoneId, sliceId);
|
|
726
|
-
|
|
727
|
-
const diskTriggered = !dbTriggered &&
|
|
728
|
-
!!resolveSliceFile(basePath, milestoneId, sliceId, "REPLAN-TRIGGER");
|
|
729
|
-
return dbTriggered || diskTriggered;
|
|
730
|
-
}
|
|
731
|
-
async function checkInterruptedWork(basePath, milestoneId, sliceId) {
|
|
732
|
-
const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
733
|
-
const continueFile = sDir ? resolveSliceFile(basePath, milestoneId, sliceId, "CONTINUE") : null;
|
|
734
|
-
return !!(continueFile && await loadFile(continueFile)) ||
|
|
735
|
-
!!(sDir && await loadFile(join(sDir, "continue.md")));
|
|
503
|
+
return !!sliceRow?.replan_triggered_at;
|
|
736
504
|
}
|
|
737
505
|
export async function deriveStateFromDb(basePath) {
|
|
738
|
-
const requirements =
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
|
|
742
|
-
const byId = new Map(allMilestones.map(m => [m.id, m]));
|
|
743
|
-
allMilestones.length = 0;
|
|
744
|
-
for (const id of sortedIds)
|
|
745
|
-
allMilestones.push(byId.get(id));
|
|
746
|
-
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
506
|
+
const requirements = getRequirementCounts();
|
|
507
|
+
const allMilestones = getAllMilestones();
|
|
508
|
+
const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
|
|
747
509
|
const milestones = milestoneLock
|
|
748
510
|
? allMilestones.filter(m => m.id === milestoneLock)
|
|
749
511
|
: allMilestones;
|
|
@@ -766,26 +528,16 @@ export async function deriveStateFromDb(basePath) {
|
|
|
766
528
|
if (!activeMilestone) {
|
|
767
529
|
return handleNoActiveMilestone(registry, requirements, milestoneProgress);
|
|
768
530
|
}
|
|
769
|
-
const hasRoadmap = resolveMilestoneFile(basePath, activeMilestone.id, "ROADMAP") !== null;
|
|
770
531
|
if (activeMilestoneSlices.length === 0) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
: `Plan milestone ${activeMilestone.id}.`;
|
|
776
|
-
return {
|
|
777
|
-
activeMilestone, activeSlice: null, activeTask: null,
|
|
778
|
-
phase, recentDecisions: [], blockers: [],
|
|
779
|
-
nextAction, registry, requirements,
|
|
780
|
-
progress: { milestones: milestoneProgress },
|
|
781
|
-
};
|
|
782
|
-
}
|
|
532
|
+
const phase = activeMilestoneHasDraft ? 'needs-discussion' : 'pre-planning';
|
|
533
|
+
const nextAction = activeMilestoneHasDraft
|
|
534
|
+
? `Discuss draft context for milestone ${activeMilestone.id}.`
|
|
535
|
+
: `Plan milestone ${activeMilestone.id}.`;
|
|
783
536
|
return {
|
|
784
537
|
activeMilestone, activeSlice: null, activeTask: null,
|
|
785
|
-
phase
|
|
786
|
-
nextAction
|
|
787
|
-
|
|
788
|
-
progress: { milestones: milestoneProgress, slices: { done: 0, total: 0 } },
|
|
538
|
+
phase, recentDecisions: [], blockers: [],
|
|
539
|
+
nextAction, registry, requirements,
|
|
540
|
+
progress: { milestones: milestoneProgress },
|
|
789
541
|
};
|
|
790
542
|
}
|
|
791
543
|
const allSlicesDone = activeMilestoneSlices.every(s => isStatusDone(s.status));
|
|
@@ -796,19 +548,14 @@ export async function deriveStateFromDb(basePath) {
|
|
|
796
548
|
if (allSlicesDone) {
|
|
797
549
|
return handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress);
|
|
798
550
|
}
|
|
799
|
-
|
|
800
|
-
// (e.g. a crash between plan-slice write and the sketch flip), reconcile before
|
|
801
|
-
// running phase derivation so the flag doesn't misroute state.
|
|
802
|
-
autoHealSketchFlags(activeMilestone.id, (sid) => !!resolveSliceFile(basePath, activeMilestone.id, sid, "PLAN"));
|
|
803
|
-
// Re-read slices after auto-heal so downstream reads see fresh is_sketch values.
|
|
804
|
-
const healedSlices = getMilestoneSlices(activeMilestone.id);
|
|
805
|
-
const activeSliceContext = resolveSliceDependencies(healedSlices);
|
|
551
|
+
const activeSliceContext = resolveSliceDependencies(activeMilestoneSlices);
|
|
806
552
|
if (!activeSliceContext.activeSlice) {
|
|
807
553
|
// If locked slice wasn't found, it returns null but logs warning, we need to return 'blocked'
|
|
808
|
-
|
|
554
|
+
const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
555
|
+
if (sliceLock) {
|
|
809
556
|
return {
|
|
810
557
|
activeMilestone, activeSlice: null, activeTask: null,
|
|
811
|
-
phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${
|
|
558
|
+
phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${sliceLock} not found in active milestone slices`],
|
|
812
559
|
nextAction: 'Slice lock references a non-existent slice — check orchestrator dispatch.',
|
|
813
560
|
registry, requirements,
|
|
814
561
|
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
@@ -823,31 +570,19 @@ export async function deriveStateFromDb(basePath) {
|
|
|
823
570
|
};
|
|
824
571
|
}
|
|
825
572
|
const { activeSlice, activeSliceRow } = activeSliceContext;
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
// before execution. When the flag is off, sketches are indistinguishable
|
|
831
|
-
// from a missing plan and fall through to the normal `planning` phase.
|
|
832
|
-
const progressive = loadEffectiveGSDPreferences()?.preferences?.phases?.progressive_planning === true;
|
|
833
|
-
if (progressive && activeSliceRow?.is_sketch === 1) {
|
|
834
|
-
return {
|
|
835
|
-
activeMilestone, activeSlice, activeTask: null,
|
|
836
|
-
phase: 'refining', recentDecisions: [], blockers: [],
|
|
837
|
-
nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
|
|
838
|
-
registry, requirements,
|
|
839
|
-
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
840
|
-
};
|
|
841
|
-
}
|
|
573
|
+
// ADR-011: DB slice metadata is authoritative for sketch refinement.
|
|
574
|
+
// PLAN.md and preference flags are projections/configuration and are
|
|
575
|
+
// deliberately not used to infer whether the slice itself is a sketch.
|
|
576
|
+
if (activeSliceRow?.is_sketch === 1) {
|
|
842
577
|
return {
|
|
843
578
|
activeMilestone, activeSlice, activeTask: null,
|
|
844
|
-
phase: '
|
|
845
|
-
nextAction: `
|
|
579
|
+
phase: 'refining', recentDecisions: [], blockers: [],
|
|
580
|
+
nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
|
|
846
581
|
registry, requirements,
|
|
847
582
|
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
848
583
|
};
|
|
849
584
|
}
|
|
850
|
-
const tasks =
|
|
585
|
+
const tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
|
|
851
586
|
const taskProgress = {
|
|
852
587
|
done: tasks.filter(t => isStatusDone(t.status)).length,
|
|
853
588
|
total: tasks.length,
|
|
@@ -866,25 +601,12 @@ export async function deriveStateFromDb(basePath) {
|
|
|
866
601
|
return {
|
|
867
602
|
activeMilestone, activeSlice, activeTask: null,
|
|
868
603
|
phase: 'planning', recentDecisions: [], blockers: [],
|
|
869
|
-
nextAction: `Slice ${activeSlice.id} has
|
|
604
|
+
nextAction: `Slice ${activeSlice.id} has no DB tasks. Plan slice tasks before execution.`,
|
|
870
605
|
registry, requirements,
|
|
871
606
|
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
872
607
|
};
|
|
873
608
|
}
|
|
874
609
|
const activeTask = { id: activeTaskRow.id, title: activeTaskRow.title };
|
|
875
|
-
const tasksDir = resolveTasksDir(basePath, activeMilestone.id, activeSlice.id);
|
|
876
|
-
if (tasksDir && existsSync(tasksDir) && tasks.length > 0) {
|
|
877
|
-
const allFiles = readdirSync(tasksDir).filter(f => f.endsWith(".md"));
|
|
878
|
-
if (allFiles.length === 0) {
|
|
879
|
-
return {
|
|
880
|
-
activeMilestone, activeSlice, activeTask: null,
|
|
881
|
-
phase: 'planning', recentDecisions: [], blockers: [],
|
|
882
|
-
nextAction: `Task plan files missing for ${activeSlice.id}. Run plan-slice to generate task plans.`,
|
|
883
|
-
registry, requirements,
|
|
884
|
-
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
610
|
// ── Quality gate evaluation check ──────────────────────────────────
|
|
889
611
|
// Pause before execution only when gates owned by the `gate-evaluate`
|
|
890
612
|
// turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
|
|
@@ -956,13 +678,10 @@ export async function deriveStateFromDb(basePath) {
|
|
|
956
678
|
}
|
|
957
679
|
}
|
|
958
680
|
}
|
|
959
|
-
const hasInterrupted = await checkInterruptedWork(basePath, activeMilestone.id, activeSlice.id);
|
|
960
681
|
return {
|
|
961
682
|
activeMilestone, activeSlice, activeTask,
|
|
962
683
|
phase: 'executing', recentDecisions: [], blockers: [],
|
|
963
|
-
nextAction:
|
|
964
|
-
? `Resume interrupted work on ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}. Read continue.md first.`
|
|
965
|
-
: `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
|
|
684
|
+
nextAction: `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
|
|
966
685
|
registry, requirements,
|
|
967
686
|
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
968
687
|
};
|
|
@@ -975,12 +694,12 @@ export async function _deriveStateImpl(basePath) {
|
|
|
975
694
|
const customOrder = loadQueueOrder(basePath);
|
|
976
695
|
const milestoneIds = sortByQueueOrder(diskIds, customOrder);
|
|
977
696
|
// ── Parallel worker isolation ──────────────────────────────────────────
|
|
978
|
-
// When GSD_MILESTONE_LOCK
|
|
697
|
+
// When GSD_PARALLEL_WORKER and GSD_MILESTONE_LOCK are set, this process is a parallel worker
|
|
979
698
|
// scoped to a single milestone. Filter the milestone list so this worker
|
|
980
699
|
// only sees its assigned milestone (all others are treated as if they
|
|
981
700
|
// don't exist). This gives each worker complete isolation without
|
|
982
701
|
// modifying any other state derivation logic.
|
|
983
|
-
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
702
|
+
const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
|
|
984
703
|
if (milestoneLock && milestoneIds.includes(milestoneLock)) {
|
|
985
704
|
milestoneIds.length = 0;
|
|
986
705
|
milestoneIds.push(milestoneLock);
|
|
@@ -1429,8 +1148,8 @@ export async function _deriveStateImpl(basePath) {
|
|
|
1429
1148
|
const doneSliceIds = new Set(activeRoadmap.slices.filter(s => s.done).map(s => s.id));
|
|
1430
1149
|
let activeSlice = null;
|
|
1431
1150
|
// ── Slice-level parallel worker isolation ─────────────────────────────
|
|
1432
|
-
// When GSD_SLICE_LOCK
|
|
1433
|
-
const sliceLockLegacy = process.env.GSD_SLICE_LOCK;
|
|
1151
|
+
// When GSD_PARALLEL_WORKER and GSD_SLICE_LOCK are set, override activeSlice to only the locked slice.
|
|
1152
|
+
const sliceLockLegacy = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
1434
1153
|
if (sliceLockLegacy) {
|
|
1435
1154
|
const lockedSlice = activeRoadmap.slices.find(s => s.id === sliceLockLegacy);
|
|
1436
1155
|
if (lockedSlice) {
|