@opengsd/gsd-pi 1.8.1 → 1.9.0
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 +10 -8
- package/dist/cli.js +5 -1
- package/dist/headless-events.d.ts +2 -1
- package/dist/headless-events.js +11 -3
- package/dist/headless-milestone-readiness.d.ts +29 -0
- package/dist/headless-milestone-readiness.js +161 -0
- package/dist/headless.js +25 -2
- package/dist/print-mode-exit.d.ts +12 -0
- package/dist/print-mode-exit.js +14 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +16 -47
- package/dist/resources/extensions/browser-tools/capture.js +2 -77
- package/dist/resources/extensions/browser-tools/screenshot-constraints.js +83 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -2
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +30 -2
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +74 -19
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +35 -1
- package/dist/resources/extensions/gsd/auto.js +5 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +78 -4
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +84 -26
- package/dist/resources/extensions/gsd/command-feedback.js +16 -12
- package/dist/resources/extensions/gsd/compat/compat-marker.js +19 -2
- package/dist/resources/extensions/gsd/db/command-queue.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +51 -16
- package/dist/resources/extensions/gsd/db/writers/cascades.js +5 -1
- package/dist/resources/extensions/gsd/db/writers/status.js +13 -3
- package/dist/resources/extensions/gsd/db-base-schema.js +28 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +30 -0
- package/dist/resources/extensions/gsd/db-schema-metadata.js +4 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +4 -4
- package/dist/resources/extensions/gsd/flat-phase-migration.js +156 -21
- package/dist/resources/extensions/gsd/gsd-db.js +187 -45
- package/dist/resources/extensions/gsd/guided-flow.js +29 -10
- package/dist/resources/extensions/gsd/markdown-renderer.js +16 -2
- package/dist/resources/extensions/gsd/md-importer.js +102 -14
- package/dist/resources/extensions/gsd/migrate/command.js +19 -162
- package/dist/resources/extensions/gsd/migrate/execution.js +98 -0
- package/dist/resources/extensions/gsd/migrate/plan.js +49 -0
- package/dist/resources/extensions/gsd/migrate/planning-writer.js +12 -17
- package/dist/resources/extensions/gsd/migrate/presentation.js +64 -0
- package/dist/resources/extensions/gsd/parallel-eligibility.js +5 -4
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +28 -43
- package/dist/resources/extensions/gsd/paths.js +4 -1
- package/dist/resources/extensions/gsd/phase-anchor.js +7 -2
- package/dist/resources/extensions/gsd/post-execution-checks.js +4 -4
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +26 -1
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +30 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +27 -13
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +13 -3
- package/dist/resources/extensions/gsd/stop-notice.js +15 -1
- package/dist/resources/extensions/gsd/sync-lock.js +13 -7
- package/dist/resources/extensions/gsd/tools/complete-task.js +82 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +13 -15
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +18 -15
- package/dist/resources/extensions/gsd/tools/replan-task.js +128 -0
- package/dist/resources/extensions/gsd/tools/rework-brief.js +74 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +81 -0
- package/dist/resources/extensions/gsd/unit-registry.js +4 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +19 -3
- package/dist/resources/extensions/gsd/worktree-state-projection.js +6 -0
- package/dist/resources/extensions/mac-tools/index.js +31 -9
- 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 +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/8357.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/static/css/3b1ceb7abfb9a230.css +1 -0
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/package.json +0 -1
- package/dist/web-mode.js +31 -3
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +12 -10
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/cloud-config.d.ts +1 -1
- package/packages/daemon/dist/cloud-config.d.ts.map +1 -1
- package/packages/daemon/dist/cloud-config.js +5 -1
- package/packages/daemon/dist/cloud-config.js.map +1 -1
- package/packages/daemon/dist/cloud-config.test.js +51 -1
- package/packages/daemon/dist/cloud-config.test.js.map +1 -1
- package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -1
- package/packages/daemon/dist/local-tool-executor.js +2 -0
- package/packages/daemon/dist/local-tool-executor.js.map +1 -1
- package/packages/daemon/package.json +6 -6
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/dist/cli.d.ts +5 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cli.js +160 -0
- package/packages/gsd-cloud/dist/cli.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.d.ts +19 -0
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.js +231 -0
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/cloud-config.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-config.test.js +118 -0
- package/packages/gsd-cloud/dist/cloud-config.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js +37 -0
- package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +35 -0
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.js +258 -0
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.js +88 -0
- package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cloud-token.d.ts +3 -0
- package/packages/gsd-cloud/dist/cloud-token.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/cloud-token.js +37 -0
- package/packages/gsd-cloud/dist/cloud-token.js.map +1 -0
- package/packages/gsd-cloud/dist/config.d.ts +17 -0
- package/packages/gsd-cloud/dist/config.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/config.js +146 -0
- package/packages/gsd-cloud/dist/config.js.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.d.ts +24 -0
- package/packages/gsd-cloud/dist/device-flow.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.js +128 -0
- package/packages/gsd-cloud/dist/device-flow.js.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/device-flow.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/device-flow.test.js +155 -0
- package/packages/gsd-cloud/dist/device-flow.test.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.d.ts +6 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.js +15 -0
- package/packages/gsd-cloud/dist/executors/claude-executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.d.ts +6 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.js +15 -0
- package/packages/gsd-cloud/dist/executors/codex-executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/executor.d.ts +28 -0
- package/packages/gsd-cloud/dist/executors/executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/executor.js +11 -0
- package/packages/gsd-cloud/dist/executors/executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts +34 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js +179 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js +44 -0
- package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/index.d.ts +17 -0
- package/packages/gsd-cloud/dist/executors/index.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/index.js +29 -0
- package/packages/gsd-cloud/dist/executors/index.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts +36 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js +176 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js +34 -0
- package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js.map +1 -0
- package/packages/gsd-cloud/dist/index.d.ts +7 -0
- package/packages/gsd-cloud/dist/index.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/index.js +8 -0
- package/packages/gsd-cloud/dist/index.js.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.d.ts +16 -0
- package/packages/gsd-cloud/dist/inject-gateway.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.js +29 -0
- package/packages/gsd-cloud/dist/inject-gateway.js.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.js +41 -0
- package/packages/gsd-cloud/dist/inject-gateway.test.js.map +1 -0
- package/packages/gsd-cloud/dist/logger.d.ts +25 -0
- package/packages/gsd-cloud/dist/logger.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/logger.js +72 -0
- package/packages/gsd-cloud/dist/logger.js.map +1 -0
- package/packages/gsd-cloud/dist/types.d.ts +51 -0
- package/packages/gsd-cloud/dist/types.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/types.js +9 -0
- package/packages/gsd-cloud/dist/types.js.map +1 -0
- package/packages/gsd-cloud/package.json +3 -3
- package/packages/mcp-server/README.md +8 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +64 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +60 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +54 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +1 -0
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +19 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +16 -47
- package/src/resources/extensions/browser-tools/capture.ts +2 -86
- package/src/resources/extensions/browser-tools/screenshot-constraints.ts +94 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +108 -0
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +30 -2
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +86 -19
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +38 -1
- package/src/resources/extensions/gsd/auto.ts +5 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +91 -4
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -24
- package/src/resources/extensions/gsd/command-feedback.ts +19 -12
- package/src/resources/extensions/gsd/compat/compat-marker.ts +21 -4
- package/src/resources/extensions/gsd/db/command-queue.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +53 -17
- package/src/resources/extensions/gsd/db/writers/cascades.ts +5 -1
- package/src/resources/extensions/gsd/db/writers/status.ts +25 -8
- package/src/resources/extensions/gsd/db-base-schema.ts +30 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +32 -0
- package/src/resources/extensions/gsd/db-schema-metadata.ts +4 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +4 -4
- package/src/resources/extensions/gsd/flat-phase-migration.ts +160 -19
- package/src/resources/extensions/gsd/gsd-db.ts +255 -52
- package/src/resources/extensions/gsd/guided-flow.ts +39 -10
- package/src/resources/extensions/gsd/markdown-renderer.ts +23 -7
- package/src/resources/extensions/gsd/md-importer.ts +109 -11
- package/src/resources/extensions/gsd/migrate/command.ts +21 -227
- package/src/resources/extensions/gsd/migrate/execution.ts +136 -0
- package/src/resources/extensions/gsd/migrate/plan.ts +101 -0
- package/src/resources/extensions/gsd/migrate/planning-writer.ts +12 -20
- package/src/resources/extensions/gsd/migrate/presentation.ts +83 -0
- package/src/resources/extensions/gsd/parallel-eligibility.ts +5 -4
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +37 -40
- package/src/resources/extensions/gsd/paths.ts +4 -1
- package/src/resources/extensions/gsd/phase-anchor.ts +8 -2
- package/src/resources/extensions/gsd/post-execution-checks.ts +4 -4
- package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +28 -1
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +31 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +30 -13
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +16 -3
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +2 -2
- package/src/resources/extensions/gsd/stop-notice.ts +18 -1
- package/src/resources/extensions/gsd/sync-lock.ts +14 -6
- package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +96 -1
- package/src/resources/extensions/gsd/tests/command-feedback.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +286 -0
- package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/external-edit-scoped-import.test.ts +262 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +202 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/import-slice-milestone-completion-preserve.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/migrate-plan.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/migrate-presentation.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/migrate-safety-audit.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/parallel-eligibility-file-overlap.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +32 -4
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +26 -23
- package/src/resources/extensions/gsd/tests/planning-writer.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/replan-task-handler.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/rework-brief.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/skip-slice-cascades-tasks.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +93 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/workflow-migration-gates.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +52 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +120 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +14 -16
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +18 -17
- package/src/resources/extensions/gsd/tools/replan-task.ts +160 -0
- package/src/resources/extensions/gsd/tools/rework-brief.ts +91 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +92 -0
- package/src/resources/extensions/gsd/types.ts +11 -0
- package/src/resources/extensions/gsd/unit-registry.ts +4 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +20 -3
- package/src/resources/extensions/gsd/worktree-state-projection.ts +12 -0
- package/src/resources/extensions/mac-tools/index.ts +52 -9
- package/src/resources/extensions/mac-tools/tests/mac-screenshot-payload.test.cjs +70 -0
- package/dist/web/standalone/.next/static/css/18c2fdf261bfc1b7.css +0 -1
- /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → BE5XoklToI6lZjRzpXih8}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → BE5XoklToI6lZjRzpXih8}/_ssgManifest.js +0 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
insertVerificationEvidence,
|
|
18
18
|
insertGateRow,
|
|
19
19
|
getGateResults,
|
|
20
|
+
saveReworkBrief,
|
|
20
21
|
updateMilestoneStatus,
|
|
21
22
|
updateSliceStatus,
|
|
22
23
|
SCHEMA_VERSION,
|
|
@@ -507,6 +508,77 @@ console.log('\n=== complete-task: disabled hard-blocker escalation rolls back co
|
|
|
507
508
|
cleanup(dbPath);
|
|
508
509
|
}
|
|
509
510
|
|
|
511
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
512
|
+
// complete-task: rollback reverts applied rework resolutions
|
|
513
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
514
|
+
|
|
515
|
+
console.log('\n=== complete-task: rollback reverts applied rework resolutions ===');
|
|
516
|
+
{
|
|
517
|
+
const dbPath = tempDbPath();
|
|
518
|
+
openDatabase(dbPath);
|
|
519
|
+
|
|
520
|
+
const { basePath } = createTempProject();
|
|
521
|
+
writeProjectPreferences(basePath, 'phases:\n mid_execution_escalation: false\n');
|
|
522
|
+
|
|
523
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
524
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
525
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Test task', status: 'pending' });
|
|
526
|
+
saveReworkBrief({
|
|
527
|
+
briefId: 'RB-001',
|
|
528
|
+
milestoneId: 'M001',
|
|
529
|
+
sliceId: 'S01',
|
|
530
|
+
taskId: 'T01',
|
|
531
|
+
findings: [{
|
|
532
|
+
findingId: 'F1',
|
|
533
|
+
severity: 'blocking',
|
|
534
|
+
description: 'Compile regression',
|
|
535
|
+
requiredFix: 'Fix compile error',
|
|
536
|
+
verificationCommands: ['pnpm run typecheck:extensions'],
|
|
537
|
+
}],
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// Completion resolves the blocking finding but then rolls back because the
|
|
541
|
+
// hard-blocker escalation is disabled. The rollback must also revert the
|
|
542
|
+
// finding. Otherwise a retry would see no unresolved blocking findings and
|
|
543
|
+
// silently skip the blocking gate.
|
|
544
|
+
const result = await withWorkingDirectory(basePath, () => handleCompleteTask({
|
|
545
|
+
...makeValidParams(),
|
|
546
|
+
blockerDiscovered: true,
|
|
547
|
+
reworkResolution: [{
|
|
548
|
+
findingId: 'F1',
|
|
549
|
+
status: 'resolved',
|
|
550
|
+
evidence: 'Fixed compile error and reran pnpm run typecheck:extensions.',
|
|
551
|
+
}],
|
|
552
|
+
escalation: {
|
|
553
|
+
question: 'Should execution pause for the hard blocker?',
|
|
554
|
+
options: makeEscalationOptions(),
|
|
555
|
+
recommendation: 'pause',
|
|
556
|
+
recommendationRationale: 'The blocker should not be silently advanced.',
|
|
557
|
+
continueWithDefault: false,
|
|
558
|
+
},
|
|
559
|
+
}, basePath));
|
|
560
|
+
|
|
561
|
+
assertTrue('error' in result, 'completion should roll back when hard-blocker escalation is disabled');
|
|
562
|
+
assertEq(getTask('M001', 'S01', 'T01')?.status, 'pending', 'task status should roll back to pending');
|
|
563
|
+
|
|
564
|
+
const finding = _getAdapter()!.prepare(
|
|
565
|
+
"SELECT status, evidence, decision_ref FROM rework_brief_findings WHERE brief_id = 'RB-001' AND finding_id = 'F1'"
|
|
566
|
+
).get() as { status: string; evidence: string; decision_ref: string };
|
|
567
|
+
assertEq(finding.status, 'pending', 'rework finding should revert to pending after rollback');
|
|
568
|
+
assertEq(finding.evidence, '', 'rework finding evidence should be cleared after rollback');
|
|
569
|
+
|
|
570
|
+
// Retry-safety invariant: the blocking gate must fire again because the
|
|
571
|
+
// finding is unresolved once more.
|
|
572
|
+
const retry = await withWorkingDirectory(basePath, () => handleCompleteTask(makeValidParams(), basePath));
|
|
573
|
+
assertTrue('error' in retry, 'retry without reworkResolution should be blocked again');
|
|
574
|
+
if ('error' in retry) {
|
|
575
|
+
assertMatch(retry.error, /unresolved blocking rework finding/i, 'retry should re-trigger the blocking gate');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
cleanupDir(basePath);
|
|
579
|
+
cleanup(dbPath);
|
|
580
|
+
}
|
|
581
|
+
|
|
510
582
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
511
583
|
// complete-task: soft escalation with mid-execution escalation disabled
|
|
512
584
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -838,6 +910,220 @@ console.log('\n=== complete-task: closes Q5/Q6/Q7 gates (pass vs omitted) ===');
|
|
|
838
910
|
cleanup(dbPath);
|
|
839
911
|
}
|
|
840
912
|
|
|
913
|
+
|
|
914
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
915
|
+
// complete-task: unresolved blocking rework findings reject completion
|
|
916
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
917
|
+
|
|
918
|
+
console.log('\n=== complete-task: rejects unresolved blocking rework findings ===');
|
|
919
|
+
{
|
|
920
|
+
const dbPath = tempDbPath();
|
|
921
|
+
openDatabase(dbPath);
|
|
922
|
+
const { basePath } = createTempProject();
|
|
923
|
+
|
|
924
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
925
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
926
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Test task', status: 'pending' });
|
|
927
|
+
|
|
928
|
+
saveReworkBrief({
|
|
929
|
+
briefId: 'RB-001',
|
|
930
|
+
milestoneId: 'M001',
|
|
931
|
+
sliceId: 'S01',
|
|
932
|
+
taskId: 'T01',
|
|
933
|
+
findings: [{
|
|
934
|
+
findingId: 'F1',
|
|
935
|
+
severity: 'blocking',
|
|
936
|
+
description: 'Compile regression',
|
|
937
|
+
requiredFix: 'Fix compile error',
|
|
938
|
+
verificationCommands: ['pnpm run typecheck:extensions'],
|
|
939
|
+
}],
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
const result = await handleCompleteTask(makeValidParams(), basePath);
|
|
943
|
+
assertTrue('error' in result, 'completion should reject unresolved blocking rework findings');
|
|
944
|
+
if ('error' in result) {
|
|
945
|
+
assertMatch(result.error, /unresolved blocking rework finding/i, 'error should explain unresolved rework');
|
|
946
|
+
assertMatch(result.error, /F1/, 'error should name the unresolved finding id');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
cleanupDir(basePath);
|
|
950
|
+
cleanup(dbPath);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
console.log('\n=== complete-task: accepts resolved blocking rework evidence ===');
|
|
955
|
+
{
|
|
956
|
+
const dbPath = tempDbPath();
|
|
957
|
+
openDatabase(dbPath);
|
|
958
|
+
const { basePath } = createTempProject();
|
|
959
|
+
|
|
960
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
961
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
962
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Test task', status: 'pending' });
|
|
963
|
+
saveReworkBrief({
|
|
964
|
+
briefId: 'RB-001',
|
|
965
|
+
milestoneId: 'M001',
|
|
966
|
+
sliceId: 'S01',
|
|
967
|
+
taskId: 'T01',
|
|
968
|
+
findings: [{
|
|
969
|
+
findingId: 'F1',
|
|
970
|
+
severity: 'blocking',
|
|
971
|
+
description: 'Compile regression',
|
|
972
|
+
requiredFix: 'Fix compile error',
|
|
973
|
+
verificationCommands: ['pnpm run typecheck:extensions'],
|
|
974
|
+
}],
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
const result = await handleCompleteTask({
|
|
978
|
+
...makeValidParams(),
|
|
979
|
+
reworkResolution: [{
|
|
980
|
+
findingId: 'F1',
|
|
981
|
+
status: 'resolved',
|
|
982
|
+
evidence: 'Fixed compile error and reran pnpm run typecheck:extensions.',
|
|
983
|
+
}],
|
|
984
|
+
}, basePath);
|
|
985
|
+
|
|
986
|
+
assertTrue(!('error' in result), 'completion should accept resolved blocking rework evidence');
|
|
987
|
+
assertEq(getTask('M001', 'S01', 'T01')?.status, 'complete', 'task should complete after resolving rework');
|
|
988
|
+
const finding = _getAdapter()!.prepare("SELECT status, evidence FROM rework_brief_findings WHERE brief_id = 'RB-001' AND finding_id = 'F1'").get() as { status: string; evidence: string };
|
|
989
|
+
assertEq(finding.status, 'resolved', 'finding status should be updated');
|
|
990
|
+
assertMatch(finding.evidence, /Fixed compile error/, 'resolution evidence should be persisted');
|
|
991
|
+
|
|
992
|
+
cleanupDir(basePath);
|
|
993
|
+
cleanup(dbPath);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
console.log('\n=== complete-task: accepts deferred blocking rework with decision reference ===');
|
|
997
|
+
{
|
|
998
|
+
const dbPath = tempDbPath();
|
|
999
|
+
openDatabase(dbPath);
|
|
1000
|
+
const { basePath } = createTempProject();
|
|
1001
|
+
|
|
1002
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
1003
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
1004
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Test task', status: 'pending' });
|
|
1005
|
+
saveReworkBrief({
|
|
1006
|
+
briefId: 'RB-001',
|
|
1007
|
+
milestoneId: 'M001',
|
|
1008
|
+
sliceId: 'S01',
|
|
1009
|
+
taskId: 'T01',
|
|
1010
|
+
findings: [{
|
|
1011
|
+
findingId: 'F1',
|
|
1012
|
+
severity: 'blocking',
|
|
1013
|
+
description: 'Compile regression',
|
|
1014
|
+
requiredFix: 'Fix compile error',
|
|
1015
|
+
verificationCommands: ['pnpm run typecheck:extensions'],
|
|
1016
|
+
}],
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
const result = await handleCompleteTask({
|
|
1020
|
+
...makeValidParams(),
|
|
1021
|
+
reworkResolution: [{
|
|
1022
|
+
findingId: 'F1',
|
|
1023
|
+
status: 'deferred-with-override',
|
|
1024
|
+
evidence: 'Maintainer accepted temporary deferral.',
|
|
1025
|
+
decisionRef: 'DEC-2026-07-07-rework-deferral',
|
|
1026
|
+
}],
|
|
1027
|
+
}, basePath);
|
|
1028
|
+
|
|
1029
|
+
assertTrue(!('error' in result), 'completion should accept deferred blocking rework with decision reference');
|
|
1030
|
+
const finding = _getAdapter()!.prepare("SELECT status, decision_ref FROM rework_brief_findings WHERE brief_id = 'RB-001' AND finding_id = 'F1'").get() as { status: string; decision_ref: string };
|
|
1031
|
+
assertEq(finding.status, 'deferred-with-override', 'finding status should record the override');
|
|
1032
|
+
assertEq(finding.decision_ref, 'DEC-2026-07-07-rework-deferral', 'decision reference should be persisted');
|
|
1033
|
+
|
|
1034
|
+
cleanupDir(basePath);
|
|
1035
|
+
cleanup(dbPath);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
console.log('\n=== complete-task: invalid duplicate rework resolution cannot overwrite a valid one ===');
|
|
1039
|
+
{
|
|
1040
|
+
const dbPath = tempDbPath();
|
|
1041
|
+
openDatabase(dbPath);
|
|
1042
|
+
const { basePath } = createTempProject();
|
|
1043
|
+
|
|
1044
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
1045
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
1046
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Test task', status: 'pending' });
|
|
1047
|
+
saveReworkBrief({
|
|
1048
|
+
briefId: 'RB-001',
|
|
1049
|
+
milestoneId: 'M001',
|
|
1050
|
+
sliceId: 'S01',
|
|
1051
|
+
taskId: 'T01',
|
|
1052
|
+
findings: [{
|
|
1053
|
+
findingId: 'F1',
|
|
1054
|
+
severity: 'blocking',
|
|
1055
|
+
description: 'Compile regression',
|
|
1056
|
+
requiredFix: 'Fix compile error',
|
|
1057
|
+
verificationCommands: ['pnpm run typecheck:extensions'],
|
|
1058
|
+
}],
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
// The guard admits F1 because a satisfying entry exists, but a later
|
|
1062
|
+
// non-satisfying duplicate for the same finding must NOT overwrite the valid
|
|
1063
|
+
// resolution and leave F1 resolved with no evidence.
|
|
1064
|
+
const result = await handleCompleteTask({
|
|
1065
|
+
...makeValidParams(),
|
|
1066
|
+
reworkResolution: [
|
|
1067
|
+
{ findingId: 'F1', status: 'resolved', evidence: 'Fixed compile error and reran pnpm run typecheck:extensions.' },
|
|
1068
|
+
{ findingId: 'F1', status: 'resolved', evidence: '' },
|
|
1069
|
+
],
|
|
1070
|
+
}, basePath);
|
|
1071
|
+
|
|
1072
|
+
assertTrue(!('error' in result), 'completion should succeed on the valid resolution');
|
|
1073
|
+
const finding = _getAdapter()!.prepare(
|
|
1074
|
+
"SELECT status, evidence FROM rework_brief_findings WHERE brief_id = 'RB-001' AND finding_id = 'F1'"
|
|
1075
|
+
).get() as { status: string; evidence: string };
|
|
1076
|
+
assertEq(finding.status, 'resolved', 'finding should remain resolved');
|
|
1077
|
+
assertMatch(finding.evidence, /Fixed compile error/, 'valid evidence must survive the invalid duplicate');
|
|
1078
|
+
|
|
1079
|
+
cleanupDir(basePath);
|
|
1080
|
+
cleanup(dbPath);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
console.log('\n=== complete-task: invalid deferred duplicate cannot strip decisionRef ===');
|
|
1084
|
+
{
|
|
1085
|
+
const dbPath = tempDbPath();
|
|
1086
|
+
openDatabase(dbPath);
|
|
1087
|
+
const { basePath } = createTempProject();
|
|
1088
|
+
|
|
1089
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
1090
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
1091
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Test task', status: 'pending' });
|
|
1092
|
+
saveReworkBrief({
|
|
1093
|
+
briefId: 'RB-001',
|
|
1094
|
+
milestoneId: 'M001',
|
|
1095
|
+
sliceId: 'S01',
|
|
1096
|
+
taskId: 'T01',
|
|
1097
|
+
findings: [{
|
|
1098
|
+
findingId: 'F1',
|
|
1099
|
+
severity: 'blocking',
|
|
1100
|
+
description: 'Compile regression',
|
|
1101
|
+
requiredFix: 'Fix compile error',
|
|
1102
|
+
verificationCommands: ['pnpm run typecheck:extensions'],
|
|
1103
|
+
}],
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
// A deferred-with-override duplicate that omits decisionRef is non-satisfying
|
|
1107
|
+
// and must not overwrite the valid deferral.
|
|
1108
|
+
const result = await handleCompleteTask({
|
|
1109
|
+
...makeValidParams(),
|
|
1110
|
+
reworkResolution: [
|
|
1111
|
+
{ findingId: 'F1', status: 'deferred-with-override', evidence: 'Maintainer accepted temporary deferral.', decisionRef: 'DEC-2026-07-07-rework-deferral' },
|
|
1112
|
+
{ findingId: 'F1', status: 'deferred-with-override', evidence: 'Maintainer accepted temporary deferral.' },
|
|
1113
|
+
],
|
|
1114
|
+
}, basePath);
|
|
1115
|
+
|
|
1116
|
+
assertTrue(!('error' in result), 'completion should succeed on the valid deferral');
|
|
1117
|
+
const finding = _getAdapter()!.prepare(
|
|
1118
|
+
"SELECT status, decision_ref FROM rework_brief_findings WHERE brief_id = 'RB-001' AND finding_id = 'F1'"
|
|
1119
|
+
).get() as { status: string; decision_ref: string };
|
|
1120
|
+
assertEq(finding.status, 'deferred-with-override', 'finding should remain deferred-with-override');
|
|
1121
|
+
assertEq(finding.decision_ref, 'DEC-2026-07-07-rework-deferral', 'valid decisionRef must survive the invalid duplicate');
|
|
1122
|
+
|
|
1123
|
+
cleanupDir(basePath);
|
|
1124
|
+
cleanup(dbPath);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
841
1127
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
842
1128
|
|
|
843
1129
|
report();
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// GSD Extension - Migration guard tests (plan 026).
|
|
2
|
+
// Covers the newer-DB refusal in migrateSchema, whole-chain rollback atomicity
|
|
3
|
+
// via the _setMigrationFaultForTest seam, the legacy-data guard in initSchema,
|
|
4
|
+
// and getCurrentSchemaVersion's empty-table NULL handling.
|
|
5
|
+
|
|
6
|
+
import { describe, test } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
openDatabase,
|
|
15
|
+
closeDatabase,
|
|
16
|
+
isDbAvailable,
|
|
17
|
+
SCHEMA_VERSION,
|
|
18
|
+
_setMigrationFaultForTest,
|
|
19
|
+
} from '../gsd-db.ts';
|
|
20
|
+
import { createDbAdapter } from '../db-adapter.ts';
|
|
21
|
+
import { getCurrentSchemaVersion } from '../db-schema-metadata.ts';
|
|
22
|
+
|
|
23
|
+
const _require = createRequire(import.meta.url);
|
|
24
|
+
|
|
25
|
+
function tempDbPath(): string {
|
|
26
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-migrate-guards-'));
|
|
27
|
+
return path.join(dir, 'gsd.db');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cleanup(dbPath: string): void {
|
|
31
|
+
closeDatabase();
|
|
32
|
+
fs.rmSync(path.dirname(dbPath), { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface RawDb {
|
|
36
|
+
exec(sql: string): void;
|
|
37
|
+
prepare(sql: string): { get(...args: unknown[]): Record<string, unknown> | undefined; all(...args: unknown[]): Array<Record<string, unknown>> };
|
|
38
|
+
close(): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function openRawSqliteForTest(dbPath: string): RawDb {
|
|
42
|
+
try {
|
|
43
|
+
const mod = _require('node:sqlite') as { DatabaseSync: new (path: string) => RawDb };
|
|
44
|
+
return new mod.DatabaseSync(dbPath);
|
|
45
|
+
} catch {
|
|
46
|
+
type SqliteCtor = new (path: string) => RawDb;
|
|
47
|
+
const mod = _require('better-sqlite3') as SqliteCtor | { default: SqliteCtor };
|
|
48
|
+
const DatabaseCtor: SqliteCtor = typeof mod === 'function' ? mod : mod.default;
|
|
49
|
+
return new DatabaseCtor(dbPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function rawColumnNames(raw: RawDb, table: string): string[] {
|
|
54
|
+
return raw.prepare(`PRAGMA table_info(${table})`).all().map((r) => r['name'] as string);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rawHasTable(raw: RawDb, table: string): boolean {
|
|
58
|
+
const row = raw.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table);
|
|
59
|
+
return Boolean(row);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function rawMaxVersion(raw: RawDb): number | null {
|
|
63
|
+
const row = raw.prepare('SELECT MAX(version) as v FROM schema_version').get();
|
|
64
|
+
return (row?.['v'] as number | null) ?? null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe('db-engine migrate guards', () => {
|
|
68
|
+
test('getCurrentSchemaVersion returns 0 on an empty schema_version table', () => {
|
|
69
|
+
const sqlite = _require('node:sqlite') as { DatabaseSync: new (path: string) => unknown };
|
|
70
|
+
const adapter = createDbAdapter(new sqlite.DatabaseSync(':memory:'));
|
|
71
|
+
try {
|
|
72
|
+
adapter.exec('CREATE TABLE schema_version (version INTEGER NOT NULL, applied_at TEXT NOT NULL)');
|
|
73
|
+
assert.equal(getCurrentSchemaVersion(adapter), 0);
|
|
74
|
+
} finally {
|
|
75
|
+
adapter.close();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('refuses to open a DB whose schema is newer than this binary', () => {
|
|
80
|
+
const dbPath = tempDbPath();
|
|
81
|
+
try {
|
|
82
|
+
assert.ok(openDatabase(dbPath), 'fresh open should succeed');
|
|
83
|
+
closeDatabase();
|
|
84
|
+
|
|
85
|
+
const raw = openRawSqliteForTest(dbPath);
|
|
86
|
+
raw.exec(
|
|
87
|
+
`INSERT INTO schema_version (version, applied_at) VALUES (${SCHEMA_VERSION + 1}, '2026-01-01T00:00:00Z')`,
|
|
88
|
+
);
|
|
89
|
+
raw.close();
|
|
90
|
+
|
|
91
|
+
assert.throws(() => openDatabase(dbPath), /newer than/);
|
|
92
|
+
assert.equal(isDbAvailable(), false, 'no connection may stay open on refusal');
|
|
93
|
+
} finally {
|
|
94
|
+
cleanup(dbPath);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('a mid-migration failure rolls back to the starting schema version', () => {
|
|
99
|
+
const dbPath = tempDbPath();
|
|
100
|
+
try {
|
|
101
|
+
assert.ok(openDatabase(dbPath), 'fresh open should succeed');
|
|
102
|
+
closeDatabase();
|
|
103
|
+
|
|
104
|
+
// Rewind the DB to v(SCHEMA_VERSION-1): stamp the prior version and drop
|
|
105
|
+
// the objects the last migration adds, so the re-run has real work to redo.
|
|
106
|
+
const startVersion = SCHEMA_VERSION - 2;
|
|
107
|
+
const raw = openRawSqliteForTest(dbPath);
|
|
108
|
+
raw.exec('DELETE FROM schema_version');
|
|
109
|
+
raw.exec(`INSERT INTO schema_version (version, applied_at) VALUES (${startVersion}, '2026-01-01T00:00:00Z')`);
|
|
110
|
+
raw.exec('ALTER TABLE slices DROP COLUMN target_repositories');
|
|
111
|
+
raw.exec('ALTER TABLE tasks DROP COLUMN target_repositories');
|
|
112
|
+
raw.close();
|
|
113
|
+
|
|
114
|
+
_setMigrationFaultForTest(true);
|
|
115
|
+
try {
|
|
116
|
+
assert.throws(() => openDatabase(dbPath), /migration fault injected/);
|
|
117
|
+
} finally {
|
|
118
|
+
_setMigrationFaultForTest(false);
|
|
119
|
+
}
|
|
120
|
+
assert.equal(isDbAvailable(), false, 'no connection may stay open on migration failure');
|
|
121
|
+
|
|
122
|
+
const inspect = openRawSqliteForTest(dbPath);
|
|
123
|
+
try {
|
|
124
|
+
assert.equal(rawMaxVersion(inspect), startVersion, 'version must roll back to the starting version');
|
|
125
|
+
assert.ok(
|
|
126
|
+
!rawColumnNames(inspect, 'slices').includes('target_repositories'),
|
|
127
|
+
'column added by the failed migration must be rolled back',
|
|
128
|
+
);
|
|
129
|
+
} finally {
|
|
130
|
+
inspect.close();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The DB is intact — a clean re-open migrates to the current version.
|
|
134
|
+
assert.ok(openDatabase(dbPath), 're-open without the fault should succeed');
|
|
135
|
+
closeDatabase();
|
|
136
|
+
const after = openRawSqliteForTest(dbPath);
|
|
137
|
+
try {
|
|
138
|
+
assert.equal(rawMaxVersion(after), SCHEMA_VERSION);
|
|
139
|
+
assert.ok(rawColumnNames(after, 'slices').includes('target_repositories'));
|
|
140
|
+
assert.equal(rawHasTable(after, 'rework_briefs'), true);
|
|
141
|
+
assert.equal(rawHasTable(after, 'rework_brief_findings'), true);
|
|
142
|
+
} finally {
|
|
143
|
+
after.close();
|
|
144
|
+
}
|
|
145
|
+
} finally {
|
|
146
|
+
cleanup(dbPath);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('a DB with data but no version rows migrates from v1 instead of being stamped current', () => {
|
|
151
|
+
const dbPath = tempDbPath();
|
|
152
|
+
try {
|
|
153
|
+
assert.ok(openDatabase(dbPath), 'fresh open should succeed');
|
|
154
|
+
closeDatabase();
|
|
155
|
+
|
|
156
|
+
// Simulate a legacy DB: user data present, version table empty, and a
|
|
157
|
+
// migration-added column missing so we can observe the chain actually run.
|
|
158
|
+
const raw = openRawSqliteForTest(dbPath);
|
|
159
|
+
raw.exec("INSERT INTO milestones (id, title) VALUES ('M001', 'Legacy milestone')");
|
|
160
|
+
raw.exec('DELETE FROM schema_version');
|
|
161
|
+
raw.exec('ALTER TABLE milestones DROP COLUMN vision');
|
|
162
|
+
raw.close();
|
|
163
|
+
|
|
164
|
+
assert.ok(openDatabase(dbPath), 'legacy DB open should succeed');
|
|
165
|
+
closeDatabase();
|
|
166
|
+
|
|
167
|
+
const inspect = openRawSqliteForTest(dbPath);
|
|
168
|
+
try {
|
|
169
|
+
const versions = inspect.prepare('SELECT version FROM schema_version ORDER BY version').all()
|
|
170
|
+
.map((r) => r['version'] as number);
|
|
171
|
+
assert.equal(versions[0], 1, 'baseline v1 must be recorded, not a fresh-install stamp');
|
|
172
|
+
assert.equal(versions[versions.length - 1], SCHEMA_VERSION, 'migration chain must reach the current version');
|
|
173
|
+
assert.ok(
|
|
174
|
+
rawColumnNames(inspect, 'milestones').includes('vision'),
|
|
175
|
+
'migration-added column must be restored by the chain',
|
|
176
|
+
);
|
|
177
|
+
const row = inspect.prepare('SELECT count(*) as cnt FROM milestones').get();
|
|
178
|
+
assert.equal(row?.['cnt'], 1, 'existing data must survive');
|
|
179
|
+
} finally {
|
|
180
|
+
inspect.close();
|
|
181
|
+
}
|
|
182
|
+
} finally {
|
|
183
|
+
cleanup(dbPath);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -7,6 +7,8 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
|
|
9
9
|
import { deriveState, invalidateStateCache, _deriveStateImpl, deriveStateFromDb, isGhostMilestone } from '../state.ts';
|
|
10
|
+
import { readCachedDeriveState, writeCachedDeriveState } from '../state/derive/cache.ts';
|
|
11
|
+
import type { GSDState } from '../types.ts';
|
|
10
12
|
import {
|
|
11
13
|
openDatabase,
|
|
12
14
|
closeDatabase,
|
|
@@ -582,6 +584,33 @@ describe('derive-state-db', async () => {
|
|
|
582
584
|
}
|
|
583
585
|
});
|
|
584
586
|
|
|
587
|
+
// ─── Test 7b: lock flip must not serve a basePath-keyed cache hit ─────
|
|
588
|
+
// The derive cache is keyed on basePath only, but deriveStateFromDb filters
|
|
589
|
+
// by GSD_MILESTONE_LOCK. auto.ts's capture/restoreMilestoneLockEnv call
|
|
590
|
+
// invalidateStateCache() on every flip; this guards that the cache is
|
|
591
|
+
// genuinely lock-blind (so that invalidate call is load-bearing, not
|
|
592
|
+
// redundant) and that invalidation defeats the stale read.
|
|
593
|
+
test('derive-state-db: a milestone-lock flip needs explicit cache invalidation', () => {
|
|
594
|
+
const prevLock = process.env.GSD_MILESTONE_LOCK;
|
|
595
|
+
try {
|
|
596
|
+
const base = '/tmp/gsd-lock-flip-fixture';
|
|
597
|
+
const stateA = { phase: 'planning' } as unknown as GSDState;
|
|
598
|
+
process.env.GSD_MILESTONE_LOCK = 'M001';
|
|
599
|
+
writeCachedDeriveState(base, stateA);
|
|
600
|
+
|
|
601
|
+
// Lock flips but basePath is unchanged → the cache still hits.
|
|
602
|
+
process.env.GSD_MILESTONE_LOCK = 'M002';
|
|
603
|
+
assert.strictEqual(readCachedDeriveState(base), stateA, 'basePath-only cache ignores the lock flip');
|
|
604
|
+
|
|
605
|
+
// What the lock-env functions do on flip:
|
|
606
|
+
invalidateStateCache();
|
|
607
|
+
assert.strictEqual(readCachedDeriveState(base), null, 'invalidation defeats the stale-lock read');
|
|
608
|
+
} finally {
|
|
609
|
+
if (prevLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
|
|
610
|
+
else process.env.GSD_MILESTONE_LOCK = prevLock;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
585
614
|
// ═════════════════════════════════════════════════════════════════════════
|
|
586
615
|
// New: deriveStateFromDb() cross-validation tests
|
|
587
616
|
// ═════════════════════════════════════════════════════════════════════════
|
|
@@ -80,6 +80,43 @@ function makeDiscussCtx(notifications: Array<{ message: string; level?: string }
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
async function runDiscussTargetFixture(
|
|
84
|
+
target: string,
|
|
85
|
+
milestones: Array<{ id: string; title?: string; status?: string }>,
|
|
86
|
+
writeArtifacts?: (base: string) => void,
|
|
87
|
+
) {
|
|
88
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-discuss-target-"));
|
|
89
|
+
const notifications: Array<{ message: string; level?: string }> = [];
|
|
90
|
+
const harness = makeDiscussPi();
|
|
91
|
+
try {
|
|
92
|
+
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
93
|
+
writeArtifacts?.(base);
|
|
94
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
95
|
+
assert.equal(openDatabase(dbPath), true);
|
|
96
|
+
for (const milestone of milestones) {
|
|
97
|
+
insertMilestone(milestone);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await showDiscuss(
|
|
101
|
+
makeDiscussCtx(notifications) as any,
|
|
102
|
+
harness.pi as any,
|
|
103
|
+
base,
|
|
104
|
+
{ target },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
notifications: [...notifications],
|
|
109
|
+
sent: [...harness.sent],
|
|
110
|
+
};
|
|
111
|
+
} finally {
|
|
112
|
+
harness.restore();
|
|
113
|
+
if (isDbAvailable()) closeDatabase();
|
|
114
|
+
invalidateStateCache();
|
|
115
|
+
clearGuidedUnitContext();
|
|
116
|
+
rmSync(base, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
83
120
|
describe("discuss target normalization", () => {
|
|
84
121
|
test("canonicalizes milestone and slice casing", () => {
|
|
85
122
|
assert.equal(normalizeDiscussTarget("m014"), "M014");
|
|
@@ -90,6 +127,92 @@ describe("discuss target normalization", () => {
|
|
|
90
127
|
});
|
|
91
128
|
});
|
|
92
129
|
|
|
130
|
+
describe("showDiscuss targeted milestone guardrails (#1320)", () => {
|
|
131
|
+
test("missing, complete, and parked milestone targets get actionable messages", async () => {
|
|
132
|
+
const cases = [
|
|
133
|
+
{
|
|
134
|
+
target: "M006",
|
|
135
|
+
milestones: [{ id: "M005", title: "Current", status: "active" }],
|
|
136
|
+
level: "warning",
|
|
137
|
+
message: /Milestone M006 was not found in the roadmap\. Use \/gsd new-milestone to add it, or \/gsd status to see available milestones\./,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
target: "M006/S01",
|
|
141
|
+
milestones: [{ id: "M005", title: "Current", status: "active" }],
|
|
142
|
+
level: "warning",
|
|
143
|
+
message: /Milestone M006 was not found in the roadmap\. Use \/gsd new-milestone to add it, or \/gsd status to see available milestones\./,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
target: "M006",
|
|
147
|
+
milestones: [{ id: "M006", title: "Done", status: "complete" }],
|
|
148
|
+
level: "info",
|
|
149
|
+
message: /Milestone M006 is already complete\./,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
target: "M006/S01",
|
|
153
|
+
milestones: [{ id: "M006", title: "Done", status: "complete" }],
|
|
154
|
+
level: "info",
|
|
155
|
+
message: /Milestone M006 is already complete\./,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
target: "M006",
|
|
159
|
+
milestones: [{ id: "M006", title: "Deferred", status: "parked" }],
|
|
160
|
+
level: "warning",
|
|
161
|
+
message: /Milestone M006 is parked\. Run \/gsd unpark M006 to reactivate\./,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
target: "M006/S01",
|
|
165
|
+
milestones: [{ id: "M006", title: "Deferred", status: "parked" }],
|
|
166
|
+
level: "warning",
|
|
167
|
+
message: /Milestone M006 is parked\. Run \/gsd unpark M006 to reactivate\./,
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
for (const testCase of cases) {
|
|
172
|
+
const result = await runDiscussTargetFixture(testCase.target, testCase.milestones);
|
|
173
|
+
assert.equal(result.sent.length, 0, `${testCase.target} must not dispatch`);
|
|
174
|
+
assert.equal(result.notifications.length, 1, `${testCase.target} must emit one notification`);
|
|
175
|
+
assert.equal(result.notifications[0]?.level, testCase.level);
|
|
176
|
+
assert.match(result.notifications[0]?.message ?? "", testCase.message);
|
|
177
|
+
assert.doesNotMatch(result.notifications[0]?.message ?? "", /not discussable/i);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("bare milestone targets match unique-suffix milestone IDs", async () => {
|
|
182
|
+
const result = await runDiscussTargetFixture("M006", [
|
|
183
|
+
{ id: "M006-abc123", title: "Unique milestone", status: "active" },
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
assert.equal(result.notifications.length, 0);
|
|
187
|
+
assert.equal(result.sent.length, 1, "bare ID must dispatch the suffixed milestone");
|
|
188
|
+
assert.match(String(result.sent[0]?.content), /M006-abc123|Unique milestone|guided-discuss-milestone/i);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("bare slice targets match unique-suffix milestone IDs", async () => {
|
|
192
|
+
const result = await runDiscussTargetFixture(
|
|
193
|
+
"M006/S01",
|
|
194
|
+
[{ id: "M006-abc123", title: "Unique milestone", status: "active" }],
|
|
195
|
+
(base) => {
|
|
196
|
+
mkdirSync(join(base, ".gsd", "milestones", "M006-abc123"), { recursive: true });
|
|
197
|
+
writeFileSync(
|
|
198
|
+
join(base, ".gsd", "milestones", "M006-abc123", "M006-abc123-ROADMAP.md"),
|
|
199
|
+
`# M006-abc123 Roadmap
|
|
200
|
+
|
|
201
|
+
## Slices
|
|
202
|
+
- [ ] **S01: Unique slice** \`risk:medium\` \`depends:[]\`
|
|
203
|
+
> After this: the suffixed milestone slice can be discussed
|
|
204
|
+
`,
|
|
205
|
+
"utf-8",
|
|
206
|
+
);
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
assert.equal(result.notifications.length, 0);
|
|
211
|
+
assert.equal(result.sent.length, 1, "bare ID slice target must dispatch through the suffixed milestone");
|
|
212
|
+
assert.match(String(result.sent[0]?.content), /M006-abc123|S01|Unique slice|guided-discuss-slice/i);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
93
216
|
describe("loadDiscussNormSlices roadmap fallback (#2892)", () => {
|
|
94
217
|
test("falls back to ROADMAP when DB has no slice rows", async () => {
|
|
95
218
|
const base = mkdtempSync(join(tmpdir(), "gsd-discuss-slices-"));
|