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,221 @@
|
|
|
1
|
+
// GSD-2 + db-writer root-artifact path guard: regression tests for M1 fix
|
|
2
|
+
|
|
3
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import {
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
realpathSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
|
|
16
|
+
import { createWorkspace, scopeMilestone } from "../workspace.ts";
|
|
17
|
+
import {
|
|
18
|
+
saveArtifactToDb,
|
|
19
|
+
saveArtifactToDbByScope,
|
|
20
|
+
saveArtifactToDbForWorkspace,
|
|
21
|
+
} from "../db-writer.ts";
|
|
22
|
+
import { openDatabase, closeDatabase } from "../gsd-db.ts";
|
|
23
|
+
|
|
24
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function makeProjectDir(): string {
|
|
27
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-dbwriter-root-")));
|
|
28
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── Suite 1: saveArtifactToDb with undefined milestone_id writes to .gsd/ root, not milestones/ ──
|
|
33
|
+
|
|
34
|
+
describe("saveArtifactToDb: root artifact (no milestone_id) routes to workspace .gsd root", () => {
|
|
35
|
+
let tmp: string;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
tmp = makeProjectDir();
|
|
39
|
+
openDatabase(join(tmp, ".gsd", "gsd.db"));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
closeDatabase();
|
|
44
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("opts.milestone_id = undefined writes artifact at .gsd/REQUIREMENTS.md, not inside milestones/", async () => {
|
|
48
|
+
const content = "# Requirements\n\nTest root artifact.\n";
|
|
49
|
+
const opts = {
|
|
50
|
+
path: "REQUIREMENTS.md",
|
|
51
|
+
artifact_type: "REQUIREMENTS_DRAFT",
|
|
52
|
+
content,
|
|
53
|
+
milestone_id: undefined,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
await saveArtifactToDb(opts, tmp);
|
|
57
|
+
|
|
58
|
+
const ws = createWorkspace(tmp);
|
|
59
|
+
const expectedPath = resolve(ws.contract.projectGsd, "REQUIREMENTS.md");
|
|
60
|
+
|
|
61
|
+
assert.ok(existsSync(expectedPath), "root artifact written at .gsd/REQUIREMENTS.md");
|
|
62
|
+
assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
|
|
63
|
+
|
|
64
|
+
// Must NOT be inside milestones/ — the latent trap being fixed
|
|
65
|
+
const wrongPath = resolve(ws.contract.projectGsd, "milestones", "", "REQUIREMENTS.md");
|
|
66
|
+
assert.ok(!existsSync(wrongPath), "artifact NOT written inside milestones/");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("opts.milestone_id = null writes artifact at .gsd/ root", async () => {
|
|
70
|
+
const content = "# Project\n\nRoot project doc.\n";
|
|
71
|
+
const opts = {
|
|
72
|
+
path: "PROJECT.md",
|
|
73
|
+
artifact_type: "PROJECT",
|
|
74
|
+
content,
|
|
75
|
+
milestone_id: undefined,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
await saveArtifactToDb(opts, tmp);
|
|
79
|
+
|
|
80
|
+
const ws = createWorkspace(tmp);
|
|
81
|
+
const expectedPath = resolve(ws.contract.projectGsd, "PROJECT.md");
|
|
82
|
+
|
|
83
|
+
assert.ok(existsSync(expectedPath), "PROJECT.md written at .gsd/PROJECT.md");
|
|
84
|
+
assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("path resolves via workspace.contract.projectGsd, not a hand-rolled join", async () => {
|
|
88
|
+
const content = "# Knowledge\n";
|
|
89
|
+
const opts = {
|
|
90
|
+
path: "KNOWLEDGE.md",
|
|
91
|
+
artifact_type: "KNOWLEDGE",
|
|
92
|
+
content,
|
|
93
|
+
milestone_id: undefined,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
await saveArtifactToDb(opts, tmp);
|
|
97
|
+
|
|
98
|
+
const ws = createWorkspace(tmp);
|
|
99
|
+
// The canonical path must equal contract.projectGsd + '/KNOWLEDGE.md'
|
|
100
|
+
const canonicalPath = join(ws.contract.projectGsd, "KNOWLEDGE.md");
|
|
101
|
+
assert.ok(existsSync(canonicalPath), "file at contract.projectGsd/KNOWLEDGE.md");
|
|
102
|
+
assert.equal(
|
|
103
|
+
canonicalPath,
|
|
104
|
+
join(ws.projectRoot, ".gsd", "KNOWLEDGE.md"),
|
|
105
|
+
"contract.projectGsd-based path equals projectRoot/.gsd/KNOWLEDGE.md",
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ─── Suite 2: saveArtifactToDb with a real milestone_id still works (no regression) ──
|
|
111
|
+
|
|
112
|
+
describe("saveArtifactToDb: milestone_id present routes to milestones/ (no regression)", () => {
|
|
113
|
+
let tmp: string;
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
tmp = makeProjectDir();
|
|
117
|
+
openDatabase(join(tmp, ".gsd", "gsd.db"));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
afterEach(() => {
|
|
121
|
+
closeDatabase();
|
|
122
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("milestone_id = 'M001' writes to .gsd/milestones/M001/...", async () => {
|
|
126
|
+
const relPath = "milestones/M001/M001-CONTEXT.md";
|
|
127
|
+
const content = "# M001 Context\n";
|
|
128
|
+
const opts = {
|
|
129
|
+
path: relPath,
|
|
130
|
+
artifact_type: "CONTEXT",
|
|
131
|
+
content,
|
|
132
|
+
milestone_id: "M001",
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
await saveArtifactToDb(opts, tmp);
|
|
136
|
+
|
|
137
|
+
const ws = createWorkspace(tmp);
|
|
138
|
+
const expectedPath = resolve(ws.contract.projectGsd, relPath);
|
|
139
|
+
|
|
140
|
+
assert.ok(existsSync(expectedPath), "milestone artifact written at correct path");
|
|
141
|
+
assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ─── Suite 3: saveArtifactToDbByScope with empty milestoneId throws a clear error ──
|
|
146
|
+
|
|
147
|
+
describe("saveArtifactToDbByScope: empty milestoneId throws defensive error", () => {
|
|
148
|
+
let tmp: string;
|
|
149
|
+
|
|
150
|
+
beforeEach(() => {
|
|
151
|
+
tmp = makeProjectDir();
|
|
152
|
+
openDatabase(join(tmp, ".gsd", "gsd.db"));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
afterEach(() => {
|
|
156
|
+
closeDatabase();
|
|
157
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("scope with empty milestoneId throws GSDError mentioning saveArtifactToDbForWorkspace", async () => {
|
|
161
|
+
const ws = createWorkspace(tmp);
|
|
162
|
+
const emptyScope = scopeMilestone(ws, "");
|
|
163
|
+
const opts = {
|
|
164
|
+
path: "REQUIREMENTS.md",
|
|
165
|
+
artifact_type: "REQUIREMENTS_DRAFT",
|
|
166
|
+
content: "# req\n",
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
await assert.rejects(
|
|
170
|
+
() => saveArtifactToDbByScope(emptyScope, opts),
|
|
171
|
+
(err: unknown) => {
|
|
172
|
+
assert.ok(err instanceof Error, "thrown value is an Error");
|
|
173
|
+
assert.ok(
|
|
174
|
+
err.message.includes("milestoneId is empty"),
|
|
175
|
+
`error message should mention 'milestoneId is empty', got: ${err.message}`,
|
|
176
|
+
);
|
|
177
|
+
assert.ok(
|
|
178
|
+
err.message.includes("saveArtifactToDbForWorkspace"),
|
|
179
|
+
`error message should mention 'saveArtifactToDbForWorkspace', got: ${err.message}`,
|
|
180
|
+
);
|
|
181
|
+
return true;
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ─── Suite 4: saveArtifactToDbForWorkspace writes at contract.projectGsd, not milestones/ ──
|
|
188
|
+
|
|
189
|
+
describe("saveArtifactToDbForWorkspace: writes directly to .gsd root via workspace contract", () => {
|
|
190
|
+
let tmp: string;
|
|
191
|
+
|
|
192
|
+
beforeEach(() => {
|
|
193
|
+
tmp = makeProjectDir();
|
|
194
|
+
openDatabase(join(tmp, ".gsd", "gsd.db"));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
afterEach(() => {
|
|
198
|
+
closeDatabase();
|
|
199
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("root artifact lands at contract.projectGsd/path, not milestones/", async () => {
|
|
203
|
+
const ws = createWorkspace(tmp);
|
|
204
|
+
const content = "# Requirements\n";
|
|
205
|
+
const opts = {
|
|
206
|
+
path: "REQUIREMENTS.md",
|
|
207
|
+
artifact_type: "REQUIREMENTS_DRAFT",
|
|
208
|
+
content,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
await saveArtifactToDbForWorkspace(ws, opts);
|
|
212
|
+
|
|
213
|
+
const expectedPath = resolve(ws.contract.projectGsd, "REQUIREMENTS.md");
|
|
214
|
+
assert.ok(existsSync(expectedPath), "artifact written at contract.projectGsd/REQUIREMENTS.md");
|
|
215
|
+
assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
|
|
216
|
+
|
|
217
|
+
// Must not have landed inside milestones/
|
|
218
|
+
const milestonePath = join(ws.contract.projectGsd, "milestones", "", "REQUIREMENTS.md");
|
|
219
|
+
assert.ok(!existsSync(milestonePath), "artifact NOT inside milestones/empty-string/");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// GSD-2 + db-writer saveArtifactToDbByScope: workspace-contract path routing tests
|
|
2
|
+
|
|
3
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import {
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
realpathSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
|
|
16
|
+
import { createWorkspace, scopeMilestone } from "../workspace.ts";
|
|
17
|
+
import { saveArtifactToDb, saveArtifactToDbByScope } from "../db-writer.ts";
|
|
18
|
+
import { openDatabase, closeDatabase } from "../gsd-db.ts";
|
|
19
|
+
|
|
20
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
function makeProjectDir(): string {
|
|
23
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-dbwriter-scope-")));
|
|
24
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Suite 1: scope variant writes to the same canonical path as legacy ──────
|
|
29
|
+
|
|
30
|
+
describe("saveArtifactToDbByScope: path parity with legacy saveArtifactToDb", () => {
|
|
31
|
+
let tmp1: string;
|
|
32
|
+
let tmp2: string;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
tmp1 = makeProjectDir();
|
|
36
|
+
tmp2 = makeProjectDir();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
closeDatabase();
|
|
41
|
+
rmSync(tmp1, { recursive: true, force: true });
|
|
42
|
+
rmSync(tmp2, { recursive: true, force: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("scope variant writes artifact to same canonical path as legacy variant", async () => {
|
|
46
|
+
const relPath = "milestones/M001/slices/S01/tasks/T01-SUMMARY.md";
|
|
47
|
+
const content = "# T01 Summary\n\nTest content.\n";
|
|
48
|
+
const opts = {
|
|
49
|
+
path: relPath,
|
|
50
|
+
artifact_type: "SUMMARY",
|
|
51
|
+
content,
|
|
52
|
+
milestone_id: "M001",
|
|
53
|
+
slice_id: "S01",
|
|
54
|
+
task_id: "T01",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Legacy path: basePath + '.gsd' join
|
|
58
|
+
const legacyExpectedPath = resolve(tmp1, ".gsd", relPath);
|
|
59
|
+
|
|
60
|
+
// Scope path: contract.projectGsd
|
|
61
|
+
const ws = createWorkspace(tmp2);
|
|
62
|
+
const scope = scopeMilestone(ws, "M001");
|
|
63
|
+
const scopeExpectedPath = resolve(ws.contract.projectGsd, relPath);
|
|
64
|
+
|
|
65
|
+
// Both should resolve to the same relative structure
|
|
66
|
+
// (though under different temp dirs — so we compare structure, not absolute path)
|
|
67
|
+
assert.equal(
|
|
68
|
+
scopeExpectedPath,
|
|
69
|
+
resolve(ws.contract.projectGsd, relPath),
|
|
70
|
+
"scope path must be contract.projectGsd + relPath",
|
|
71
|
+
);
|
|
72
|
+
assert.equal(
|
|
73
|
+
legacyExpectedPath,
|
|
74
|
+
resolve(tmp1, ".gsd", relPath),
|
|
75
|
+
"legacy path must be basePath/.gsd + relPath",
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Open DB for tmp1 and write via legacy
|
|
79
|
+
const dbPath1 = join(tmp1, ".gsd", "gsd.db");
|
|
80
|
+
openDatabase(dbPath1);
|
|
81
|
+
await saveArtifactToDb(opts, tmp1);
|
|
82
|
+
closeDatabase();
|
|
83
|
+
|
|
84
|
+
// Open DB for tmp2 and write via scope variant
|
|
85
|
+
const dbPath2 = join(tmp2, ".gsd", "gsd.db");
|
|
86
|
+
openDatabase(dbPath2);
|
|
87
|
+
await saveArtifactToDbByScope(scope, opts);
|
|
88
|
+
closeDatabase();
|
|
89
|
+
|
|
90
|
+
// Both should have written to the correct location under their respective .gsd dirs
|
|
91
|
+
assert.ok(existsSync(legacyExpectedPath), "legacy: artifact written at basePath/.gsd/relPath");
|
|
92
|
+
assert.ok(existsSync(scopeExpectedPath), "scope: artifact written at contract.projectGsd/relPath");
|
|
93
|
+
|
|
94
|
+
// Content must match
|
|
95
|
+
assert.equal(readFileSync(legacyExpectedPath, "utf-8"), content, "legacy: content matches");
|
|
96
|
+
assert.equal(readFileSync(scopeExpectedPath, "utf-8"), content, "scope: content matches");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ─── Suite 2: scope variant uses contract.projectGsd, not a basePath join ────
|
|
101
|
+
|
|
102
|
+
describe("saveArtifactToDbByScope: uses contract.projectGsd, not hand-rolled basePath join", () => {
|
|
103
|
+
let tmp: string;
|
|
104
|
+
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
tmp = makeProjectDir();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
closeDatabase();
|
|
111
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("scope.workspace.contract.projectGsd is used as the .gsd root, not basePath/.gsd", async () => {
|
|
115
|
+
const ws = createWorkspace(tmp);
|
|
116
|
+
const scope = scopeMilestone(ws, "M001");
|
|
117
|
+
|
|
118
|
+
// The contract.projectGsd must equal the canonical join(projectRoot, '.gsd')
|
|
119
|
+
assert.equal(
|
|
120
|
+
ws.contract.projectGsd,
|
|
121
|
+
join(ws.projectRoot, ".gsd"),
|
|
122
|
+
"contract.projectGsd must equal join(projectRoot, '.gsd')",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// It must NOT be a hand-rolled resolution from an arbitrary basePath string
|
|
126
|
+
// (i.e., contract.projectGsd routes through the workspace contract)
|
|
127
|
+
assert.ok(
|
|
128
|
+
ws.contract.projectGsd.startsWith(ws.projectRoot),
|
|
129
|
+
"contract.projectGsd must be rooted at projectRoot",
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const relPath = "milestones/M001/M001-CONTEXT.md";
|
|
133
|
+
const content = "# M001 Context\n";
|
|
134
|
+
const opts = {
|
|
135
|
+
path: relPath,
|
|
136
|
+
artifact_type: "CONTEXT",
|
|
137
|
+
content,
|
|
138
|
+
milestone_id: "M001",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
openDatabase(join(tmp, ".gsd", "gsd.db"));
|
|
142
|
+
await saveArtifactToDbByScope(scope, opts);
|
|
143
|
+
|
|
144
|
+
// File must be at contract.projectGsd/relPath
|
|
145
|
+
const expectedPath = resolve(ws.contract.projectGsd, relPath);
|
|
146
|
+
assert.ok(existsSync(expectedPath), "artifact written at contract.projectGsd/relPath");
|
|
147
|
+
assert.equal(readFileSync(expectedPath, "utf-8"), content, "content matches");
|
|
148
|
+
|
|
149
|
+
// And must NOT be at some other location
|
|
150
|
+
const handRolledPath = resolve(tmp, ".gsd", relPath);
|
|
151
|
+
// Both should be the same path in project mode (they should agree)
|
|
152
|
+
assert.equal(
|
|
153
|
+
expectedPath,
|
|
154
|
+
handRolledPath,
|
|
155
|
+
"in project mode, contract.projectGsd resolves same as basePath/.gsd",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ─── Suite 3: worktree-mode scope routes to project root's .gsd/ ─────────────
|
|
161
|
+
|
|
162
|
+
describe("saveArtifactToDbByScope: worktree scope writes to project root .gsd/", () => {
|
|
163
|
+
let tmp: string;
|
|
164
|
+
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
tmp = realpathSync(mkdtempSync(join(tmpdir(), "gsd-dbwriter-wt-scope-")));
|
|
167
|
+
// Create project .gsd directory
|
|
168
|
+
mkdirSync(join(tmp, ".gsd"), { recursive: true });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
afterEach(() => {
|
|
172
|
+
closeDatabase();
|
|
173
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("worktree-mode scope: contract.projectGsd resolves to project root's .gsd/, not worktree .gsd/", async () => {
|
|
177
|
+
// Construct a worktree path inside the project's .gsd/worktrees/<MID>
|
|
178
|
+
const worktreePath = join(tmp, ".gsd", "worktrees", "M001");
|
|
179
|
+
mkdirSync(join(worktreePath, ".gsd"), { recursive: true });
|
|
180
|
+
|
|
181
|
+
const projectWs = createWorkspace(tmp);
|
|
182
|
+
const worktreeWs = createWorkspace(worktreePath);
|
|
183
|
+
|
|
184
|
+
// Both should share the same projectRoot (worktree-root resolution)
|
|
185
|
+
assert.equal(
|
|
186
|
+
worktreeWs.projectRoot,
|
|
187
|
+
projectWs.projectRoot,
|
|
188
|
+
"worktree workspace must have same projectRoot as project workspace",
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// contract.projectGsd for the worktree workspace must point to the PROJECT root's .gsd/
|
|
192
|
+
assert.equal(
|
|
193
|
+
worktreeWs.contract.projectGsd,
|
|
194
|
+
join(projectWs.projectRoot, ".gsd"),
|
|
195
|
+
"worktree contract.projectGsd must equal project root's .gsd/",
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Must NOT be the worktree-local .gsd/
|
|
199
|
+
assert.notEqual(
|
|
200
|
+
worktreeWs.contract.projectGsd,
|
|
201
|
+
join(worktreePath, ".gsd"),
|
|
202
|
+
"worktree contract.projectGsd must NOT be the worktree-local .gsd/",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Write via the worktree-mode scope
|
|
206
|
+
const scope = scopeMilestone(worktreeWs, "M001");
|
|
207
|
+
const relPath = "milestones/M001/M001-CONTEXT.md";
|
|
208
|
+
const content = "# M001 Context from worktree scope\n";
|
|
209
|
+
const opts = {
|
|
210
|
+
path: relPath,
|
|
211
|
+
artifact_type: "CONTEXT",
|
|
212
|
+
content,
|
|
213
|
+
milestone_id: "M001",
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
openDatabase(join(tmp, ".gsd", "gsd.db"));
|
|
217
|
+
await saveArtifactToDbByScope(scope, opts);
|
|
218
|
+
|
|
219
|
+
// File must land in the PROJECT root's .gsd/, not in the worktree's .gsd/
|
|
220
|
+
const projectPath = resolve(projectWs.contract.projectGsd, relPath);
|
|
221
|
+
const worktreeLocalPath = resolve(worktreePath, ".gsd", relPath);
|
|
222
|
+
|
|
223
|
+
assert.ok(existsSync(projectPath), "artifact written to project root's .gsd/");
|
|
224
|
+
assert.ok(
|
|
225
|
+
!existsSync(worktreeLocalPath),
|
|
226
|
+
"artifact must NOT be written to worktree-local .gsd/",
|
|
227
|
+
);
|
|
228
|
+
assert.equal(readFileSync(projectPath, "utf-8"), content, "content at project root matches");
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -481,7 +481,7 @@ describe('db-writer', () => {
|
|
|
481
481
|
}
|
|
482
482
|
});
|
|
483
483
|
|
|
484
|
-
test('updateRequirementInDb —
|
|
484
|
+
test('updateRequirementInDb — ignores REQUIREMENTS.md projection when DB empty', async () => {
|
|
485
485
|
const tmpDir = makeTmpDir();
|
|
486
486
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
487
487
|
openDatabase(dbPath);
|
|
@@ -515,31 +515,28 @@ describe('db-writer', () => {
|
|
|
515
515
|
].join('\n');
|
|
516
516
|
fs.writeFileSync(path.join(tmpDir, '.gsd', 'REQUIREMENTS.md'), reqContent);
|
|
517
517
|
|
|
518
|
-
// DB is empty
|
|
519
|
-
//
|
|
520
|
-
// After fix: this seeds all 3 requirements from REQUIREMENTS.md first.
|
|
518
|
+
// DB is empty. REQUIREMENTS.md is a projection and must not be imported
|
|
519
|
+
// implicitly by a runtime DB write.
|
|
521
520
|
await updateRequirementInDb('R005', {
|
|
522
521
|
status: 'validated',
|
|
523
522
|
validation: 'S02 — auth flow verified',
|
|
524
523
|
}, tmpDir);
|
|
525
524
|
|
|
526
|
-
// R005 should have the update
|
|
525
|
+
// R005 should have the requested update only; disk projection content is ignored.
|
|
527
526
|
const r005 = getRequirementById('R005');
|
|
528
527
|
assert.ok(r005, 'R005 should exist');
|
|
529
528
|
assert.equal(r005!.status, 'validated', 'status should be updated');
|
|
530
529
|
assert.equal(r005!.validation, 'S02 — auth flow verified', 'validation should be updated');
|
|
531
|
-
assert.equal(r005!.class, '
|
|
532
|
-
assert.ok(r005!.description?.includes('authentication')
|
|
533
|
-
|
|
530
|
+
assert.equal(r005!.class, '', 'class should not be imported from REQUIREMENTS.md');
|
|
531
|
+
assert.ok(!r005!.description?.includes('authentication'), 'description should not be imported');
|
|
532
|
+
assert.ok(!r005!.full_content?.includes('authentication'), 'full content should not be imported');
|
|
534
533
|
|
|
535
|
-
//
|
|
534
|
+
// Other requirements in the projection are not seeded.
|
|
536
535
|
const r007 = getRequirementById('R007');
|
|
537
|
-
assert.
|
|
538
|
-
assert.equal(r007!.status, 'active', 'R007 status should be active');
|
|
536
|
+
assert.equal(r007, null, 'R007 should not be imported from REQUIREMENTS.md');
|
|
539
537
|
|
|
540
538
|
const r001 = getRequirementById('R001');
|
|
541
|
-
assert.
|
|
542
|
-
assert.equal(r001!.status, 'validated', 'R001 status should be validated (from section heading)');
|
|
539
|
+
assert.equal(r001, null, 'R001 should not be imported from REQUIREMENTS.md');
|
|
543
540
|
} finally {
|
|
544
541
|
closeDatabase();
|
|
545
542
|
cleanupDir(tmpDir);
|
|
@@ -663,15 +660,16 @@ describe('db-writer', () => {
|
|
|
663
660
|
'disk file preserved — shrinkage guard prevented overwrite',
|
|
664
661
|
);
|
|
665
662
|
|
|
666
|
-
// DB should
|
|
663
|
+
// DB should keep the caller-provided content. The larger disk file is a
|
|
664
|
+
// stale projection, not runtime authority.
|
|
667
665
|
const adapter = _getAdapter();
|
|
668
666
|
const row = adapter!
|
|
669
667
|
.prepare('SELECT full_content FROM artifacts WHERE path = ?')
|
|
670
668
|
.get(relPath);
|
|
671
669
|
assert.deepStrictEqual(
|
|
672
670
|
row!['full_content'],
|
|
673
|
-
|
|
674
|
-
'DB stores
|
|
671
|
+
abbreviatedContent,
|
|
672
|
+
'DB stores caller-provided content instead of importing disk projection content',
|
|
675
673
|
);
|
|
676
674
|
} finally {
|
|
677
675
|
closeDatabase();
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
annotateBackgroundable,
|
|
5
|
+
getDelegationVerdict,
|
|
6
|
+
getVerdictByUnitType,
|
|
7
|
+
isBackgroundable,
|
|
8
|
+
listBackgroundableTools,
|
|
9
|
+
} from "../delegation-policy.js";
|
|
10
|
+
|
|
11
|
+
// Pin the GOOD set: changes here must come with explicit re-evaluation.
|
|
12
|
+
const EXPECTED_BACKGROUNDABLE = [
|
|
13
|
+
"gsd_execute",
|
|
14
|
+
"gsd_plan_slice",
|
|
15
|
+
"gsd_reassess_roadmap",
|
|
16
|
+
"gsd_validate_milestone",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
test("isBackgroundable returns true for the four GOOD-verdict tools", () => {
|
|
20
|
+
for (const name of EXPECTED_BACKGROUNDABLE) {
|
|
21
|
+
assert.equal(isBackgroundable(name), true, `${name} should be backgroundable`);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("isBackgroundable returns false for RISKY-verdict tools", () => {
|
|
26
|
+
for (const name of ["gsd_doctor", "gsd_plan_milestone", "gsd_replan_slice"]) {
|
|
27
|
+
assert.equal(isBackgroundable(name), false, `${name} should not be backgroundable`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("isBackgroundable returns false for NO-verdict tools", () => {
|
|
32
|
+
assert.equal(isBackgroundable("gsd_plan_task"), false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("isBackgroundable defaults to false for unknown tools (default-deny)", () => {
|
|
36
|
+
assert.equal(isBackgroundable("gsd_nonexistent_tool"), false);
|
|
37
|
+
assert.equal(isBackgroundable(""), false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("listBackgroundableTools returns exactly the four GOOD tools, sorted", () => {
|
|
41
|
+
assert.deepEqual(listBackgroundableTools(), EXPECTED_BACKGROUNDABLE);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("getDelegationVerdict resolves alias names to canonical entries", () => {
|
|
45
|
+
for (const [alias, canonical] of [
|
|
46
|
+
["gsd_milestone_validate", "gsd_validate_milestone"],
|
|
47
|
+
["gsd_roadmap_reassess", "gsd_reassess_roadmap"],
|
|
48
|
+
["gsd_slice_replan", "gsd_replan_slice"],
|
|
49
|
+
["gsd_task_plan", "gsd_plan_task"],
|
|
50
|
+
] as const) {
|
|
51
|
+
const entry = getDelegationVerdict(alias);
|
|
52
|
+
assert.ok(entry, `alias ${alias} should resolve`);
|
|
53
|
+
assert.equal(entry.toolName, canonical, `${alias} should resolve to ${canonical}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("plan_slice carries the slice-lock + await constraints", () => {
|
|
58
|
+
const entry = getDelegationVerdict("gsd_plan_slice");
|
|
59
|
+
assert.ok(entry);
|
|
60
|
+
assert.ok(entry.constraints && entry.constraints.length >= 3);
|
|
61
|
+
assert.ok(
|
|
62
|
+
entry.constraints!.some((c) => /lock the slice/i.test(c)),
|
|
63
|
+
"plan_slice must carry the slice-lock constraint",
|
|
64
|
+
);
|
|
65
|
+
assert.ok(
|
|
66
|
+
entry.constraints!.some((c) => /await background completion/i.test(c)),
|
|
67
|
+
"plan_slice must require await before downstream reads",
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("doctor carries fix-mode safety constraints", () => {
|
|
72
|
+
const entry = getDelegationVerdict("gsd_doctor");
|
|
73
|
+
assert.ok(entry);
|
|
74
|
+
assert.equal(entry.verdict, "risky");
|
|
75
|
+
assert.ok(
|
|
76
|
+
entry.constraints && entry.constraints.some((c) => /fix=false/.test(c)),
|
|
77
|
+
"doctor must restrict background runs to fix=false",
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("getVerdictByUnitType maps dispatcher unit types back to the policy", () => {
|
|
82
|
+
assert.equal(getVerdictByUnitType("plan-slice")?.toolName, "gsd_plan_slice");
|
|
83
|
+
assert.equal(getVerdictByUnitType("validate-milestone")?.toolName, "gsd_validate_milestone");
|
|
84
|
+
assert.equal(getVerdictByUnitType("reassess-roadmap")?.toolName, "gsd_reassess_roadmap");
|
|
85
|
+
assert.equal(getVerdictByUnitType("plan-milestone")?.toolName, "gsd_plan_milestone");
|
|
86
|
+
assert.equal(getVerdictByUnitType("replan-slice")?.toolName, "gsd_replan_slice");
|
|
87
|
+
assert.equal(getVerdictByUnitType("nonexistent-unit"), null);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("every entry carries a non-empty rationale so the verdict is auditable", () => {
|
|
91
|
+
for (const name of [...EXPECTED_BACKGROUNDABLE, "gsd_doctor", "gsd_plan_milestone", "gsd_replan_slice", "gsd_plan_task"]) {
|
|
92
|
+
const entry = getDelegationVerdict(name);
|
|
93
|
+
assert.ok(entry, `${name} should be in the policy`);
|
|
94
|
+
assert.ok(entry.rationale.length > 20, `${name} rationale must be substantive`);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ─── annotateBackgroundable contract pins ────────────────────────────────
|
|
99
|
+
|
|
100
|
+
test("annotateBackgroundable recomputes the verdict on every call (no internal cache)", () => {
|
|
101
|
+
// The annotator mutates in place. Repeated calls on the same object with
|
|
102
|
+
// different unit types must always reflect the latest unitType — never a
|
|
103
|
+
// stale cached value. This pins the contract documented in the JSDoc so a
|
|
104
|
+
// future "optimization" that adds memoization keyed on object identity
|
|
105
|
+
// breaks the suite instead of silently leaking a stale flag.
|
|
106
|
+
const action: { action: "dispatch"; unitType: string; backgroundable?: boolean } = {
|
|
107
|
+
action: "dispatch",
|
|
108
|
+
unitType: "plan-slice",
|
|
109
|
+
};
|
|
110
|
+
annotateBackgroundable(action);
|
|
111
|
+
assert.equal(action.backgroundable, true, "plan-slice should annotate true");
|
|
112
|
+
|
|
113
|
+
action.unitType = "plan-milestone";
|
|
114
|
+
annotateBackgroundable(action);
|
|
115
|
+
assert.equal(action.backgroundable, false, "plan-milestone (risky) should re-annotate false");
|
|
116
|
+
|
|
117
|
+
action.unitType = "validate-milestone";
|
|
118
|
+
annotateBackgroundable(action);
|
|
119
|
+
assert.equal(action.backgroundable, true, "validate-milestone should re-annotate true");
|
|
120
|
+
|
|
121
|
+
action.unitType = "complete-slice";
|
|
122
|
+
annotateBackgroundable(action);
|
|
123
|
+
assert.equal(action.backgroundable, false, "uncovered unit type should re-annotate false (default-deny)");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("annotateBackgroundable passes stop/skip actions through unchanged", () => {
|
|
127
|
+
const stop = { action: "stop" as const, reason: "x", level: "info" as const };
|
|
128
|
+
const skip = { action: "skip" as const };
|
|
129
|
+
assert.equal(annotateBackgroundable(stop), stop);
|
|
130
|
+
assert.equal(annotateBackgroundable(skip), skip);
|
|
131
|
+
assert.equal((stop as Record<string, unknown>).backgroundable, undefined);
|
|
132
|
+
assert.equal((skip as Record<string, unknown>).backgroundable, undefined);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ─── F4 latent gap pin: silent default-deny on unit types invoking GOOD tools ──
|
|
136
|
+
|
|
137
|
+
test("execute-task / reactive-execute / execute-task-simple intentionally default-deny despite gsd_execute being GOOD", () => {
|
|
138
|
+
// gsd_execute carries a GOOD verdict but no `unitType`, by design — the
|
|
139
|
+
// unit-level orchestrations wrap prompt and harness work whose safety is
|
|
140
|
+
// a separate analysis. Lifting these out of default-deny must be an
|
|
141
|
+
// explicit, audited change. This test pins the current behavior; if the
|
|
142
|
+
// policy entry gains a unitType mapping (or a unitTypes array), update
|
|
143
|
+
// both the entry and this test together.
|
|
144
|
+
for (const unitType of ["execute-task", "execute-task-simple", "reactive-execute"]) {
|
|
145
|
+
assert.equal(
|
|
146
|
+
getVerdictByUnitType(unitType),
|
|
147
|
+
null,
|
|
148
|
+
`${unitType} must remain unmapped until per-unit analysis is recorded`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
});
|