gsd-pi 2.44.0-dev.848dd4c → 2.44.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 +12 -30
- package/dist/resources/extensions/gsd/auto-start.js +0 -10
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +0 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- 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 +2 -2
- 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.js +2 -2
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +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 +14 -14
- package/dist/web/standalone/.next/server/chunks/229.js +1 -1
- package/dist/web/standalone/.next/server/chunks/471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-7e9530a7122506c5.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
- 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/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +8 -6
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +26 -24
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +48 -29
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +44 -34
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +34 -30
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +12 -10
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +47 -43
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +43 -31
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +45 -40
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto-start.ts +0 -14
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +0 -8
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +16 -14
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +57 -43
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +13 -11
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +523 -465
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +75 -73
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +56 -34
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +656 -533
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +143 -165
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +52 -29
- package/src/resources/extensions/gsd/tests/captures.test.ts +176 -148
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +33 -32
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +143 -141
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +59 -38
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +263 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +302 -250
- package/src/resources/extensions/gsd/tests/context-store.test.ts +367 -354
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +72 -68
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +106 -92
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +35 -27
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +237 -220
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +420 -390
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +92 -76
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +83 -68
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +183 -152
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +101 -78
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +227 -192
- package/src/resources/extensions/gsd/tests/detection.test.ts +278 -232
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +34 -30
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +180 -164
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +49 -43
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +32 -28
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +29 -27
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +38 -34
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +75 -54
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +32 -21
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +97 -72
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +44 -38
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +145 -104
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +106 -84
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +60 -54
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +93 -72
- package/src/resources/extensions/gsd/tests/doctor.test.ts +134 -104
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +131 -123
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +24 -20
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +57 -48
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +42 -30
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +206 -198
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +27 -13
- package/src/resources/extensions/gsd/tests/git-service.test.ts +388 -285
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +39 -31
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +69 -63
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +264 -255
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +119 -108
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +103 -81
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +262 -229
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +37 -29
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +102 -81
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +18 -16
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +46 -41
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +53 -42
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +91 -75
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +194 -150
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +125 -101
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +54 -45
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +93 -80
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +66 -57
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +93 -83
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +170 -161
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +141 -125
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +131 -107
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +96 -87
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +164 -125
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +94 -81
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +36 -35
- package/src/resources/extensions/gsd/tests/overrides.test.ts +106 -99
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +47 -40
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +28 -25
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +83 -66
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +77 -54
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +115 -68
- package/src/resources/extensions/gsd/tests/parsers.test.ts +611 -546
- package/src/resources/extensions/gsd/tests/paths.test.ts +87 -72
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +117 -77
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +119 -93
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +82 -70
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +55 -42
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +73 -45
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +38 -28
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +80 -73
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +74 -71
- package/src/resources/extensions/gsd/tests/requirements.test.ts +75 -70
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +66 -44
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +181 -114
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +65 -63
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +128 -66
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +25 -18
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +44 -37
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +26 -19
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +28 -22
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +56 -54
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +25 -23
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +11 -9
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +82 -66
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +47 -46
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +22 -20
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +86 -84
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +43 -41
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +96 -94
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +13 -11
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +29 -27
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +52 -50
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +13 -10
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +18 -14
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +39 -38
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +21 -17
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +30 -25
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +37 -30
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +22 -15
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +66 -59
- package/src/resources/extensions/gsd/tests/worktree.test.ts +50 -44
- package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +0 -100
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +0 -63
- /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
1
|
+
import { createTestContext } from './test-helpers.ts';
|
|
3
2
|
import * as path from 'node:path';
|
|
4
3
|
import * as os from 'node:os';
|
|
5
4
|
import * as fs from 'node:fs';
|
|
@@ -27,6 +26,8 @@ import {
|
|
|
27
26
|
} from '../db-writer.ts';
|
|
28
27
|
import type { Decision, Requirement } from '../types.ts';
|
|
29
28
|
|
|
29
|
+
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
30
|
+
|
|
30
31
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
32
|
// Helpers
|
|
32
33
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -150,433 +151,462 @@ const SAMPLE_REQUIREMENTS: Requirement[] = [
|
|
|
150
151
|
// Round-Trip Tests: Decisions
|
|
151
152
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
}
|
|
154
|
+
console.log('\n── generateDecisionsMd round-trip ──');
|
|
155
|
+
|
|
156
|
+
{
|
|
157
|
+
const md = generateDecisionsMd(SAMPLE_DECISIONS);
|
|
158
|
+
const parsed = parseDecisionsTable(md);
|
|
159
|
+
|
|
160
|
+
assertEq(parsed.length, SAMPLE_DECISIONS.length, 'decisions count matches');
|
|
161
|
+
|
|
162
|
+
for (let i = 0; i < SAMPLE_DECISIONS.length; i++) {
|
|
163
|
+
const orig = SAMPLE_DECISIONS[i];
|
|
164
|
+
const rt = parsed[i];
|
|
165
|
+
assertEq(rt.id, orig.id, `decision ${orig.id} id round-trips`);
|
|
166
|
+
assertEq(rt.when_context, orig.when_context, `decision ${orig.id} when_context round-trips`);
|
|
167
|
+
assertEq(rt.scope, orig.scope, `decision ${orig.id} scope round-trips`);
|
|
168
|
+
assertEq(rt.decision, orig.decision, `decision ${orig.id} decision round-trips`);
|
|
169
|
+
assertEq(rt.choice, orig.choice, `decision ${orig.id} choice round-trips`);
|
|
170
|
+
assertEq(rt.rationale, orig.rationale, `decision ${orig.id} rationale round-trips`);
|
|
171
|
+
assertEq(rt.revisable, orig.revisable, `decision ${orig.id} revisable round-trips`);
|
|
172
|
+
assertEq(rt.made_by, orig.made_by, `decision ${orig.id} made_by round-trips`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
const md = generateDecisionsMd(SAMPLE_DECISIONS);
|
|
176
|
-
assert.ok(md.startsWith('# Decisions Register\n'), 'starts with H1 header');
|
|
177
|
-
assert.ok(md.includes('<!-- Append-only'), 'contains HTML comment block');
|
|
178
|
-
assert.ok(md.includes('| # | When | Scope'), 'contains table header');
|
|
179
|
-
assert.ok(md.includes('|---|------|-------'), 'contains separator row');
|
|
180
|
-
assert.ok(md.includes('| Made By |'), 'contains Made By column header');
|
|
181
|
-
});
|
|
176
|
+
console.log('\n── generateDecisionsMd format ──');
|
|
182
177
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
178
|
+
{
|
|
179
|
+
const md = generateDecisionsMd(SAMPLE_DECISIONS);
|
|
180
|
+
assertTrue(md.startsWith('# Decisions Register\n'), 'starts with H1 header');
|
|
181
|
+
assertTrue(md.includes('<!-- Append-only'), 'contains HTML comment block');
|
|
182
|
+
assertTrue(md.includes('| # | When | Scope'), 'contains table header');
|
|
183
|
+
assertTrue(md.includes('|---|------|-------'), 'contains separator row');
|
|
184
|
+
assertTrue(md.includes('| Made By |'), 'contains Made By column header');
|
|
185
|
+
}
|
|
189
186
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
made_by: 'agent',
|
|
201
|
-
superseded_by: null,
|
|
202
|
-
};
|
|
203
|
-
const md = generateDecisionsMd([withPipe]);
|
|
204
|
-
// Should not break the table — pipe in decision text should be escaped
|
|
205
|
-
const parsed = parseDecisionsTable(md);
|
|
206
|
-
assert.ok(parsed.length >= 1, 'pipe-containing decision parses without breaking table');
|
|
207
|
-
});
|
|
187
|
+
console.log('\n── generateDecisionsMd empty input ──');
|
|
188
|
+
|
|
189
|
+
{
|
|
190
|
+
const md = generateDecisionsMd([]);
|
|
191
|
+
const parsed = parseDecisionsTable(md);
|
|
192
|
+
assertEq(parsed.length, 0, 'empty decisions produces empty parse');
|
|
193
|
+
assertTrue(md.includes('| # | When | Scope'), 'still has table header even when empty');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log('\n── generateDecisionsMd pipe escaping ──');
|
|
208
197
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
198
|
+
{
|
|
199
|
+
const withPipe: Decision = {
|
|
200
|
+
seq: 1,
|
|
201
|
+
id: 'D001',
|
|
202
|
+
when_context: 'M001',
|
|
203
|
+
scope: 'arch',
|
|
204
|
+
decision: 'Choice A | Choice B comparison',
|
|
205
|
+
choice: 'A',
|
|
206
|
+
rationale: 'Better',
|
|
207
|
+
revisable: 'No',
|
|
208
|
+
made_by: 'agent',
|
|
209
|
+
superseded_by: null,
|
|
210
|
+
};
|
|
211
|
+
const md = generateDecisionsMd([withPipe]);
|
|
212
|
+
// Should not break the table — pipe in decision text should be escaped
|
|
213
|
+
const parsed = parseDecisionsTable(md);
|
|
214
|
+
assertTrue(parsed.length >= 1, 'pipe-containing decision parses without breaking table');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
218
|
+
// Round-Trip Tests: Requirements
|
|
219
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
220
|
+
|
|
221
|
+
console.log('\n── generateRequirementsMd round-trip ──');
|
|
222
|
+
|
|
223
|
+
{
|
|
224
|
+
const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
|
|
225
|
+
const parsed = parseRequirementsSections(md);
|
|
226
|
+
|
|
227
|
+
assertEq(parsed.length, SAMPLE_REQUIREMENTS.length, 'requirements count matches');
|
|
228
|
+
|
|
229
|
+
for (const orig of SAMPLE_REQUIREMENTS) {
|
|
230
|
+
const rt = parsed.find(r => r.id === orig.id);
|
|
231
|
+
assertTrue(!!rt, `requirement ${orig.id} found in parsed output`);
|
|
232
|
+
if (rt) {
|
|
233
|
+
assertEq(rt.class, orig.class, `requirement ${orig.id} class round-trips`);
|
|
234
|
+
assertEq(rt.description, orig.description, `requirement ${orig.id} description round-trips`);
|
|
235
|
+
assertEq(rt.why, orig.why, `requirement ${orig.id} why round-trips`);
|
|
236
|
+
assertEq(rt.source, orig.source, `requirement ${orig.id} source round-trips`);
|
|
237
|
+
assertEq(rt.primary_owner, orig.primary_owner, `requirement ${orig.id} primary_owner round-trips`);
|
|
238
|
+
assertEq(rt.supporting_slices, orig.supporting_slices, `requirement ${orig.id} supporting_slices round-trips`);
|
|
239
|
+
if (orig.notes) {
|
|
240
|
+
assertEq(rt.notes, orig.notes, `requirement ${orig.id} notes round-trips`);
|
|
232
241
|
}
|
|
233
242
|
}
|
|
234
|
-
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
235
245
|
|
|
236
|
-
|
|
237
|
-
const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
|
|
238
|
-
assert.ok(md.includes('## Active'), 'has Active section');
|
|
239
|
-
assert.ok(md.includes('## Validated'), 'has Validated section');
|
|
240
|
-
assert.ok(md.includes('## Deferred'), 'has Deferred section');
|
|
241
|
-
assert.ok(md.includes('## Out of Scope'), 'has Out of Scope section');
|
|
242
|
-
assert.ok(md.includes('## Traceability'), 'has Traceability section');
|
|
243
|
-
assert.ok(md.includes('## Coverage Summary'), 'has Coverage Summary section');
|
|
244
|
-
});
|
|
246
|
+
console.log('\n── generateRequirementsMd sections ──');
|
|
245
247
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
{
|
|
249
|
+
const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
|
|
250
|
+
assertTrue(md.includes('## Active'), 'has Active section');
|
|
251
|
+
assertTrue(md.includes('## Validated'), 'has Validated section');
|
|
252
|
+
assertTrue(md.includes('## Deferred'), 'has Deferred section');
|
|
253
|
+
assertTrue(md.includes('## Out of Scope'), 'has Out of Scope section');
|
|
254
|
+
assertTrue(md.includes('## Traceability'), 'has Traceability section');
|
|
255
|
+
assertTrue(md.includes('## Coverage Summary'), 'has Coverage Summary section');
|
|
256
|
+
}
|
|
255
257
|
|
|
256
|
-
|
|
257
|
-
const md = generateRequirementsMd([]);
|
|
258
|
-
const parsed = parseRequirementsSections(md);
|
|
259
|
-
assert.deepStrictEqual(parsed.length, 0, 'empty requirements produces empty parse');
|
|
260
|
-
});
|
|
258
|
+
console.log('\n── generateRequirementsMd only populated sections ──');
|
|
261
259
|
|
|
262
|
-
|
|
263
|
-
//
|
|
264
|
-
|
|
260
|
+
{
|
|
261
|
+
// Only active requirements — should only have Active section
|
|
262
|
+
const activeOnly = SAMPLE_REQUIREMENTS.filter(r => r.status === 'active');
|
|
263
|
+
const md = generateRequirementsMd(activeOnly);
|
|
264
|
+
assertTrue(md.includes('## Active'), 'has Active section');
|
|
265
|
+
assertTrue(!md.includes('## Validated'), 'no Validated section when no validated reqs');
|
|
266
|
+
assertTrue(!md.includes('## Deferred'), 'no Deferred section when no deferred reqs');
|
|
267
|
+
assertTrue(!md.includes('## Out of Scope'), 'no Out of Scope section when no out-of-scope reqs');
|
|
268
|
+
}
|
|
265
269
|
|
|
266
|
-
|
|
267
|
-
// Open in-memory DB
|
|
268
|
-
openDatabase(':memory:');
|
|
270
|
+
console.log('\n── generateRequirementsMd empty input ──');
|
|
269
271
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
{
|
|
273
|
+
const md = generateRequirementsMd([]);
|
|
274
|
+
const parsed = parseRequirementsSections(md);
|
|
275
|
+
assertEq(parsed.length, 0, 'empty requirements produces empty parse');
|
|
276
|
+
}
|
|
272
277
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
when_context: 'M001',
|
|
277
|
-
scope: 'test',
|
|
278
|
-
decision: 'test decision',
|
|
279
|
-
choice: 'test choice',
|
|
280
|
-
rationale: 'test',
|
|
281
|
-
revisable: 'No',
|
|
282
|
-
made_by: 'agent',
|
|
283
|
-
superseded_by: null,
|
|
284
|
-
});
|
|
285
|
-
upsertDecision({
|
|
286
|
-
id: 'D005',
|
|
287
|
-
when_context: 'M001',
|
|
288
|
-
scope: 'test',
|
|
289
|
-
decision: 'test decision 5',
|
|
290
|
-
choice: 'test choice',
|
|
291
|
-
rationale: 'test',
|
|
292
|
-
revisable: 'No',
|
|
293
|
-
made_by: 'agent',
|
|
294
|
-
superseded_by: null,
|
|
295
|
-
});
|
|
278
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
279
|
+
// nextDecisionId Tests
|
|
280
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
296
281
|
|
|
297
|
-
|
|
298
|
-
assert.deepStrictEqual(id2, 'D006', 'next ID after D005 is D006');
|
|
282
|
+
console.log('\n── nextDecisionId ──');
|
|
299
283
|
|
|
300
|
-
|
|
284
|
+
{
|
|
285
|
+
// Open in-memory DB
|
|
286
|
+
openDatabase(':memory:');
|
|
287
|
+
|
|
288
|
+
const id1 = await nextDecisionId();
|
|
289
|
+
assertEq(id1, 'D001', 'first ID when no decisions exist');
|
|
290
|
+
|
|
291
|
+
// Insert some decisions
|
|
292
|
+
upsertDecision({
|
|
293
|
+
id: 'D001',
|
|
294
|
+
when_context: 'M001',
|
|
295
|
+
scope: 'test',
|
|
296
|
+
decision: 'test decision',
|
|
297
|
+
choice: 'test choice',
|
|
298
|
+
rationale: 'test',
|
|
299
|
+
revisable: 'No',
|
|
300
|
+
made_by: 'agent',
|
|
301
|
+
superseded_by: null,
|
|
302
|
+
});
|
|
303
|
+
upsertDecision({
|
|
304
|
+
id: 'D005',
|
|
305
|
+
when_context: 'M001',
|
|
306
|
+
scope: 'test',
|
|
307
|
+
decision: 'test decision 5',
|
|
308
|
+
choice: 'test choice',
|
|
309
|
+
rationale: 'test',
|
|
310
|
+
revisable: 'No',
|
|
311
|
+
made_by: 'agent',
|
|
312
|
+
superseded_by: null,
|
|
301
313
|
});
|
|
302
314
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
315
|
+
const id2 = await nextDecisionId();
|
|
316
|
+
assertEq(id2, 'D006', 'next ID after D005 is D006');
|
|
306
317
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
310
|
-
openDatabase(dbPath);
|
|
318
|
+
closeDatabase();
|
|
319
|
+
}
|
|
311
320
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
decision: 'Test decision',
|
|
316
|
-
choice: 'Option A',
|
|
317
|
-
rationale: 'Best option',
|
|
318
|
-
when_context: 'M001',
|
|
319
|
-
}, tmpDir);
|
|
320
|
-
|
|
321
|
-
assert.deepStrictEqual(result.id, 'D001', 'saveDecisionToDb returns D001 as first ID');
|
|
322
|
-
|
|
323
|
-
// Verify DB state
|
|
324
|
-
const dbDecision = getDecisionById('D001');
|
|
325
|
-
assert.ok(!!dbDecision, 'decision exists in DB after save');
|
|
326
|
-
assert.deepStrictEqual(dbDecision?.scope, 'arch', 'DB decision has correct scope');
|
|
327
|
-
assert.deepStrictEqual(dbDecision?.choice, 'Option A', 'DB decision has correct choice');
|
|
328
|
-
|
|
329
|
-
// Verify markdown file was written
|
|
330
|
-
const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
|
|
331
|
-
assert.ok(fs.existsSync(mdPath), 'DECISIONS.md file created');
|
|
332
|
-
|
|
333
|
-
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
|
334
|
-
assert.ok(mdContent.includes('D001'), 'DECISIONS.md contains new decision ID');
|
|
335
|
-
assert.ok(mdContent.includes('Test decision'), 'DECISIONS.md contains decision text');
|
|
336
|
-
|
|
337
|
-
// Verify round-trip of the written file
|
|
338
|
-
const parsed = parseDecisionsTable(mdContent);
|
|
339
|
-
assert.deepStrictEqual(parsed.length, 1, 'written DECISIONS.md parses to 1 decision');
|
|
340
|
-
assert.deepStrictEqual(parsed[0].id, 'D001', 'parsed decision has correct ID');
|
|
341
|
-
|
|
342
|
-
// Add second decision
|
|
343
|
-
const result2 = await saveDecisionToDb({
|
|
344
|
-
scope: 'impl',
|
|
345
|
-
decision: 'Second decision',
|
|
346
|
-
choice: 'Option B',
|
|
347
|
-
rationale: 'Also good',
|
|
348
|
-
}, tmpDir);
|
|
349
|
-
|
|
350
|
-
assert.deepStrictEqual(result2.id, 'D002', 'second decision gets D002');
|
|
351
|
-
|
|
352
|
-
const mdContent2 = fs.readFileSync(mdPath, 'utf-8');
|
|
353
|
-
const parsed2 = parseDecisionsTable(mdContent2);
|
|
354
|
-
assert.deepStrictEqual(parsed2.length, 2, 'DECISIONS.md now has 2 decisions');
|
|
355
|
-
} finally {
|
|
356
|
-
closeDatabase();
|
|
357
|
-
cleanupDir(tmpDir);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
321
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
322
|
+
// saveDecisionToDb Tests
|
|
323
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
360
324
|
|
|
361
|
-
|
|
362
|
-
// updateRequirementInDb Tests
|
|
363
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
325
|
+
console.log('\n── saveDecisionToDb ──');
|
|
364
326
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
327
|
+
{
|
|
328
|
+
const tmpDir = makeTmpDir();
|
|
329
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
330
|
+
openDatabase(dbPath);
|
|
369
331
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
332
|
+
try {
|
|
333
|
+
const result = await saveDecisionToDb({
|
|
334
|
+
scope: 'arch',
|
|
335
|
+
decision: 'Test decision',
|
|
336
|
+
choice: 'Option A',
|
|
337
|
+
rationale: 'Best option',
|
|
338
|
+
when_context: 'M001',
|
|
339
|
+
}, tmpDir);
|
|
340
|
+
|
|
341
|
+
assertEq(result.id, 'D001', 'saveDecisionToDb returns D001 as first ID');
|
|
342
|
+
|
|
343
|
+
// Verify DB state
|
|
344
|
+
const dbDecision = getDecisionById('D001');
|
|
345
|
+
assertTrue(!!dbDecision, 'decision exists in DB after save');
|
|
346
|
+
assertEq(dbDecision?.scope, 'arch', 'DB decision has correct scope');
|
|
347
|
+
assertEq(dbDecision?.choice, 'Option A', 'DB decision has correct choice');
|
|
348
|
+
|
|
349
|
+
// Verify markdown file was written
|
|
350
|
+
const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
|
|
351
|
+
assertTrue(fs.existsSync(mdPath), 'DECISIONS.md file created');
|
|
352
|
+
|
|
353
|
+
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
|
354
|
+
assertTrue(mdContent.includes('D001'), 'DECISIONS.md contains new decision ID');
|
|
355
|
+
assertTrue(mdContent.includes('Test decision'), 'DECISIONS.md contains decision text');
|
|
356
|
+
|
|
357
|
+
// Verify round-trip of the written file
|
|
358
|
+
const parsed = parseDecisionsTable(mdContent);
|
|
359
|
+
assertEq(parsed.length, 1, 'written DECISIONS.md parses to 1 decision');
|
|
360
|
+
assertEq(parsed[0].id, 'D001', 'parsed decision has correct ID');
|
|
361
|
+
|
|
362
|
+
// Add second decision
|
|
363
|
+
const result2 = await saveDecisionToDb({
|
|
364
|
+
scope: 'impl',
|
|
365
|
+
decision: 'Second decision',
|
|
366
|
+
choice: 'Option B',
|
|
367
|
+
rationale: 'Also good',
|
|
368
|
+
}, tmpDir);
|
|
369
|
+
|
|
370
|
+
assertEq(result2.id, 'D002', 'second decision gets D002');
|
|
371
|
+
|
|
372
|
+
const mdContent2 = fs.readFileSync(mdPath, 'utf-8');
|
|
373
|
+
const parsed2 = parseDecisionsTable(mdContent2);
|
|
374
|
+
assertEq(parsed2.length, 2, 'DECISIONS.md now has 2 decisions');
|
|
375
|
+
} finally {
|
|
376
|
+
closeDatabase();
|
|
377
|
+
cleanupDir(tmpDir);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
418
380
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
openDatabase(dbPath);
|
|
381
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
382
|
+
// updateRequirementInDb Tests
|
|
383
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
423
384
|
|
|
424
|
-
|
|
425
|
-
let threw = false;
|
|
426
|
-
try {
|
|
427
|
-
await updateRequirementInDb('R999', { status: 'validated' }, tmpDir);
|
|
428
|
-
} catch (err) {
|
|
429
|
-
threw = true;
|
|
430
|
-
assert.ok(
|
|
431
|
-
(err as Error).message.includes('R999'),
|
|
432
|
-
'error message mentions the missing ID',
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
assert.ok(threw, 'throws when requirement not found');
|
|
436
|
-
} finally {
|
|
437
|
-
closeDatabase();
|
|
438
|
-
cleanupDir(tmpDir);
|
|
439
|
-
}
|
|
440
|
-
});
|
|
385
|
+
console.log('\n── updateRequirementInDb ──');
|
|
441
386
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
387
|
+
{
|
|
388
|
+
const tmpDir = makeTmpDir();
|
|
389
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
390
|
+
openDatabase(dbPath);
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
// Seed a requirement
|
|
394
|
+
upsertRequirement({
|
|
395
|
+
id: 'R001',
|
|
396
|
+
class: 'core-capability',
|
|
397
|
+
status: 'active',
|
|
398
|
+
description: 'Test requirement',
|
|
399
|
+
why: 'Testing',
|
|
400
|
+
source: 'test',
|
|
401
|
+
primary_owner: 'M001/S01',
|
|
402
|
+
supporting_slices: 'none',
|
|
403
|
+
validation: 'unmapped',
|
|
404
|
+
notes: '',
|
|
405
|
+
full_content: '',
|
|
406
|
+
superseded_by: null,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Update it
|
|
410
|
+
await updateRequirementInDb('R001', {
|
|
411
|
+
status: 'validated',
|
|
412
|
+
validation: 'S01 — all tests pass',
|
|
413
|
+
notes: 'Validated in S01',
|
|
414
|
+
}, tmpDir);
|
|
415
|
+
|
|
416
|
+
// Verify DB state
|
|
417
|
+
const updated = getRequirementById('R001');
|
|
418
|
+
assertTrue(!!updated, 'requirement still exists after update');
|
|
419
|
+
assertEq(updated?.status, 'validated', 'status updated in DB');
|
|
420
|
+
assertEq(updated?.validation, 'S01 — all tests pass', 'validation updated in DB');
|
|
421
|
+
assertEq(updated?.description, 'Test requirement', 'description preserved after update');
|
|
422
|
+
|
|
423
|
+
// Verify markdown file was written
|
|
424
|
+
const mdPath = path.join(tmpDir, '.gsd', 'REQUIREMENTS.md');
|
|
425
|
+
assertTrue(fs.existsSync(mdPath), 'REQUIREMENTS.md file created');
|
|
426
|
+
|
|
427
|
+
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
|
428
|
+
assertTrue(mdContent.includes('R001'), 'REQUIREMENTS.md contains requirement ID');
|
|
429
|
+
assertTrue(mdContent.includes('validated'), 'REQUIREMENTS.md shows updated status');
|
|
430
|
+
|
|
431
|
+
// Verify round-trip
|
|
432
|
+
const parsed = parseRequirementsSections(mdContent);
|
|
433
|
+
assertEq(parsed.length, 1, 'parsed 1 requirement from written file');
|
|
434
|
+
assertEq(parsed[0].status, 'validated', 'parsed status matches update');
|
|
435
|
+
} finally {
|
|
436
|
+
closeDatabase();
|
|
437
|
+
cleanupDir(tmpDir);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
445
440
|
|
|
446
|
-
|
|
447
|
-
const tmpDir = makeTmpDir();
|
|
448
|
-
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
449
|
-
openDatabase(dbPath);
|
|
441
|
+
console.log('\n── updateRequirementInDb — not found ──');
|
|
450
442
|
|
|
443
|
+
{
|
|
444
|
+
const tmpDir = makeTmpDir();
|
|
445
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
446
|
+
openDatabase(dbPath);
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
let threw = false;
|
|
451
450
|
try {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
slice_id: 'S06',
|
|
459
|
-
task_id: 'T01',
|
|
460
|
-
}, tmpDir);
|
|
461
|
-
|
|
462
|
-
// Verify DB state
|
|
463
|
-
const adapter = _getAdapter();
|
|
464
|
-
assert.ok(!!adapter, 'adapter available');
|
|
465
|
-
const row = adapter!
|
|
466
|
-
.prepare('SELECT * FROM artifacts WHERE path = ?')
|
|
467
|
-
.get('milestones/M001/slices/S06/tasks/T01-SUMMARY.md');
|
|
468
|
-
assert.ok(!!row, 'artifact exists in DB');
|
|
469
|
-
assert.deepStrictEqual(row!['artifact_type'], 'SUMMARY', 'artifact type correct in DB');
|
|
470
|
-
assert.deepStrictEqual(row!['milestone_id'], 'M001', 'milestone_id correct in DB');
|
|
471
|
-
assert.deepStrictEqual(row!['slice_id'], 'S06', 'slice_id correct in DB');
|
|
472
|
-
assert.deepStrictEqual(row!['task_id'], 'T01', 'task_id correct in DB');
|
|
473
|
-
|
|
474
|
-
// Verify file on disk
|
|
475
|
-
const filePath = path.join(
|
|
476
|
-
tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S06', 'tasks', 'T01-SUMMARY.md',
|
|
451
|
+
await updateRequirementInDb('R999', { status: 'validated' }, tmpDir);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
threw = true;
|
|
454
|
+
assertTrue(
|
|
455
|
+
(err as Error).message.includes('R999'),
|
|
456
|
+
'error message mentions the missing ID',
|
|
477
457
|
);
|
|
478
|
-
assert.ok(fs.existsSync(filePath), 'artifact file written to disk');
|
|
479
|
-
assert.deepStrictEqual(fs.readFileSync(filePath, 'utf-8'), content, 'file content matches');
|
|
480
|
-
} finally {
|
|
481
|
-
closeDatabase();
|
|
482
|
-
cleanupDir(tmpDir);
|
|
483
458
|
}
|
|
484
|
-
|
|
459
|
+
assertTrue(threw, 'throws when requirement not found');
|
|
460
|
+
} finally {
|
|
461
|
+
closeDatabase();
|
|
462
|
+
cleanupDir(tmpDir);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
485
465
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
test('Full DB round-trip: decisions', () => {
|
|
491
|
-
openDatabase(':memory:');
|
|
492
|
-
|
|
493
|
-
// Insert via DB
|
|
494
|
-
for (const d of SAMPLE_DECISIONS) {
|
|
495
|
-
upsertDecision({
|
|
496
|
-
id: d.id,
|
|
497
|
-
when_context: d.when_context,
|
|
498
|
-
scope: d.scope,
|
|
499
|
-
decision: d.decision,
|
|
500
|
-
choice: d.choice,
|
|
501
|
-
rationale: d.rationale,
|
|
502
|
-
revisable: d.revisable,
|
|
503
|
-
made_by: d.made_by,
|
|
504
|
-
superseded_by: d.superseded_by,
|
|
505
|
-
});
|
|
506
|
-
}
|
|
466
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
467
|
+
// saveArtifactToDb Tests
|
|
468
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
507
469
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
when_context: row['when_context'] as string,
|
|
515
|
-
scope: row['scope'] as string,
|
|
516
|
-
decision: row['decision'] as string,
|
|
517
|
-
choice: row['choice'] as string,
|
|
518
|
-
rationale: row['rationale'] as string,
|
|
519
|
-
revisable: row['revisable'] as string,
|
|
520
|
-
made_by: (row['made_by'] as string as import('../types.js').DecisionMadeBy) ?? 'agent',
|
|
521
|
-
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
522
|
-
}));
|
|
523
|
-
|
|
524
|
-
const md = generateDecisionsMd(dbDecisions);
|
|
525
|
-
const parsed = parseDecisionsTable(md);
|
|
526
|
-
|
|
527
|
-
assert.deepStrictEqual(parsed.length, SAMPLE_DECISIONS.length, 'DB round-trip decision count');
|
|
528
|
-
for (const orig of SAMPLE_DECISIONS) {
|
|
529
|
-
const rt = parsed.find(p => p.id === orig.id);
|
|
530
|
-
assert.ok(!!rt, `DB round-trip: ${orig.id} found`);
|
|
531
|
-
if (rt) {
|
|
532
|
-
assert.deepStrictEqual(rt.scope, orig.scope, `DB round-trip: ${orig.id} scope`);
|
|
533
|
-
assert.deepStrictEqual(rt.choice, orig.choice, `DB round-trip: ${orig.id} choice`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
470
|
+
console.log('\n── saveArtifactToDb ──');
|
|
471
|
+
|
|
472
|
+
{
|
|
473
|
+
const tmpDir = makeTmpDir();
|
|
474
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
475
|
+
openDatabase(dbPath);
|
|
536
476
|
|
|
477
|
+
try {
|
|
478
|
+
const content = '# Task Summary\n\nTest content\n';
|
|
479
|
+
await saveArtifactToDb({
|
|
480
|
+
path: 'milestones/M001/slices/S06/tasks/T01-SUMMARY.md',
|
|
481
|
+
artifact_type: 'SUMMARY',
|
|
482
|
+
content,
|
|
483
|
+
milestone_id: 'M001',
|
|
484
|
+
slice_id: 'S06',
|
|
485
|
+
task_id: 'T01',
|
|
486
|
+
}, tmpDir);
|
|
487
|
+
|
|
488
|
+
// Verify DB state
|
|
489
|
+
const adapter = _getAdapter();
|
|
490
|
+
assertTrue(!!adapter, 'adapter available');
|
|
491
|
+
const row = adapter!
|
|
492
|
+
.prepare('SELECT * FROM artifacts WHERE path = ?')
|
|
493
|
+
.get('milestones/M001/slices/S06/tasks/T01-SUMMARY.md');
|
|
494
|
+
assertTrue(!!row, 'artifact exists in DB');
|
|
495
|
+
assertEq(row!['artifact_type'], 'SUMMARY', 'artifact type correct in DB');
|
|
496
|
+
assertEq(row!['milestone_id'], 'M001', 'milestone_id correct in DB');
|
|
497
|
+
assertEq(row!['slice_id'], 'S06', 'slice_id correct in DB');
|
|
498
|
+
assertEq(row!['task_id'], 'T01', 'task_id correct in DB');
|
|
499
|
+
|
|
500
|
+
// Verify file on disk
|
|
501
|
+
const filePath = path.join(
|
|
502
|
+
tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S06', 'tasks', 'T01-SUMMARY.md',
|
|
503
|
+
);
|
|
504
|
+
assertTrue(fs.existsSync(filePath), 'artifact file written to disk');
|
|
505
|
+
assertEq(fs.readFileSync(filePath, 'utf-8'), content, 'file content matches');
|
|
506
|
+
} finally {
|
|
537
507
|
closeDatabase();
|
|
538
|
-
|
|
508
|
+
cleanupDir(tmpDir);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
539
511
|
|
|
540
|
-
|
|
541
|
-
|
|
512
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
513
|
+
// Full Round-Trip: DB → Markdown → Parse → Compare
|
|
514
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
515
|
+
|
|
516
|
+
console.log('\n── Full DB round-trip: decisions ──');
|
|
517
|
+
|
|
518
|
+
{
|
|
519
|
+
openDatabase(':memory:');
|
|
542
520
|
|
|
543
|
-
|
|
544
|
-
|
|
521
|
+
// Insert via DB
|
|
522
|
+
for (const d of SAMPLE_DECISIONS) {
|
|
523
|
+
upsertDecision({
|
|
524
|
+
id: d.id,
|
|
525
|
+
when_context: d.when_context,
|
|
526
|
+
scope: d.scope,
|
|
527
|
+
decision: d.decision,
|
|
528
|
+
choice: d.choice,
|
|
529
|
+
rationale: d.rationale,
|
|
530
|
+
revisable: d.revisable,
|
|
531
|
+
made_by: d.made_by,
|
|
532
|
+
superseded_by: d.superseded_by,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Generate markdown from DB state
|
|
537
|
+
const adapter = _getAdapter()!;
|
|
538
|
+
const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
|
|
539
|
+
const dbDecisions: Decision[] = rows.map(row => ({
|
|
540
|
+
seq: row['seq'] as number,
|
|
541
|
+
id: row['id'] as string,
|
|
542
|
+
when_context: row['when_context'] as string,
|
|
543
|
+
scope: row['scope'] as string,
|
|
544
|
+
decision: row['decision'] as string,
|
|
545
|
+
choice: row['choice'] as string,
|
|
546
|
+
rationale: row['rationale'] as string,
|
|
547
|
+
revisable: row['revisable'] as string,
|
|
548
|
+
made_by: (row['made_by'] as string as import('../types.js').DecisionMadeBy) ?? 'agent',
|
|
549
|
+
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
550
|
+
}));
|
|
551
|
+
|
|
552
|
+
const md = generateDecisionsMd(dbDecisions);
|
|
553
|
+
const parsed = parseDecisionsTable(md);
|
|
554
|
+
|
|
555
|
+
assertEq(parsed.length, SAMPLE_DECISIONS.length, 'DB round-trip decision count');
|
|
556
|
+
for (const orig of SAMPLE_DECISIONS) {
|
|
557
|
+
const rt = parsed.find(p => p.id === orig.id);
|
|
558
|
+
assertTrue(!!rt, `DB round-trip: ${orig.id} found`);
|
|
559
|
+
if (rt) {
|
|
560
|
+
assertEq(rt.scope, orig.scope, `DB round-trip: ${orig.id} scope`);
|
|
561
|
+
assertEq(rt.choice, orig.choice, `DB round-trip: ${orig.id} choice`);
|
|
545
562
|
}
|
|
563
|
+
}
|
|
546
564
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
565
|
+
closeDatabase();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
console.log('\n── Full DB round-trip: requirements ──');
|
|
569
|
+
|
|
570
|
+
{
|
|
571
|
+
openDatabase(':memory:');
|
|
572
|
+
|
|
573
|
+
for (const r of SAMPLE_REQUIREMENTS) {
|
|
574
|
+
upsertRequirement(r);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const adapter = _getAdapter()!;
|
|
578
|
+
const rows = adapter.prepare('SELECT * FROM requirements ORDER BY id').all();
|
|
579
|
+
const dbReqs: Requirement[] = rows.map(row => ({
|
|
580
|
+
id: row['id'] as string,
|
|
581
|
+
class: row['class'] as string,
|
|
582
|
+
status: row['status'] as string,
|
|
583
|
+
description: row['description'] as string,
|
|
584
|
+
why: row['why'] as string,
|
|
585
|
+
source: row['source'] as string,
|
|
586
|
+
primary_owner: row['primary_owner'] as string,
|
|
587
|
+
supporting_slices: row['supporting_slices'] as string,
|
|
588
|
+
validation: row['validation'] as string,
|
|
589
|
+
notes: row['notes'] as string,
|
|
590
|
+
full_content: row['full_content'] as string,
|
|
591
|
+
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
592
|
+
}));
|
|
593
|
+
|
|
594
|
+
const md = generateRequirementsMd(dbReqs);
|
|
595
|
+
const parsed = parseRequirementsSections(md);
|
|
596
|
+
|
|
597
|
+
assertEq(parsed.length, SAMPLE_REQUIREMENTS.length, 'DB round-trip requirement count');
|
|
598
|
+
for (const orig of SAMPLE_REQUIREMENTS) {
|
|
599
|
+
const rt = parsed.find(p => p.id === orig.id);
|
|
600
|
+
assertTrue(!!rt, `DB round-trip: ${orig.id} found`);
|
|
601
|
+
if (rt) {
|
|
602
|
+
assertEq(rt.class, orig.class, `DB round-trip: ${orig.id} class`);
|
|
603
|
+
assertEq(rt.description, orig.description, `DB round-trip: ${orig.id} description`);
|
|
575
604
|
}
|
|
605
|
+
}
|
|
576
606
|
|
|
577
|
-
|
|
578
|
-
|
|
607
|
+
closeDatabase();
|
|
608
|
+
}
|
|
579
609
|
|
|
580
|
-
|
|
610
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
581
611
|
|
|
582
|
-
|
|
612
|
+
report();
|