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
|
|
|
5
5
|
import type {
|
|
@@ -40,11 +40,10 @@ import { isClosedStatus, isDeferredStatus } from './status-guards.js';
|
|
|
40
40
|
import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
|
|
41
41
|
|
|
42
42
|
import { join, resolve } from 'path';
|
|
43
|
-
import { existsSync, readdirSync
|
|
43
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
44
44
|
import { debugCount, debugTime } from './debug-logger.js';
|
|
45
|
-
import { logWarning
|
|
45
|
+
import { logWarning } from './workflow-logger.js';
|
|
46
46
|
import { extractVerdict } from './verdict-parser.js';
|
|
47
|
-
import { loadEffectiveGSDPreferences } from './preferences.js';
|
|
48
47
|
import { detectPendingEscalation } from './escalation.js';
|
|
49
48
|
import { isTerminalMilestoneSummaryContent } from './milestone-summary-classifier.js';
|
|
50
49
|
|
|
@@ -57,13 +56,9 @@ import {
|
|
|
57
56
|
getSliceTasks,
|
|
58
57
|
getReplanHistory,
|
|
59
58
|
getSlice,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
insertTask,
|
|
63
|
-
updateSliceStatus,
|
|
64
|
-
updateTaskStatus,
|
|
59
|
+
getRequirementCounts,
|
|
60
|
+
getLatestAssessmentByScope,
|
|
65
61
|
getPendingGateCountForTurn,
|
|
66
|
-
autoHealSketchFlags,
|
|
67
62
|
type MilestoneRow,
|
|
68
63
|
type SliceRow,
|
|
69
64
|
type TaskRow,
|
|
@@ -223,9 +218,16 @@ export function invalidateStateCache(): void {
|
|
|
223
218
|
* Returns the ID of the first incomplete milestone, or null if all are complete.
|
|
224
219
|
*/
|
|
225
220
|
export async function getActiveMilestoneId(basePath: string): Promise<string | null> {
|
|
226
|
-
// Parallel worker isolation
|
|
227
|
-
|
|
221
|
+
// Parallel worker isolation. Normal DB state derivation remains DB-only;
|
|
222
|
+
// lock env vars are execution routing for explicit worker processes.
|
|
223
|
+
const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
|
|
228
224
|
if (milestoneLock) {
|
|
225
|
+
if (isDbAvailable()) {
|
|
226
|
+
const locked = getAllMilestones().find(m => m.id === milestoneLock);
|
|
227
|
+
if (!locked || isClosedStatus(locked.status) || locked.status === "parked") return null;
|
|
228
|
+
return locked.id;
|
|
229
|
+
}
|
|
230
|
+
|
|
229
231
|
const milestoneIds = findMilestoneIds(basePath);
|
|
230
232
|
if (!milestoneIds.includes(milestoneLock)) return null;
|
|
231
233
|
const lockedParked = resolveMilestoneFile(basePath, milestoneLock, "PARKED");
|
|
@@ -237,14 +239,7 @@ export async function getActiveMilestoneId(basePath: string): Promise<string | n
|
|
|
237
239
|
if (isDbAvailable()) {
|
|
238
240
|
const allMilestones = getAllMilestones();
|
|
239
241
|
if (allMilestones.length > 0) {
|
|
240
|
-
|
|
241
|
-
// Without this, the DB path uses lexicographic sort while the dispatch
|
|
242
|
-
// guard uses queue order — causing a deadlock.
|
|
243
|
-
const customOrder = loadQueueOrder(basePath);
|
|
244
|
-
const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
|
|
245
|
-
const byId = new Map(allMilestones.map(m => [m.id, m]));
|
|
246
|
-
for (const id of sortedIds) {
|
|
247
|
-
const m = byId.get(id)!;
|
|
242
|
+
for (const m of allMilestones) {
|
|
248
243
|
if (isClosedStatus(m.status) || m.status === "parked") continue;
|
|
249
244
|
return m.id;
|
|
250
245
|
}
|
|
@@ -276,12 +271,12 @@ export async function getActiveMilestoneId(basePath: string): Promise<string | n
|
|
|
276
271
|
}
|
|
277
272
|
|
|
278
273
|
/**
|
|
279
|
-
* Reconstruct GSD state from
|
|
274
|
+
* Reconstruct GSD state from the authoritative DB.
|
|
280
275
|
* STATE.md is a rendered cache of this output.
|
|
281
276
|
*
|
|
282
277
|
* When DB is available, queries milestone/slice/task tables directly.
|
|
283
|
-
*
|
|
284
|
-
*
|
|
278
|
+
* Legacy filesystem parsing is available only through an explicit opt-in for
|
|
279
|
+
* tests/recovery flows; runtime must not silently infer state from markdown.
|
|
285
280
|
*/
|
|
286
281
|
export async function deriveState(basePath: string): Promise<GSDState> {
|
|
287
282
|
// Return cached result if within the TTL window for the same basePath
|
|
@@ -296,45 +291,36 @@ export async function deriveState(basePath: string): Promise<GSDState> {
|
|
|
296
291
|
const stopTimer = debugTime("derive-state-impl");
|
|
297
292
|
let result: GSDState;
|
|
298
293
|
|
|
299
|
-
//
|
|
294
|
+
// DB-backed derivation is authoritative whenever the DB is open.
|
|
295
|
+
// Markdown fallback is explicit-only; runtime degrade must not infer state
|
|
296
|
+
// from ROADMAP.md, PLAN.md, SUMMARY.md, REQUIREMENTS.md, or flag files.
|
|
300
297
|
if (isDbAvailable()) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// disk milestones exist but haven't been migrated yet.
|
|
307
|
-
if (dbMilestones.length === 0) {
|
|
308
|
-
const diskIds = findMilestoneIds(basePath);
|
|
309
|
-
let synced = false;
|
|
310
|
-
for (const diskId of diskIds) {
|
|
311
|
-
if (!isGhostMilestone(basePath, diskId)) {
|
|
312
|
-
insertMilestone(diskMilestoneInsert(basePath, diskId));
|
|
313
|
-
synced = true;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (synced) dbMilestones = getAllMilestones();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (dbMilestones.length > 0) {
|
|
320
|
-
const stopDbTimer = debugTime("derive-state-db");
|
|
321
|
-
result = await deriveStateFromDb(basePath);
|
|
322
|
-
stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
323
|
-
_telemetry.dbDeriveCount++;
|
|
324
|
-
} else {
|
|
325
|
-
// DB open but no milestones on disk either — use filesystem path
|
|
326
|
-
result = await _deriveStateImpl(basePath);
|
|
327
|
-
_telemetry.markdownDeriveCount++;
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
// Only warn when DB initialization was attempted and failed — not when
|
|
331
|
-
// the DB simply hasn't been opened yet (e.g. during before_agent_start
|
|
332
|
-
// context injection which runs before any tool invocation opens the DB).
|
|
298
|
+
const stopDbTimer = debugTime("derive-state-db");
|
|
299
|
+
result = await deriveStateFromDb(basePath);
|
|
300
|
+
stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
301
|
+
_telemetry.dbDeriveCount++;
|
|
302
|
+
} else if (process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK === "1") {
|
|
333
303
|
if (wasDbOpenAttempted()) {
|
|
334
|
-
logWarning("state", "DB unavailable — using filesystem state derivation
|
|
304
|
+
logWarning("state", "DB unavailable — using explicit legacy filesystem state derivation");
|
|
335
305
|
}
|
|
336
306
|
result = await _deriveStateImpl(basePath);
|
|
337
307
|
_telemetry.markdownDeriveCount++;
|
|
308
|
+
} else {
|
|
309
|
+
if (wasDbOpenAttempted()) {
|
|
310
|
+
logWarning("state", "DB unavailable — refusing implicit markdown state derivation");
|
|
311
|
+
}
|
|
312
|
+
result = {
|
|
313
|
+
activeMilestone: null,
|
|
314
|
+
activeSlice: null,
|
|
315
|
+
activeTask: null,
|
|
316
|
+
phase: "pre-planning",
|
|
317
|
+
recentDecisions: [],
|
|
318
|
+
blockers: ["DB unavailable — runtime markdown state derivation is disabled"],
|
|
319
|
+
nextAction: "Open or create the canonical GSD database before deriving workflow state.",
|
|
320
|
+
registry: [],
|
|
321
|
+
requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
|
|
322
|
+
progress: { milestones: { done: 0, total: 0 } },
|
|
323
|
+
};
|
|
338
324
|
}
|
|
339
325
|
|
|
340
326
|
stopTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
@@ -369,115 +355,11 @@ function extractContextTitle(content: string | null, fallback: string): string {
|
|
|
369
355
|
// Alias kept for backward compatibility within this file.
|
|
370
356
|
const isStatusDone = isClosedStatus;
|
|
371
357
|
|
|
372
|
-
function loadSync(path: string | null): string | null {
|
|
373
|
-
if (!path) return null;
|
|
374
|
-
try {
|
|
375
|
-
return readFileSync(path, "utf-8");
|
|
376
|
-
} catch {
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function diskMilestoneInsert(basePath: string, mid: string): {
|
|
382
|
-
id: string;
|
|
383
|
-
title?: string;
|
|
384
|
-
status: string;
|
|
385
|
-
depends_on: string[];
|
|
386
|
-
} {
|
|
387
|
-
const contextContent = loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT"));
|
|
388
|
-
const draftContent = !contextContent ? loadSync(resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT")) : null;
|
|
389
|
-
const roadmapContent = loadSync(resolveMilestoneFile(basePath, mid, "ROADMAP"));
|
|
390
|
-
const summaryContent = loadSync(resolveMilestoneFile(basePath, mid, "SUMMARY"));
|
|
391
|
-
const roadmap = roadmapContent ? parseRoadmap(roadmapContent) : null;
|
|
392
|
-
const summary = summaryContent ? parseSummary(summaryContent) : null;
|
|
393
|
-
const summaryTerminal = summaryContent != null && isTerminalMilestoneSummaryContent(summaryContent);
|
|
394
|
-
const parked = resolveMilestoneFile(basePath, mid, "PARKED") !== null;
|
|
395
|
-
|
|
396
|
-
return {
|
|
397
|
-
id: mid,
|
|
398
|
-
title: roadmap
|
|
399
|
-
? stripMilestonePrefix(roadmap.title)
|
|
400
|
-
: (contextContent || draftContent)
|
|
401
|
-
? extractContextTitle(contextContent || draftContent, mid)
|
|
402
|
-
: (summary?.title || mid),
|
|
403
|
-
status: parked ? "parked" : summaryTerminal ? "complete" : "active",
|
|
404
|
-
depends_on: parseContextDependsOn(contextContent ?? draftContent),
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
|
|
408
358
|
/**
|
|
409
359
|
* Derive GSD state from the milestones/slices/tasks DB tables.
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
* Requirements also stay file-based via parseRequirementCounts().
|
|
413
|
-
*
|
|
414
|
-
* Must produce field-identical GSDState to _deriveStateImpl() for the same project.
|
|
360
|
+
* Markdown files are projections only in this path; they are never imported,
|
|
361
|
+
* reconciled, or used as completion signals.
|
|
415
362
|
*/
|
|
416
|
-
function reconcileDiskToDb(basePath: string): MilestoneRow[] {
|
|
417
|
-
let allMilestones = getAllMilestones();
|
|
418
|
-
const dbIdSet = new Set(allMilestones.map(m => m.id));
|
|
419
|
-
const diskIds = findMilestoneIds(basePath);
|
|
420
|
-
let synced = false;
|
|
421
|
-
for (const diskId of diskIds) {
|
|
422
|
-
if (!dbIdSet.has(diskId) && !isGhostMilestone(basePath, diskId)) {
|
|
423
|
-
insertMilestone(diskMilestoneInsert(basePath, diskId));
|
|
424
|
-
synced = true;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
if (synced) allMilestones = getAllMilestones();
|
|
428
|
-
|
|
429
|
-
for (const mid of diskIds) {
|
|
430
|
-
if (isGhostMilestone(basePath, mid)) continue;
|
|
431
|
-
const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
432
|
-
if (!roadmapPath) continue;
|
|
433
|
-
|
|
434
|
-
const dbSlices = getMilestoneSlices(mid);
|
|
435
|
-
const dbSliceIds = new Set(dbSlices.map(s => s.id));
|
|
436
|
-
|
|
437
|
-
let roadmapContent: string;
|
|
438
|
-
try {
|
|
439
|
-
roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
440
|
-
} catch (err) {
|
|
441
|
-
logWarning("state", "reconcileDiskToDb: roadmap read failed, skipping milestone", {
|
|
442
|
-
mid,
|
|
443
|
-
error: (err as Error).message,
|
|
444
|
-
});
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const parsed = parseRoadmap(roadmapContent);
|
|
449
|
-
for (const s of parsed.slices) {
|
|
450
|
-
if (dbSliceIds.has(s.id)) continue;
|
|
451
|
-
const summaryPath = resolveSliceFile(basePath, mid, s.id, "SUMMARY");
|
|
452
|
-
const sliceStatus = (s.done || summaryPath) ? "complete" : "pending";
|
|
453
|
-
insertSlice({
|
|
454
|
-
id: s.id, milestoneId: mid, title: s.title,
|
|
455
|
-
status: sliceStatus, risk: s.risk,
|
|
456
|
-
depends: s.depends, demo: s.demo,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Reconcile stale *existing* slice rows (#3599): a slice row may exist in
|
|
461
|
-
// the DB with status "pending" even though disk artifacts (SUMMARY) prove
|
|
462
|
-
// completion — the same class of desync that task-level reconciliation
|
|
463
|
-
// (further below) already handles. Without this, the dependency resolver
|
|
464
|
-
// builds doneSliceIds from stale DB rows and downstream slices stay blocked
|
|
465
|
-
// forever with "No slice eligible".
|
|
466
|
-
for (const dbSlice of dbSlices) {
|
|
467
|
-
if (isStatusDone(dbSlice.status)) continue;
|
|
468
|
-
const summaryPath = resolveSliceFile(basePath, mid, dbSlice.id, "SUMMARY");
|
|
469
|
-
if (summaryPath) {
|
|
470
|
-
try {
|
|
471
|
-
updateSliceStatus(mid, dbSlice.id, "complete");
|
|
472
|
-
logWarning("reconcile", `slice ${mid}/${dbSlice.id} status reconciled from "${dbSlice.status}" to "complete" (#3599)`, { mid, sid: dbSlice.id });
|
|
473
|
-
} catch (e) {
|
|
474
|
-
logError("reconcile", `failed to update slice ${dbSlice.id}`, { sid: dbSlice.id, error: (e as Error).message });
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return allMilestones;
|
|
480
|
-
}
|
|
481
363
|
|
|
482
364
|
function buildCompletenessSet(basePath: string, milestones: MilestoneRow[]) {
|
|
483
365
|
const completeMilestoneIds = new Set<string>();
|
|
@@ -488,8 +370,7 @@ function buildCompletenessSet(basePath: string, milestones: MilestoneRow[]) {
|
|
|
488
370
|
// (crashed complete-milestone turn, partial merge, manual edit) must not
|
|
489
371
|
// flip derived state to complete and cascade into a false auto-merge (#4179).
|
|
490
372
|
for (const m of milestones) {
|
|
491
|
-
|
|
492
|
-
if (parkedFile || m.status === 'parked') {
|
|
373
|
+
if (m.status === 'parked') {
|
|
493
374
|
parkedMilestoneIds.add(m.id);
|
|
494
375
|
continue;
|
|
495
376
|
}
|
|
@@ -521,39 +402,19 @@ async function buildRegistryAndFindActive(
|
|
|
521
402
|
}
|
|
522
403
|
|
|
523
404
|
const slices = getMilestoneSlices(m.id);
|
|
524
|
-
if (slices.length === 0 && !isStatusDone(m.status) && m.status !== 'queued') {
|
|
525
|
-
if (isGhostMilestone(basePath, m.id)) continue;
|
|
526
|
-
}
|
|
527
405
|
|
|
528
406
|
// DB-authoritative completeness (#4179): only trust completeMilestoneIds,
|
|
529
407
|
// which is itself derived from DB status. SUMMARY-file presence alone must
|
|
530
|
-
// not imply completion.
|
|
531
|
-
// title source for legitimately-complete milestones whose DB row has no title.
|
|
408
|
+
// not imply completion.
|
|
532
409
|
if (completeMilestoneIds.has(m.id)) {
|
|
533
|
-
|
|
534
|
-
if (!m.title) {
|
|
535
|
-
const summaryFile = resolveMilestoneFile(basePath, m.id, "SUMMARY");
|
|
536
|
-
if (summaryFile) {
|
|
537
|
-
const summaryContent = await loadFile(summaryFile);
|
|
538
|
-
if (summaryContent) {
|
|
539
|
-
title = parseSummary(summaryContent).title || m.id;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
410
|
+
const title = stripMilestonePrefix(m.title) || m.id;
|
|
543
411
|
registry.push({ id: m.id, title, status: 'complete' });
|
|
544
412
|
continue;
|
|
545
413
|
}
|
|
546
414
|
|
|
547
415
|
const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
|
|
548
416
|
|
|
549
|
-
|
|
550
|
-
if (title === m.id) {
|
|
551
|
-
const contextFile = resolveMilestoneFile(basePath, m.id, "CONTEXT");
|
|
552
|
-
const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
553
|
-
const contextContent = contextFile ? await loadFile(contextFile) : null;
|
|
554
|
-
const draftContent = draftFile && !contextContent ? await loadFile(draftFile) : null;
|
|
555
|
-
title = extractContextTitle(contextContent || draftContent, m.id);
|
|
556
|
-
}
|
|
417
|
+
const title = stripMilestonePrefix(m.title) || m.id;
|
|
557
418
|
|
|
558
419
|
if (!activeMilestoneFound) {
|
|
559
420
|
const deps = m.depends_on;
|
|
@@ -565,41 +426,22 @@ async function buildRegistryAndFindActive(
|
|
|
565
426
|
}
|
|
566
427
|
|
|
567
428
|
if (m.status === 'queued' && slices.length === 0) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (!contextFile && !draftFile) {
|
|
571
|
-
if (!firstDeferredQueuedShell) {
|
|
572
|
-
firstDeferredQueuedShell = { id: m.id, title, deps };
|
|
573
|
-
}
|
|
574
|
-
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
575
|
-
continue;
|
|
429
|
+
if (!firstDeferredQueuedShell) {
|
|
430
|
+
firstDeferredQueuedShell = { id: m.id, title, deps };
|
|
576
431
|
}
|
|
432
|
+
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
433
|
+
continue;
|
|
577
434
|
}
|
|
578
435
|
|
|
579
436
|
if (allSlicesDone) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
// completeMilestoneIds above. If we reached this branch, the DB says
|
|
586
|
-
// the milestone is NOT complete — so any SUMMARY file on disk is an
|
|
587
|
-
// orphan (crashed complete-milestone, partial merge, manual edit) and
|
|
588
|
-
// must not short-circuit this path. When validation is terminal, fall
|
|
589
|
-
// through to the default active-push below so `complete-milestone` can
|
|
590
|
-
// re-run idempotently.
|
|
591
|
-
if (!validationTerminal) {
|
|
592
|
-
activeMilestone = { id: m.id, title };
|
|
593
|
-
activeMilestoneSlices = slices;
|
|
594
|
-
activeMilestoneFound = true;
|
|
595
|
-
registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
437
|
+
activeMilestone = { id: m.id, title };
|
|
438
|
+
activeMilestoneSlices = slices;
|
|
439
|
+
activeMilestoneFound = true;
|
|
440
|
+
registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
441
|
+
continue;
|
|
598
442
|
}
|
|
599
443
|
|
|
600
|
-
|
|
601
|
-
const draftFile = resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
602
|
-
if (!contextFile && draftFile) activeMilestoneHasDraft = true;
|
|
444
|
+
if (m.status === 'needs-discussion') activeMilestoneHasDraft = true;
|
|
603
445
|
|
|
604
446
|
activeMilestone = { id: m.id, title };
|
|
605
447
|
activeMilestoneSlices = slices;
|
|
@@ -695,10 +537,9 @@ async function handleAllSlicesDone(
|
|
|
695
537
|
milestoneProgress: { done: number, total: number },
|
|
696
538
|
sliceProgress: { done: number, total: number }
|
|
697
539
|
): Promise<GSDState> {
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const validationTerminal =
|
|
701
|
-
const verdict = validationContent ? extractVerdict(validationContent) : undefined;
|
|
540
|
+
const validation = getLatestAssessmentByScope(activeMilestone.id, "milestone-validation");
|
|
541
|
+
const verdict = typeof validation?.status === "string" ? validation.status : undefined;
|
|
542
|
+
const validationTerminal = verdict != null && verdict !== "";
|
|
702
543
|
|
|
703
544
|
if (!validationTerminal) {
|
|
704
545
|
return {
|
|
@@ -744,7 +585,7 @@ function resolveSliceDependencies(activeMilestoneSlices: SliceRow[]): { activeSl
|
|
|
744
585
|
activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id)
|
|
745
586
|
);
|
|
746
587
|
|
|
747
|
-
const sliceLock = process.env.GSD_SLICE_LOCK;
|
|
588
|
+
const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
748
589
|
if (sliceLock) {
|
|
749
590
|
const lockedSlice = activeMilestoneSlices.find(s => s.id === sliceLock);
|
|
750
591
|
if (lockedSlice) {
|
|
@@ -766,117 +607,27 @@ function resolveSliceDependencies(activeMilestoneSlices: SliceRow[]): { activeSl
|
|
|
766
607
|
return { activeSlice: null, activeSliceRow: null };
|
|
767
608
|
}
|
|
768
609
|
|
|
769
|
-
async function reconcileSliceTasks(
|
|
770
|
-
basePath: string,
|
|
771
|
-
milestoneId: string,
|
|
772
|
-
sliceId: string,
|
|
773
|
-
planFile: string
|
|
774
|
-
): Promise<TaskRow[]> {
|
|
775
|
-
let tasks = getSliceTasks(milestoneId, sliceId);
|
|
776
|
-
|
|
777
|
-
// #3600/#4974: import missing plan-file tasks even when the DB already has
|
|
778
|
-
// a partial task set. Existing DB task statuses stay authoritative.
|
|
779
|
-
if (planFile) {
|
|
780
|
-
try {
|
|
781
|
-
const planContent = await loadFile(planFile);
|
|
782
|
-
if (planContent) {
|
|
783
|
-
const diskPlan = parsePlan(planContent);
|
|
784
|
-
if (diskPlan.tasks.length > 0) {
|
|
785
|
-
const dbTaskIds = new Set(tasks.map(t => t.id));
|
|
786
|
-
let inserted = 0;
|
|
787
|
-
for (let i = 0; i < diskPlan.tasks.length; i++) {
|
|
788
|
-
const t = diskPlan.tasks[i];
|
|
789
|
-
if (dbTaskIds.has(t.id)) continue;
|
|
790
|
-
try {
|
|
791
|
-
insertTask({
|
|
792
|
-
id: t.id,
|
|
793
|
-
sliceId,
|
|
794
|
-
milestoneId,
|
|
795
|
-
title: t.title,
|
|
796
|
-
status: t.done ? 'complete' : 'pending',
|
|
797
|
-
sequence: i + 1,
|
|
798
|
-
});
|
|
799
|
-
inserted++;
|
|
800
|
-
} catch (insertErr) {
|
|
801
|
-
logWarning("reconcile", `failed to insert task ${t.id} from plan file: ${insertErr instanceof Error ? insertErr.message : String(insertErr)}`);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
if (inserted > 0) {
|
|
805
|
-
tasks = getSliceTasks(milestoneId, sliceId);
|
|
806
|
-
logWarning("reconcile", `imported ${inserted} missing task(s) from plan file for ${milestoneId}/${sliceId}`, { mid: milestoneId, sid: sliceId });
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
} catch (err) {
|
|
811
|
-
logError("reconcile", `plan-file task import failed for ${milestoneId}/${sliceId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
let reconciled = false;
|
|
816
|
-
for (const t of tasks) {
|
|
817
|
-
if (isStatusDone(t.status)) continue;
|
|
818
|
-
const summaryPath = resolveTaskFile(basePath, milestoneId, sliceId, t.id, "SUMMARY");
|
|
819
|
-
if (summaryPath && existsSync(summaryPath)) {
|
|
820
|
-
try {
|
|
821
|
-
updateTaskStatus(milestoneId, sliceId, t.id, "complete", new Date().toISOString());
|
|
822
|
-
logWarning("reconcile", `task ${milestoneId}/${sliceId}/${t.id} status reconciled from "${t.status}" to "complete" (#2514)`, { mid: milestoneId, sid: sliceId, tid: t.id });
|
|
823
|
-
reconciled = true;
|
|
824
|
-
} catch (e) {
|
|
825
|
-
logError("reconcile", `failed to update task ${t.id}`, { tid: t.id, error: (e as Error).message });
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
if (reconciled) {
|
|
830
|
-
tasks = getSliceTasks(milestoneId, sliceId);
|
|
831
|
-
}
|
|
832
|
-
return tasks;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
610
|
async function detectBlockers(basePath: string, milestoneId: string, sliceId: string, tasks: TaskRow[]): Promise<string | null> {
|
|
836
611
|
const completedTasks = tasks.filter(t => isStatusDone(t.status));
|
|
837
612
|
for (const ct of completedTasks) {
|
|
838
613
|
if (ct.blocker_discovered) {
|
|
839
614
|
return ct.id;
|
|
840
615
|
}
|
|
841
|
-
const summaryFile = resolveTaskFile(basePath, milestoneId, sliceId, ct.id, "SUMMARY");
|
|
842
|
-
if (!summaryFile) continue;
|
|
843
|
-
const summaryContent = await loadFile(summaryFile);
|
|
844
|
-
if (!summaryContent) continue;
|
|
845
|
-
const summary = parseSummary(summaryContent);
|
|
846
|
-
if (summary.frontmatter.blocker_discovered) {
|
|
847
|
-
return ct.id;
|
|
848
|
-
}
|
|
849
616
|
}
|
|
850
617
|
return null;
|
|
851
618
|
}
|
|
852
619
|
|
|
853
620
|
function checkReplanTrigger(basePath: string, milestoneId: string, sliceId: string): boolean {
|
|
854
621
|
const sliceRow = getSlice(milestoneId, sliceId);
|
|
855
|
-
|
|
856
|
-
const diskTriggered = !dbTriggered &&
|
|
857
|
-
!!resolveSliceFile(basePath, milestoneId, sliceId, "REPLAN-TRIGGER");
|
|
858
|
-
return dbTriggered || diskTriggered;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
async function checkInterruptedWork(basePath: string, milestoneId: string, sliceId: string): Promise<boolean> {
|
|
862
|
-
const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
863
|
-
const continueFile = sDir ? resolveSliceFile(basePath, milestoneId, sliceId, "CONTINUE") : null;
|
|
864
|
-
return !!(continueFile && await loadFile(continueFile)) ||
|
|
865
|
-
!!(sDir && await loadFile(join(sDir, "continue.md")));
|
|
622
|
+
return !!sliceRow?.replan_triggered_at;
|
|
866
623
|
}
|
|
867
624
|
|
|
868
625
|
export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
869
|
-
const requirements =
|
|
870
|
-
|
|
871
|
-
let allMilestones = reconcileDiskToDb(basePath);
|
|
626
|
+
const requirements = getRequirementCounts();
|
|
872
627
|
|
|
873
|
-
const
|
|
874
|
-
const sortedIds = sortByQueueOrder(allMilestones.map(m => m.id), customOrder);
|
|
875
|
-
const byId = new Map(allMilestones.map(m => [m.id, m]));
|
|
876
|
-
allMilestones.length = 0;
|
|
877
|
-
for (const id of sortedIds) allMilestones.push(byId.get(id)!);
|
|
628
|
+
const allMilestones = getAllMilestones();
|
|
878
629
|
|
|
879
|
-
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
630
|
+
const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
|
|
880
631
|
const milestones = milestoneLock
|
|
881
632
|
? allMilestones.filter(m => m.id === milestoneLock)
|
|
882
633
|
: allMilestones;
|
|
@@ -905,28 +656,16 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
905
656
|
return handleNoActiveMilestone(registry, requirements, milestoneProgress);
|
|
906
657
|
}
|
|
907
658
|
|
|
908
|
-
const hasRoadmap = resolveMilestoneFile(basePath, activeMilestone.id, "ROADMAP") !== null;
|
|
909
|
-
|
|
910
659
|
if (activeMilestoneSlices.length === 0) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
: `Plan milestone ${activeMilestone.id}.`;
|
|
916
|
-
return {
|
|
917
|
-
activeMilestone, activeSlice: null, activeTask: null,
|
|
918
|
-
phase, recentDecisions: [], blockers: [],
|
|
919
|
-
nextAction, registry, requirements,
|
|
920
|
-
progress: { milestones: milestoneProgress },
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
|
|
660
|
+
const phase = activeMilestoneHasDraft ? 'needs-discussion' as const : 'pre-planning' as const;
|
|
661
|
+
const nextAction = activeMilestoneHasDraft
|
|
662
|
+
? `Discuss draft context for milestone ${activeMilestone.id}.`
|
|
663
|
+
: `Plan milestone ${activeMilestone.id}.`;
|
|
924
664
|
return {
|
|
925
665
|
activeMilestone, activeSlice: null, activeTask: null,
|
|
926
|
-
phase
|
|
927
|
-
nextAction
|
|
928
|
-
|
|
929
|
-
progress: { milestones: milestoneProgress, slices: { done: 0, total: 0 } },
|
|
666
|
+
phase, recentDecisions: [], blockers: [],
|
|
667
|
+
nextAction, registry, requirements,
|
|
668
|
+
progress: { milestones: milestoneProgress },
|
|
930
669
|
};
|
|
931
670
|
}
|
|
932
671
|
|
|
@@ -940,21 +679,14 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
940
679
|
return handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress);
|
|
941
680
|
}
|
|
942
681
|
|
|
943
|
-
|
|
944
|
-
// (e.g. a crash between plan-slice write and the sketch flip), reconcile before
|
|
945
|
-
// running phase derivation so the flag doesn't misroute state.
|
|
946
|
-
autoHealSketchFlags(activeMilestone.id, (sid) =>
|
|
947
|
-
!!resolveSliceFile(basePath, activeMilestone.id, sid, "PLAN"),
|
|
948
|
-
);
|
|
949
|
-
// Re-read slices after auto-heal so downstream reads see fresh is_sketch values.
|
|
950
|
-
const healedSlices = getMilestoneSlices(activeMilestone.id);
|
|
951
|
-
const activeSliceContext = resolveSliceDependencies(healedSlices);
|
|
682
|
+
const activeSliceContext = resolveSliceDependencies(activeMilestoneSlices);
|
|
952
683
|
if (!activeSliceContext.activeSlice) {
|
|
953
684
|
// If locked slice wasn't found, it returns null but logs warning, we need to return 'blocked'
|
|
954
|
-
|
|
685
|
+
const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
686
|
+
if (sliceLock) {
|
|
955
687
|
return {
|
|
956
688
|
activeMilestone, activeSlice: null, activeTask: null,
|
|
957
|
-
phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${
|
|
689
|
+
phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${sliceLock} not found in active milestone slices`],
|
|
958
690
|
nextAction: 'Slice lock references a non-existent slice — check orchestrator dispatch.',
|
|
959
691
|
registry, requirements,
|
|
960
692
|
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
@@ -970,32 +702,20 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
970
702
|
}
|
|
971
703
|
const { activeSlice, activeSliceRow } = activeSliceContext;
|
|
972
704
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
// before execution. When the flag is off, sketches are indistinguishable
|
|
978
|
-
// from a missing plan and fall through to the normal `planning` phase.
|
|
979
|
-
const progressive = loadEffectiveGSDPreferences()?.preferences?.phases?.progressive_planning === true;
|
|
980
|
-
if (progressive && activeSliceRow?.is_sketch === 1) {
|
|
981
|
-
return {
|
|
982
|
-
activeMilestone, activeSlice, activeTask: null,
|
|
983
|
-
phase: 'refining', recentDecisions: [], blockers: [],
|
|
984
|
-
nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
|
|
985
|
-
registry, requirements,
|
|
986
|
-
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
987
|
-
};
|
|
988
|
-
}
|
|
705
|
+
// ADR-011: DB slice metadata is authoritative for sketch refinement.
|
|
706
|
+
// PLAN.md and preference flags are projections/configuration and are
|
|
707
|
+
// deliberately not used to infer whether the slice itself is a sketch.
|
|
708
|
+
if (activeSliceRow?.is_sketch === 1) {
|
|
989
709
|
return {
|
|
990
710
|
activeMilestone, activeSlice, activeTask: null,
|
|
991
|
-
phase: '
|
|
992
|
-
nextAction: `
|
|
711
|
+
phase: 'refining', recentDecisions: [], blockers: [],
|
|
712
|
+
nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
|
|
993
713
|
registry, requirements,
|
|
994
714
|
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
995
715
|
};
|
|
996
716
|
}
|
|
997
717
|
|
|
998
|
-
const tasks =
|
|
718
|
+
const tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
|
|
999
719
|
|
|
1000
720
|
const taskProgress = {
|
|
1001
721
|
done: tasks.filter(t => isStatusDone(t.status)).length,
|
|
@@ -1018,7 +738,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
1018
738
|
return {
|
|
1019
739
|
activeMilestone, activeSlice, activeTask: null,
|
|
1020
740
|
phase: 'planning', recentDecisions: [], blockers: [],
|
|
1021
|
-
nextAction: `Slice ${activeSlice.id} has
|
|
741
|
+
nextAction: `Slice ${activeSlice.id} has no DB tasks. Plan slice tasks before execution.`,
|
|
1022
742
|
registry, requirements,
|
|
1023
743
|
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
1024
744
|
};
|
|
@@ -1026,20 +746,6 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
1026
746
|
|
|
1027
747
|
const activeTask: ActiveRef = { id: activeTaskRow.id, title: activeTaskRow.title };
|
|
1028
748
|
|
|
1029
|
-
const tasksDir = resolveTasksDir(basePath, activeMilestone.id, activeSlice.id);
|
|
1030
|
-
if (tasksDir && existsSync(tasksDir) && tasks.length > 0) {
|
|
1031
|
-
const allFiles = readdirSync(tasksDir).filter(f => f.endsWith(".md"));
|
|
1032
|
-
if (allFiles.length === 0) {
|
|
1033
|
-
return {
|
|
1034
|
-
activeMilestone, activeSlice, activeTask: null,
|
|
1035
|
-
phase: 'planning', recentDecisions: [], blockers: [],
|
|
1036
|
-
nextAction: `Task plan files missing for ${activeSlice.id}. Run plan-slice to generate task plans.`,
|
|
1037
|
-
registry, requirements,
|
|
1038
|
-
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
749
|
// ── Quality gate evaluation check ──────────────────────────────────
|
|
1044
750
|
// Pause before execution only when gates owned by the `gate-evaluate`
|
|
1045
751
|
// turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
|
|
@@ -1119,14 +825,10 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
1119
825
|
}
|
|
1120
826
|
}
|
|
1121
827
|
|
|
1122
|
-
const hasInterrupted = await checkInterruptedWork(basePath, activeMilestone.id, activeSlice.id);
|
|
1123
|
-
|
|
1124
828
|
return {
|
|
1125
829
|
activeMilestone, activeSlice, activeTask,
|
|
1126
830
|
phase: 'executing', recentDecisions: [], blockers: [],
|
|
1127
|
-
nextAction:
|
|
1128
|
-
? `Resume interrupted work on ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}. Read continue.md first.`
|
|
1129
|
-
: `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
|
|
831
|
+
nextAction: `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
|
|
1130
832
|
registry, requirements,
|
|
1131
833
|
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
1132
834
|
};
|
|
@@ -1142,12 +844,12 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
1142
844
|
const milestoneIds = sortByQueueOrder(diskIds, customOrder);
|
|
1143
845
|
|
|
1144
846
|
// ── Parallel worker isolation ──────────────────────────────────────────
|
|
1145
|
-
// When GSD_MILESTONE_LOCK
|
|
847
|
+
// When GSD_PARALLEL_WORKER and GSD_MILESTONE_LOCK are set, this process is a parallel worker
|
|
1146
848
|
// scoped to a single milestone. Filter the milestone list so this worker
|
|
1147
849
|
// only sees its assigned milestone (all others are treated as if they
|
|
1148
850
|
// don't exist). This gives each worker complete isolation without
|
|
1149
851
|
// modifying any other state derivation logic.
|
|
1150
|
-
const milestoneLock = process.env.GSD_MILESTONE_LOCK;
|
|
852
|
+
const milestoneLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : undefined;
|
|
1151
853
|
if (milestoneLock && milestoneIds.includes(milestoneLock)) {
|
|
1152
854
|
milestoneIds.length = 0;
|
|
1153
855
|
milestoneIds.push(milestoneLock);
|
|
@@ -1610,8 +1312,8 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
1610
1312
|
let activeSlice: ActiveRef | null = null;
|
|
1611
1313
|
|
|
1612
1314
|
// ── Slice-level parallel worker isolation ─────────────────────────────
|
|
1613
|
-
// When GSD_SLICE_LOCK
|
|
1614
|
-
const sliceLockLegacy = process.env.GSD_SLICE_LOCK;
|
|
1315
|
+
// When GSD_PARALLEL_WORKER and GSD_SLICE_LOCK are set, override activeSlice to only the locked slice.
|
|
1316
|
+
const sliceLockLegacy = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
1615
1317
|
if (sliceLockLegacy) {
|
|
1616
1318
|
const lockedSlice = activeRoadmap.slices.find(s => s.id === sliceLockLegacy);
|
|
1617
1319
|
if (lockedSlice) {
|