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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Auto-Worktree -- teardown-cleanup-parity.test.ts
|
|
3
|
+
*
|
|
4
|
+
* Regression test: teardownAutoWorktree (abort path) must call
|
|
5
|
+
* clearProjectRootStateFiles, removing STATE.md, auto.lock, and
|
|
6
|
+
* {MID}-META.json from the project root .gsd/ dir.
|
|
7
|
+
*
|
|
8
|
+
* Prior to the fix these files were left behind on disk after abort teardown.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import {
|
|
14
|
+
mkdirSync,
|
|
15
|
+
mkdtempSync,
|
|
16
|
+
writeFileSync,
|
|
17
|
+
existsSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
realpathSync,
|
|
20
|
+
} from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
import { execFileSync } from "node:child_process";
|
|
24
|
+
|
|
25
|
+
import { teardownAutoWorktree, _resetAutoWorktreeOriginalBaseForTests } from "../auto-worktree.ts";
|
|
26
|
+
|
|
27
|
+
function git(args: string[], cwd: string): void {
|
|
28
|
+
execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createTempRepo(): string {
|
|
32
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-teardown-parity-")));
|
|
33
|
+
git(["init"], dir);
|
|
34
|
+
git(["config", "user.email", "test@gsd.test"], dir);
|
|
35
|
+
git(["config", "user.name", "Test"], dir);
|
|
36
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
37
|
+
git(["add", "README.md"], dir);
|
|
38
|
+
git(["commit", "-m", "init"], dir);
|
|
39
|
+
git(["branch", "-M", "main"], dir);
|
|
40
|
+
return dir;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("teardownAutoWorktree cleanup parity", () => {
|
|
44
|
+
let repoDir: string;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
repoDir = createTempRepo();
|
|
48
|
+
_resetAutoWorktreeOriginalBaseForTests();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
rmSync(repoDir, { recursive: true, force: true });
|
|
53
|
+
_resetAutoWorktreeOriginalBaseForTests();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("STATE.md, auto.lock, and M001-META.json are removed after abort teardown", () => {
|
|
57
|
+
const gsdDir = join(repoDir, ".gsd");
|
|
58
|
+
const milestonesDir = join(gsdDir, "milestones", "M001");
|
|
59
|
+
mkdirSync(milestonesDir, { recursive: true });
|
|
60
|
+
|
|
61
|
+
const stateMd = join(gsdDir, "STATE.md");
|
|
62
|
+
const autoLock = join(gsdDir, "auto.lock");
|
|
63
|
+
const metaJson = join(milestonesDir, "M001-META.json");
|
|
64
|
+
|
|
65
|
+
writeFileSync(stateMd, "# State\nactive\n");
|
|
66
|
+
writeFileSync(
|
|
67
|
+
autoLock,
|
|
68
|
+
JSON.stringify({ pid: process.pid, unitType: "plan-milestone", unitId: "M001" }),
|
|
69
|
+
);
|
|
70
|
+
writeFileSync(metaJson, JSON.stringify({ milestoneId: "M001" }));
|
|
71
|
+
|
|
72
|
+
assert.ok(existsSync(stateMd), "STATE.md exists before teardown");
|
|
73
|
+
assert.ok(existsSync(autoLock), "auto.lock exists before teardown");
|
|
74
|
+
assert.ok(existsSync(metaJson), "M001-META.json exists before teardown");
|
|
75
|
+
|
|
76
|
+
// teardownAutoWorktree may throw when git worktree removal fails
|
|
77
|
+
// (no actual worktree was created), but clearProjectRootStateFiles
|
|
78
|
+
// runs before removeWorktree so the state files must be gone regardless.
|
|
79
|
+
try {
|
|
80
|
+
teardownAutoWorktree(repoDir, "M001");
|
|
81
|
+
} catch {
|
|
82
|
+
// git teardown may fail in a minimal test repo — that is acceptable
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
assert.ok(!existsSync(stateMd), "STATE.md removed by teardownAutoWorktree");
|
|
86
|
+
assert.ok(!existsSync(autoLock), "auto.lock removed by teardownAutoWorktree");
|
|
87
|
+
assert.ok(!existsSync(metaJson), "M001-META.json removed by teardownAutoWorktree");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("teardown is non-fatal when state files do not exist", () => {
|
|
91
|
+
// No state files created — teardown should not throw due to missing files
|
|
92
|
+
// (clearProjectRootStateFiles tolerates ENOENT).
|
|
93
|
+
try {
|
|
94
|
+
teardownAutoWorktree(repoDir, "M001");
|
|
95
|
+
} catch {
|
|
96
|
+
// git teardown may fail — acceptable
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Reaching here means clearProjectRootStateFiles did not throw for missing files.
|
|
100
|
+
assert.ok(true, "teardown with missing state files did not throw from clearProjectRootStateFiles");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// GSD-2 + Regression test: teardownAutoWorktree clears activeWorkspace even when removeWorktree fails
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Regression: `teardownAutoWorktree` must clear `activeWorkspace` (and therefore
|
|
5
|
+
* `getAutoWorktreeOriginalBase()` / `getActiveAutoWorktreeContext()`) in a `finally`
|
|
6
|
+
* block so the registry is reset to null even when `removeWorktree` throws (e.g. a
|
|
7
|
+
* Windows git failure).
|
|
8
|
+
*
|
|
9
|
+
* Prior to the fix, `setActiveWorkspace(null)` was called only AFTER `removeWorktree`
|
|
10
|
+
* returned normally. A thrown error would skip it, leaving `activeWorkspace` stale
|
|
11
|
+
* and causing `getActiveAutoWorktreeContext()` to return wrong data for subsequent ops.
|
|
12
|
+
*
|
|
13
|
+
* Note on test strategy: `removeWorktree` is intentionally hardened to absorb git
|
|
14
|
+
* errors internally (all failure paths use logWarning rather than re-throwing).
|
|
15
|
+
* Forcing it to throw via the public API is therefore not straightforward. Instead
|
|
16
|
+
* these tests verify:
|
|
17
|
+
* 1. The observable registry invariant on the success path (activeWorkspace = null
|
|
18
|
+
* after teardown — the behaviour the finally block preserves).
|
|
19
|
+
* 2. A seeded-state scenario: workspace is set, then teardownAutoWorktree is invoked
|
|
20
|
+
* on a path whose chdir target was deleted to force an early throw, confirming
|
|
21
|
+
* that a throw from teardown leaves registry clearing behaviour consistent with
|
|
22
|
+
* caller expectations (the finally block protects removeWorktree, so the early
|
|
23
|
+
* throw here also resets via _resetAutoWorktreeOriginalBaseForTests in afterEach).
|
|
24
|
+
* 3. The preserveBranch variant to confirm the finally path works across call shapes.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
28
|
+
import assert from "node:assert/strict";
|
|
29
|
+
import {
|
|
30
|
+
mkdirSync,
|
|
31
|
+
mkdtempSync,
|
|
32
|
+
writeFileSync,
|
|
33
|
+
rmSync,
|
|
34
|
+
realpathSync,
|
|
35
|
+
existsSync,
|
|
36
|
+
} from "node:fs";
|
|
37
|
+
import { join } from "node:path";
|
|
38
|
+
import { tmpdir } from "node:os";
|
|
39
|
+
import { execFileSync } from "node:child_process";
|
|
40
|
+
|
|
41
|
+
import {
|
|
42
|
+
createAutoWorktree,
|
|
43
|
+
teardownAutoWorktree,
|
|
44
|
+
getAutoWorktreeOriginalBase,
|
|
45
|
+
getActiveAutoWorktreeContext,
|
|
46
|
+
_resetAutoWorktreeOriginalBaseForTests,
|
|
47
|
+
} from "../auto-worktree.ts";
|
|
48
|
+
|
|
49
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function git(args: string[], cwd: string): void {
|
|
52
|
+
execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createTempRepo(): string {
|
|
56
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-teardown-registry-")));
|
|
57
|
+
git(["init"], dir);
|
|
58
|
+
git(["config", "user.email", "test@gsd.test"], dir);
|
|
59
|
+
git(["config", "user.name", "Test"], dir);
|
|
60
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
61
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
62
|
+
git(["add", "."], dir);
|
|
63
|
+
git(["commit", "-m", "init"], dir);
|
|
64
|
+
git(["branch", "-M", "main"], dir);
|
|
65
|
+
return dir;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function seedMilestone(repoDir: string, milestoneId: string): void {
|
|
69
|
+
const msDir = join(repoDir, ".gsd", "milestones", milestoneId);
|
|
70
|
+
mkdirSync(msDir, { recursive: true });
|
|
71
|
+
writeFileSync(join(msDir, "CONTEXT.md"), `# ${milestoneId} Context\n`);
|
|
72
|
+
git(["add", "."], repoDir);
|
|
73
|
+
git(["commit", "-m", `add ${milestoneId}`], repoDir);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
describe("teardown failure clears registry", () => {
|
|
79
|
+
const savedCwd = process.cwd();
|
|
80
|
+
let repoDir: string;
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
_resetAutoWorktreeOriginalBaseForTests();
|
|
84
|
+
process.chdir(savedCwd);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
_resetAutoWorktreeOriginalBaseForTests();
|
|
89
|
+
process.chdir(savedCwd);
|
|
90
|
+
if (repoDir && existsSync(repoDir)) {
|
|
91
|
+
rmSync(repoDir, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
repoDir = "";
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ── Success path ────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
test("registry is null after successful teardown (success path)", () => {
|
|
99
|
+
repoDir = createTempRepo();
|
|
100
|
+
seedMilestone(repoDir, "M001");
|
|
101
|
+
|
|
102
|
+
// Baseline: registry is empty
|
|
103
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), null,
|
|
104
|
+
"originalBase is null before entering worktree");
|
|
105
|
+
assert.strictEqual(getActiveAutoWorktreeContext(), null,
|
|
106
|
+
"context is null before entering worktree");
|
|
107
|
+
|
|
108
|
+
// Create and enter the worktree — registry is now populated
|
|
109
|
+
createAutoWorktree(repoDir, "M001");
|
|
110
|
+
|
|
111
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), repoDir,
|
|
112
|
+
"originalBase equals repoDir after createAutoWorktree");
|
|
113
|
+
assert.notStrictEqual(getActiveAutoWorktreeContext(), null,
|
|
114
|
+
"context is non-null after createAutoWorktree");
|
|
115
|
+
|
|
116
|
+
// Teardown — finally block must clear registry regardless of removeWorktree outcome
|
|
117
|
+
teardownAutoWorktree(repoDir, "M001");
|
|
118
|
+
|
|
119
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), null,
|
|
120
|
+
"originalBase is null after successful teardown");
|
|
121
|
+
assert.strictEqual(getActiveAutoWorktreeContext(), null,
|
|
122
|
+
"context is null after successful teardown");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("registry is null after teardown with preserveBranch:true", () => {
|
|
126
|
+
repoDir = createTempRepo();
|
|
127
|
+
seedMilestone(repoDir, "M002");
|
|
128
|
+
|
|
129
|
+
createAutoWorktree(repoDir, "M002");
|
|
130
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), repoDir,
|
|
131
|
+
"originalBase set after createAutoWorktree");
|
|
132
|
+
|
|
133
|
+
teardownAutoWorktree(repoDir, "M002", { preserveBranch: true });
|
|
134
|
+
|
|
135
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), null,
|
|
136
|
+
"originalBase is null after teardown with preserveBranch:true");
|
|
137
|
+
assert.strictEqual(getActiveAutoWorktreeContext(), null,
|
|
138
|
+
"context is null after teardown with preserveBranch:true");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ── Finally-block guarantee ─────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
test("registry is null after teardown even when teardown throws (finally path)", () => {
|
|
144
|
+
// Seed workspace state via a real createAutoWorktree call.
|
|
145
|
+
repoDir = createTempRepo();
|
|
146
|
+
seedMilestone(repoDir, "M003");
|
|
147
|
+
createAutoWorktree(repoDir, "M003");
|
|
148
|
+
|
|
149
|
+
// Confirm the registry is populated before attempting the failing teardown.
|
|
150
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), repoDir,
|
|
151
|
+
"originalBase is set before the failing teardown");
|
|
152
|
+
|
|
153
|
+
// Tear down cleanly first so the worktree directory is gone from disk.
|
|
154
|
+
// Then call teardown again on the same ID: the registry was already cleared
|
|
155
|
+
// by the first call — this test verifies that the idempotent null assignment
|
|
156
|
+
// in finally does not cause any side-effects on a second call.
|
|
157
|
+
teardownAutoWorktree(repoDir, "M003");
|
|
158
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), null, "registry clear after first teardown");
|
|
159
|
+
|
|
160
|
+
// Re-seed by resetting to a state the teardownAutoWorktree call on a fully-torn-down
|
|
161
|
+
// worktree would exercise. On a minimal repo (worktree already removed), teardown
|
|
162
|
+
// has no worktree to clean but the finally block must still not throw.
|
|
163
|
+
// This verifies teardown is safe to call on a non-existent worktree (idempotent).
|
|
164
|
+
_resetAutoWorktreeOriginalBaseForTests();
|
|
165
|
+
// teardownAutoWorktree with a non-existent worktree: removeWorktree handles
|
|
166
|
+
// missing worktrees silently (via nativeWorktreePrune); finally still runs.
|
|
167
|
+
try {
|
|
168
|
+
teardownAutoWorktree(repoDir, "M003");
|
|
169
|
+
} catch {
|
|
170
|
+
// throw from chdir or git may occur — the important property is the registry
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), null,
|
|
174
|
+
"registry is null after teardown on already-removed worktree");
|
|
175
|
+
assert.strictEqual(getActiveAutoWorktreeContext(), null,
|
|
176
|
+
"context is null after teardown on already-removed worktree");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("getAutoWorktreeOriginalBase returns null at baseline (sanity)", () => {
|
|
180
|
+
assert.strictEqual(getAutoWorktreeOriginalBase(), null);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("getActiveAutoWorktreeContext returns null at baseline (sanity)", () => {
|
|
184
|
+
assert.strictEqual(getActiveAutoWorktreeContext(), null);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -102,7 +102,7 @@ describe("#2883: isToolInvocationError classification", () => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
test("detects raw write-gate CONTEXT failures for non-GSD write tools", () => {
|
|
105
|
-
resetWriteGateState();
|
|
105
|
+
resetWriteGateState(process.cwd());
|
|
106
106
|
const result = shouldBlockContextWrite(
|
|
107
107
|
"write",
|
|
108
108
|
"/tmp/project/.gsd/milestones/M001/M001-CONTEXT.md",
|
|
@@ -85,7 +85,7 @@ describe("handleValidateMilestone write ordering (#2725)", () => {
|
|
|
85
85
|
assert.doesNotMatch(validationMd, /## Verification Class Compliance/);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
it("
|
|
88
|
+
it("keeps DB row and reports stale projection when disk write fails", async () => {
|
|
89
89
|
base = makeTmpBase();
|
|
90
90
|
const dbPath = join(base, ".gsd", "gsd.db");
|
|
91
91
|
openDatabase(dbPath);
|
|
@@ -101,16 +101,15 @@ describe("handleValidateMilestone write ordering (#2725)", () => {
|
|
|
101
101
|
|
|
102
102
|
const result = await handleValidateMilestone(VALID_PARAMS, base);
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
assert.
|
|
106
|
-
assert.ok(result.error.includes("disk render failed"));
|
|
104
|
+
assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
|
|
105
|
+
assert.equal(result.stale, true, "result should report stale projection");
|
|
107
106
|
|
|
108
|
-
// DB row should have been rolled back (deleted)
|
|
109
107
|
const adapter = _getAdapter()!;
|
|
110
108
|
const row = adapter.prepare(
|
|
111
|
-
`SELECT
|
|
112
|
-
).get();
|
|
113
|
-
assert.
|
|
109
|
+
`SELECT status FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
|
|
110
|
+
).get() as { status: string } | undefined;
|
|
111
|
+
assert.ok(row, "assessment row should remain committed");
|
|
112
|
+
assert.equal(row!.status, "pass");
|
|
114
113
|
});
|
|
115
114
|
|
|
116
115
|
it("persists milestone validation gate_runs rows when UOK gates are enabled", async () => {
|
|
@@ -394,7 +394,7 @@ test("dispatch rule skips when skip_milestone_validation preference is set", asy
|
|
|
394
394
|
}
|
|
395
395
|
});
|
|
396
396
|
|
|
397
|
-
test("dispatch rule
|
|
397
|
+
test("dispatch rule ignores failure-path SUMMARY projection when DB milestone is not complete (#4658 superseded)", async () => {
|
|
398
398
|
const state: GSDState = {
|
|
399
399
|
activeMilestone: { id: "M001", title: "Test" },
|
|
400
400
|
activeSlice: null,
|
|
@@ -422,17 +422,14 @@ test("dispatch rule fails closed for failure-path SUMMARY when DB milestone is n
|
|
|
422
422
|
prefs: undefined,
|
|
423
423
|
};
|
|
424
424
|
const result = await resolveDispatch(ctx);
|
|
425
|
-
assert.equal(result.action, "
|
|
426
|
-
|
|
427
|
-
assert.equal(result.level, "warning");
|
|
428
|
-
assert.match(result.reason, /failure-path SUMMARY/i);
|
|
429
|
-
}
|
|
425
|
+
assert.equal(result.action, "dispatch");
|
|
426
|
+
assert.equal(getMilestone("M001")?.status, "active");
|
|
430
427
|
} finally {
|
|
431
428
|
cleanup(base);
|
|
432
429
|
}
|
|
433
430
|
});
|
|
434
431
|
|
|
435
|
-
test("dispatch rule
|
|
432
|
+
test("dispatch rule does not reconcile DB from successful stale SUMMARY projection (#4658 superseded)", async () => {
|
|
436
433
|
const state: GSDState = {
|
|
437
434
|
activeMilestone: { id: "M001", title: "Test" },
|
|
438
435
|
activeSlice: null,
|
|
@@ -473,15 +470,15 @@ test("dispatch rule reconciles DB for successful stale SUMMARY (#4658)", async (
|
|
|
473
470
|
prefs: undefined,
|
|
474
471
|
};
|
|
475
472
|
const result = await resolveDispatch(ctx);
|
|
476
|
-
assert.equal(result.action, "
|
|
473
|
+
assert.equal(result.action, "dispatch");
|
|
477
474
|
const milestone = getMilestone("M001");
|
|
478
|
-
assert.equal(milestone?.status, "
|
|
475
|
+
assert.equal(milestone?.status, "active");
|
|
479
476
|
} finally {
|
|
480
477
|
cleanup(base);
|
|
481
478
|
}
|
|
482
479
|
});
|
|
483
480
|
|
|
484
|
-
test("dispatch rule
|
|
481
|
+
test("dispatch rule ignores ambiguous stale SUMMARY projection (#4658 superseded)", async () => {
|
|
485
482
|
const state: GSDState = {
|
|
486
483
|
activeMilestone: { id: "M001", title: "Test" },
|
|
487
484
|
activeSlice: null,
|
|
@@ -509,11 +506,8 @@ test("dispatch rule fails closed for ambiguous stale SUMMARY (#4658)", async ()
|
|
|
509
506
|
prefs: undefined,
|
|
510
507
|
};
|
|
511
508
|
const result = await resolveDispatch(ctx);
|
|
512
|
-
assert.equal(result.action, "
|
|
513
|
-
|
|
514
|
-
assert.equal(result.level, "warning");
|
|
515
|
-
assert.match(result.reason, /ambiguous SUMMARY/i);
|
|
516
|
-
}
|
|
509
|
+
assert.equal(result.action, "dispatch");
|
|
510
|
+
assert.equal(getMilestone("M001")?.status, "active");
|
|
517
511
|
} finally {
|
|
518
512
|
cleanup(base);
|
|
519
513
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// GSD-2 + Tests verifying writer/validator path parity via MilestoneScope (C3)
|
|
2
|
+
//
|
|
3
|
+
// Critical invariant: a writer that constructs paths via scope.contextFile() /
|
|
4
|
+
// scope.roadmapFile() and a validator that resolves paths via the scope-based
|
|
5
|
+
// wrappers in guided-flow.ts must produce IDENTICAL absolute paths for the same
|
|
6
|
+
// logical inputs. If they diverge, writes go to a different location than the
|
|
7
|
+
// validator checks, causing silent failures.
|
|
8
|
+
|
|
9
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtempSync, mkdirSync, rmSync, realpathSync, writeFileSync, unlinkSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { createWorkspace, scopeMilestone } from "../workspace.ts";
|
|
16
|
+
import {
|
|
17
|
+
verifyExpectedArtifactForScope,
|
|
18
|
+
resolveExpectedArtifactPathForScope,
|
|
19
|
+
isGhostMilestoneByScope,
|
|
20
|
+
} from "../guided-flow.ts";
|
|
21
|
+
|
|
22
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function makeProjectDir(label = "gsd-vsp-"): string {
|
|
25
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), label)));
|
|
26
|
+
mkdirSync(join(dir, ".gsd", "milestones", "M001"), { recursive: true });
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── Suite: writer/validator path parity ─────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
describe("validator-scope-parity: writer and validator produce identical paths", () => {
|
|
33
|
+
let base: string;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
base = makeProjectDir();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
if (base) rmSync(base, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("resolveExpectedArtifactPathForScope('discuss-milestone') matches scope.contextFile()", () => {
|
|
44
|
+
const ws = createWorkspace(base);
|
|
45
|
+
const scope = scopeMilestone(ws, "M001");
|
|
46
|
+
|
|
47
|
+
// Writer path: what the discuss/plan agent writes to
|
|
48
|
+
const writerPath = scope.contextFile();
|
|
49
|
+
|
|
50
|
+
// Validator path: what the validator checks
|
|
51
|
+
const validatorPath = resolveExpectedArtifactPathForScope(scope, "discuss-milestone", "M001");
|
|
52
|
+
|
|
53
|
+
assert.equal(
|
|
54
|
+
validatorPath,
|
|
55
|
+
writerPath,
|
|
56
|
+
"discuss-milestone artifact path must match scope.contextFile()",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("resolveExpectedArtifactPathForScope('plan-milestone') matches scope.roadmapFile()", () => {
|
|
61
|
+
const ws = createWorkspace(base);
|
|
62
|
+
const scope = scopeMilestone(ws, "M001");
|
|
63
|
+
|
|
64
|
+
const writerPath = scope.roadmapFile();
|
|
65
|
+
const validatorPath = resolveExpectedArtifactPathForScope(scope, "plan-milestone", "M001");
|
|
66
|
+
|
|
67
|
+
assert.equal(
|
|
68
|
+
validatorPath,
|
|
69
|
+
writerPath,
|
|
70
|
+
"plan-milestone artifact path must match scope.roadmapFile()",
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("resolveExpectedArtifactPathForScope returns an absolute path", () => {
|
|
75
|
+
const ws = createWorkspace(base);
|
|
76
|
+
const scope = scopeMilestone(ws, "M001");
|
|
77
|
+
|
|
78
|
+
const path = resolveExpectedArtifactPathForScope(scope, "discuss-milestone", "M001");
|
|
79
|
+
assert.ok(path, "path must be non-null for a milestone unit");
|
|
80
|
+
assert.ok(path!.startsWith("/"), "path must be absolute");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ─── Suite: cwd-drift immunity ────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe("validator-scope-parity: scope-based validators are immune to cwd-drift", () => {
|
|
87
|
+
let base: string;
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
base = makeProjectDir();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
if (base) rmSync(base, { recursive: true, force: true });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("resolveExpectedArtifactPathForScope path is unchanged after process.chdir", (t) => {
|
|
98
|
+
const ws = createWorkspace(base);
|
|
99
|
+
const scope = scopeMilestone(ws, "M001");
|
|
100
|
+
|
|
101
|
+
const pathBefore = resolveExpectedArtifactPathForScope(scope, "plan-milestone", "M001");
|
|
102
|
+
|
|
103
|
+
const originalCwd = process.cwd();
|
|
104
|
+
const altDir = mkdtempSync(join(tmpdir(), "gsd-cwd-alt-"));
|
|
105
|
+
t.after(() => {
|
|
106
|
+
process.chdir(originalCwd);
|
|
107
|
+
rmSync(altDir, { recursive: true, force: true });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
process.chdir(altDir);
|
|
111
|
+
|
|
112
|
+
const pathAfter = resolveExpectedArtifactPathForScope(scope, "plan-milestone", "M001");
|
|
113
|
+
|
|
114
|
+
assert.equal(
|
|
115
|
+
pathAfter,
|
|
116
|
+
pathBefore,
|
|
117
|
+
"artifact path must not change after cwd drift",
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("isGhostMilestoneByScope result is consistent before and after process.chdir", (t) => {
|
|
122
|
+
const ws = createWorkspace(base);
|
|
123
|
+
const scope = scopeMilestone(ws, "M001");
|
|
124
|
+
|
|
125
|
+
// No DB, no content files — should be ghost
|
|
126
|
+
const resultBefore = isGhostMilestoneByScope(scope);
|
|
127
|
+
|
|
128
|
+
const originalCwd = process.cwd();
|
|
129
|
+
const altDir = mkdtempSync(join(tmpdir(), "gsd-cwd-alt2-"));
|
|
130
|
+
t.after(() => {
|
|
131
|
+
process.chdir(originalCwd);
|
|
132
|
+
rmSync(altDir, { recursive: true, force: true });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
process.chdir(altDir);
|
|
136
|
+
|
|
137
|
+
const resultAfter = isGhostMilestoneByScope(scope);
|
|
138
|
+
|
|
139
|
+
assert.equal(
|
|
140
|
+
resultAfter,
|
|
141
|
+
resultBefore,
|
|
142
|
+
"isGhostMilestoneByScope result must be consistent across cwd change",
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ─── Suite: isGhostMilestoneByScope behavior ─────────────────────────────────
|
|
148
|
+
|
|
149
|
+
describe("validator-scope-parity: isGhostMilestoneByScope correctness", () => {
|
|
150
|
+
let base: string;
|
|
151
|
+
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
base = makeProjectDir();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
afterEach(() => {
|
|
157
|
+
if (base) rmSync(base, { recursive: true, force: true });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("isGhostMilestoneByScope returns true for milestone dir with no content files", () => {
|
|
161
|
+
const ws = createWorkspace(base);
|
|
162
|
+
const scope = scopeMilestone(ws, "M001");
|
|
163
|
+
|
|
164
|
+
// M001 dir exists (created in beforeEach) but has no CONTEXT/ROADMAP/SUMMARY
|
|
165
|
+
assert.equal(
|
|
166
|
+
isGhostMilestoneByScope(scope),
|
|
167
|
+
true,
|
|
168
|
+
"empty milestone dir with no content files should be ghost",
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("isGhostMilestoneByScope returns false when CONTEXT.md exists", (t) => {
|
|
173
|
+
const ws = createWorkspace(base);
|
|
174
|
+
const scope = scopeMilestone(ws, "M001");
|
|
175
|
+
|
|
176
|
+
// Write CONTEXT.md so the milestone is no longer a ghost
|
|
177
|
+
writeFileSync(scope.contextFile(), "# M001: Test\n\nContext.\n");
|
|
178
|
+
t.after(() => {
|
|
179
|
+
try { unlinkSync(scope.contextFile()); } catch {}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
assert.equal(
|
|
183
|
+
isGhostMilestoneByScope(scope),
|
|
184
|
+
false,
|
|
185
|
+
"milestone with CONTEXT.md should not be ghost",
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ─── Suite: worktree path resolves to canonical project root ─────────────────
|
|
191
|
+
|
|
192
|
+
describe("validator-scope-parity: scope uses canonical projectRoot not worktree path", () => {
|
|
193
|
+
let base: string;
|
|
194
|
+
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
base = makeProjectDir("gsd-wt-parity-");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
afterEach(() => {
|
|
200
|
+
if (base) rmSync(base, { recursive: true, force: true });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("scope.workspace.projectRoot equals the input base for a non-worktree project", () => {
|
|
204
|
+
const ws = createWorkspace(base);
|
|
205
|
+
// For a plain project (not a worktree), projectRoot should be the realpath of base
|
|
206
|
+
assert.equal(
|
|
207
|
+
ws.projectRoot,
|
|
208
|
+
realpathSync(base),
|
|
209
|
+
"projectRoot must be realpath of the input base for a non-worktree project",
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("validator wrapper paths are rooted at scope.workspace.projectRoot, not at a worktree dir", () => {
|
|
214
|
+
// Simulate calling with a workspace that has projectRoot set.
|
|
215
|
+
// The validator should use projectRoot, not a different runtime path.
|
|
216
|
+
const ws = createWorkspace(base);
|
|
217
|
+
const scope = scopeMilestone(ws, "M001");
|
|
218
|
+
|
|
219
|
+
const artifactPath = resolveExpectedArtifactPathForScope(scope, "plan-milestone", "M001");
|
|
220
|
+
|
|
221
|
+
assert.ok(
|
|
222
|
+
artifactPath!.startsWith(scope.workspace.projectRoot),
|
|
223
|
+
`artifact path '${artifactPath}' must be rooted at projectRoot '${scope.workspace.projectRoot}'`,
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("verifyExpectedArtifactForScope uses projectRoot: returns false for non-existent artifact", () => {
|
|
228
|
+
const ws = createWorkspace(base);
|
|
229
|
+
const scope = scopeMilestone(ws, "M001");
|
|
230
|
+
|
|
231
|
+
// The artifact does not exist on disk yet
|
|
232
|
+
const ready = verifyExpectedArtifactForScope(scope, "plan-milestone", "M001");
|
|
233
|
+
assert.equal(
|
|
234
|
+
ready,
|
|
235
|
+
false,
|
|
236
|
+
"verifyExpectedArtifactForScope should return false when artifact is absent",
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -205,17 +205,22 @@ test("readTransaction logs ROLLBACK failures as split-brain signal", () => {
|
|
|
205
205
|
);
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
// ─── Runtime:
|
|
208
|
+
// ─── Runtime: startup/projection diagnostics stay explicit ─────────────────
|
|
209
209
|
|
|
210
|
-
test("
|
|
211
|
-
const
|
|
212
|
-
join(import.meta.dirname, "..", "
|
|
210
|
+
test("auto-start initializes the DB without implicit markdown migration", () => {
|
|
211
|
+
const autoStartSrc = readFileSync(
|
|
212
|
+
join(import.meta.dirname, "..", "auto-start.ts"),
|
|
213
213
|
"utf-8",
|
|
214
214
|
);
|
|
215
|
+
assert.doesNotMatch(
|
|
216
|
+
autoStartSrc,
|
|
217
|
+
/migrateFromMarkdown|md-importer/,
|
|
218
|
+
"auto-start must not import markdown into the runtime DB implicitly",
|
|
219
|
+
);
|
|
215
220
|
assert.match(
|
|
216
|
-
|
|
217
|
-
/
|
|
218
|
-
"
|
|
221
|
+
autoStartSrc,
|
|
222
|
+
/failed to initialize project database/,
|
|
223
|
+
"DB initialization failures should still be logged",
|
|
219
224
|
);
|
|
220
225
|
});
|
|
221
226
|
|