gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.73f2fd5
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 +30 -12
- package/dist/resources/extensions/gsd/auto/infra-errors.js +3 -0
- package/dist/resources/extensions/gsd/auto/phases.js +36 -36
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
- package/dist/resources/extensions/gsd/auto-start.js +10 -0
- package/dist/resources/extensions/gsd/auto-timers.js +57 -3
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +9 -6
- package/dist/resources/extensions/gsd/auto.js +30 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +156 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
- package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
- package/dist/resources/extensions/gsd/db-writer.js +34 -16
- package/dist/resources/extensions/gsd/doctor.js +8 -0
- package/dist/resources/extensions/gsd/git-service.js +8 -3
- package/dist/resources/extensions/gsd/gsd-db.js +12 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
- package/dist/resources/extensions/gsd/preferences.js +9 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
- package/dist/resources/extensions/gsd/state.js +19 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +1 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -0
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +88 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +6 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- 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/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
- 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 +24 -26
- 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 +29 -48
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +20 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
- 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 +30 -34
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -0
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +17 -8
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -3
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
- 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/auth-storage.ts +15 -1
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
- package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +21 -1
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/main.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
- package/src/resources/extensions/gsd/auto/phases.ts +45 -48
- package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
- package/src/resources/extensions/gsd/auto-start.ts +14 -0
- package/src/resources/extensions/gsd/auto-timers.ts +64 -3
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +9 -6
- package/src/resources/extensions/gsd/auto.ts +37 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +148 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
- package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
- package/src/resources/extensions/gsd/db-writer.ts +39 -17
- package/src/resources/extensions/gsd/doctor.ts +7 -1
- package/src/resources/extensions/gsd/git-service.ts +6 -2
- package/src/resources/extensions/gsd/gsd-db.ts +16 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
- package/src/resources/extensions/gsd/preferences.ts +11 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
- package/src/resources/extensions/gsd/state.ts +19 -1
- 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 +14 -16
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
- package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
- package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
- 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 +38 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +465 -416
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +210 -181
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
- package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
- package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
- package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
- package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
- package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
- package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
- package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
- package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
- package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
- package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +10 -11
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
- package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +7 -0
- package/src/resources/extensions/mcp-client/index.ts +20 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → kxxAA66bah_yhPYqLBHE2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → kxxAA66bah_yhPYqLBHE2}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
2
3
|
import * as path from 'node:path';
|
|
3
4
|
import * as os from 'node:os';
|
|
4
5
|
import * as fs from 'node:fs';
|
|
@@ -26,8 +27,6 @@ import {
|
|
|
26
27
|
} from '../db-writer.ts';
|
|
27
28
|
import type { Decision, Requirement } from '../types.ts';
|
|
28
29
|
|
|
29
|
-
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
30
|
-
|
|
31
30
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
32
31
|
// Helpers
|
|
33
32
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -151,462 +150,512 @@ const SAMPLE_REQUIREMENTS: Requirement[] = [
|
|
|
151
150
|
// Round-Trip Tests: Decisions
|
|
152
151
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log('\n── generateDecisionsMd format ──');
|
|
177
|
-
|
|
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
|
-
}
|
|
186
|
-
|
|
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
|
-
}
|
|
153
|
+
describe('db-writer', () => {
|
|
154
|
+
test('generateDecisionsMd round-trip', () => {
|
|
155
|
+
const md = generateDecisionsMd(SAMPLE_DECISIONS);
|
|
156
|
+
const parsed = parseDecisionsTable(md);
|
|
157
|
+
|
|
158
|
+
assert.deepStrictEqual(parsed.length, SAMPLE_DECISIONS.length, 'decisions count matches');
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < SAMPLE_DECISIONS.length; i++) {
|
|
161
|
+
const orig = SAMPLE_DECISIONS[i];
|
|
162
|
+
const rt = parsed[i];
|
|
163
|
+
assert.deepStrictEqual(rt.id, orig.id, `decision ${orig.id} id round-trips`);
|
|
164
|
+
assert.deepStrictEqual(rt.when_context, orig.when_context, `decision ${orig.id} when_context round-trips`);
|
|
165
|
+
assert.deepStrictEqual(rt.scope, orig.scope, `decision ${orig.id} scope round-trips`);
|
|
166
|
+
assert.deepStrictEqual(rt.decision, orig.decision, `decision ${orig.id} decision round-trips`);
|
|
167
|
+
assert.deepStrictEqual(rt.choice, orig.choice, `decision ${orig.id} choice round-trips`);
|
|
168
|
+
assert.deepStrictEqual(rt.rationale, orig.rationale, `decision ${orig.id} rationale round-trips`);
|
|
169
|
+
assert.deepStrictEqual(rt.revisable, orig.revisable, `decision ${orig.id} revisable round-trips`);
|
|
170
|
+
assert.deepStrictEqual(rt.made_by, orig.made_by, `decision ${orig.id} made_by round-trips`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
195
173
|
|
|
196
|
-
|
|
174
|
+
test('generateDecisionsMd format', () => {
|
|
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
|
+
});
|
|
197
182
|
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
}
|
|
183
|
+
test('generateDecisionsMd empty input', () => {
|
|
184
|
+
const md = generateDecisionsMd([]);
|
|
185
|
+
const parsed = parseDecisionsTable(md);
|
|
186
|
+
assert.deepStrictEqual(parsed.length, 0, 'empty decisions produces empty parse');
|
|
187
|
+
assert.ok(md.includes('| # | When | Scope'), 'still has table header even when empty');
|
|
188
|
+
});
|
|
216
189
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
190
|
+
test('generateDecisionsMd pipe escaping', () => {
|
|
191
|
+
const withPipe: Decision = {
|
|
192
|
+
seq: 1,
|
|
193
|
+
id: 'D001',
|
|
194
|
+
when_context: 'M001',
|
|
195
|
+
scope: 'arch',
|
|
196
|
+
decision: 'Choice A | Choice B comparison',
|
|
197
|
+
choice: 'A',
|
|
198
|
+
rationale: 'Better',
|
|
199
|
+
revisable: 'No',
|
|
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
|
+
});
|
|
220
208
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
209
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
210
|
+
// Round-Trip Tests: Requirements
|
|
211
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
212
|
+
|
|
213
|
+
test('generateRequirementsMd round-trip', () => {
|
|
214
|
+
const md = generateRequirementsMd(SAMPLE_REQUIREMENTS);
|
|
215
|
+
const parsed = parseRequirementsSections(md);
|
|
216
|
+
|
|
217
|
+
assert.deepStrictEqual(parsed.length, SAMPLE_REQUIREMENTS.length, 'requirements count matches');
|
|
218
|
+
|
|
219
|
+
for (const orig of SAMPLE_REQUIREMENTS) {
|
|
220
|
+
const rt = parsed.find(r => r.id === orig.id);
|
|
221
|
+
assert.ok(!!rt, `requirement ${orig.id} found in parsed output`);
|
|
222
|
+
if (rt) {
|
|
223
|
+
assert.deepStrictEqual(rt.class, orig.class, `requirement ${orig.id} class round-trips`);
|
|
224
|
+
assert.deepStrictEqual(rt.description, orig.description, `requirement ${orig.id} description round-trips`);
|
|
225
|
+
assert.deepStrictEqual(rt.why, orig.why, `requirement ${orig.id} why round-trips`);
|
|
226
|
+
assert.deepStrictEqual(rt.source, orig.source, `requirement ${orig.id} source round-trips`);
|
|
227
|
+
assert.deepStrictEqual(rt.primary_owner, orig.primary_owner, `requirement ${orig.id} primary_owner round-trips`);
|
|
228
|
+
assert.deepStrictEqual(rt.supporting_slices, orig.supporting_slices, `requirement ${orig.id} supporting_slices round-trips`);
|
|
229
|
+
if (orig.notes) {
|
|
230
|
+
assert.deepStrictEqual(rt.notes, orig.notes, `requirement ${orig.id} notes round-trips`);
|
|
231
|
+
}
|
|
241
232
|
}
|
|
242
233
|
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
234
|
+
});
|
|
245
235
|
|
|
246
|
-
|
|
236
|
+
test('generateRequirementsMd sections', () => {
|
|
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
|
+
});
|
|
247
245
|
|
|
248
|
-
{
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
246
|
+
test('generateRequirementsMd only populated sections', () => {
|
|
247
|
+
// Only active requirements — should only have Active section
|
|
248
|
+
const activeOnly = SAMPLE_REQUIREMENTS.filter(r => r.status === 'active');
|
|
249
|
+
const md = generateRequirementsMd(activeOnly);
|
|
250
|
+
assert.ok(md.includes('## Active'), 'has Active section');
|
|
251
|
+
assert.ok(!md.includes('## Validated'), 'no Validated section when no validated reqs');
|
|
252
|
+
assert.ok(!md.includes('## Deferred'), 'no Deferred section when no deferred reqs');
|
|
253
|
+
assert.ok(!md.includes('## Out of Scope'), 'no Out of Scope section when no out-of-scope reqs');
|
|
254
|
+
});
|
|
257
255
|
|
|
258
|
-
|
|
256
|
+
test('generateRequirementsMd empty input', () => {
|
|
257
|
+
const md = generateRequirementsMd([]);
|
|
258
|
+
const parsed = parseRequirementsSections(md);
|
|
259
|
+
assert.deepStrictEqual(parsed.length, 0, 'empty requirements produces empty parse');
|
|
260
|
+
});
|
|
259
261
|
|
|
260
|
-
|
|
261
|
-
//
|
|
262
|
-
|
|
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
|
-
}
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
263
|
+
// nextDecisionId Tests
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
269
265
|
|
|
270
|
-
|
|
266
|
+
test('nextDecisionId', async () => {
|
|
267
|
+
// Open in-memory DB
|
|
268
|
+
openDatabase(':memory:');
|
|
271
269
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const parsed = parseRequirementsSections(md);
|
|
275
|
-
assertEq(parsed.length, 0, 'empty requirements produces empty parse');
|
|
276
|
-
}
|
|
270
|
+
const id1 = await nextDecisionId();
|
|
271
|
+
assert.deepStrictEqual(id1, 'D001', 'first ID when no decisions exist');
|
|
277
272
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
// Insert some decisions
|
|
274
|
+
upsertDecision({
|
|
275
|
+
id: 'D001',
|
|
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
|
+
});
|
|
281
296
|
|
|
282
|
-
|
|
297
|
+
const id2 = await nextDecisionId();
|
|
298
|
+
assert.deepStrictEqual(id2, 'D006', 'next ID after D005 is D006');
|
|
283
299
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
openDatabase(':memory:');
|
|
300
|
+
closeDatabase();
|
|
301
|
+
});
|
|
287
302
|
|
|
288
|
-
|
|
289
|
-
|
|
303
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
304
|
+
// saveDecisionToDb Tests
|
|
305
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
290
306
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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,
|
|
313
|
-
});
|
|
307
|
+
test('saveDecisionToDb', async () => {
|
|
308
|
+
const tmpDir = makeTmpDir();
|
|
309
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
310
|
+
openDatabase(dbPath);
|
|
314
311
|
|
|
315
|
-
|
|
316
|
-
|
|
312
|
+
try {
|
|
313
|
+
const result = await saveDecisionToDb({
|
|
314
|
+
scope: 'arch',
|
|
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
|
+
});
|
|
317
360
|
|
|
318
|
-
|
|
319
|
-
|
|
361
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
362
|
+
// updateRequirementInDb Tests
|
|
363
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
320
364
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
365
|
+
test('updateRequirementInDb', async () => {
|
|
366
|
+
const tmpDir = makeTmpDir();
|
|
367
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
368
|
+
openDatabase(dbPath);
|
|
324
369
|
|
|
325
|
-
|
|
370
|
+
try {
|
|
371
|
+
// Seed a requirement
|
|
372
|
+
upsertRequirement({
|
|
373
|
+
id: 'R001',
|
|
374
|
+
class: 'core-capability',
|
|
375
|
+
status: 'active',
|
|
376
|
+
description: 'Test requirement',
|
|
377
|
+
why: 'Testing',
|
|
378
|
+
source: 'test',
|
|
379
|
+
primary_owner: 'M001/S01',
|
|
380
|
+
supporting_slices: 'none',
|
|
381
|
+
validation: 'unmapped',
|
|
382
|
+
notes: '',
|
|
383
|
+
full_content: '',
|
|
384
|
+
superseded_by: null,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Update it
|
|
388
|
+
await updateRequirementInDb('R001', {
|
|
389
|
+
status: 'validated',
|
|
390
|
+
validation: 'S01 — all tests pass',
|
|
391
|
+
notes: 'Validated in S01',
|
|
392
|
+
}, tmpDir);
|
|
393
|
+
|
|
394
|
+
// Verify DB state
|
|
395
|
+
const updated = getRequirementById('R001');
|
|
396
|
+
assert.ok(!!updated, 'requirement still exists after update');
|
|
397
|
+
assert.deepStrictEqual(updated?.status, 'validated', 'status updated in DB');
|
|
398
|
+
assert.deepStrictEqual(updated?.validation, 'S01 — all tests pass', 'validation updated in DB');
|
|
399
|
+
assert.deepStrictEqual(updated?.description, 'Test requirement', 'description preserved after update');
|
|
400
|
+
|
|
401
|
+
// Verify markdown file was written
|
|
402
|
+
const mdPath = path.join(tmpDir, '.gsd', 'REQUIREMENTS.md');
|
|
403
|
+
assert.ok(fs.existsSync(mdPath), 'REQUIREMENTS.md file created');
|
|
404
|
+
|
|
405
|
+
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
|
406
|
+
assert.ok(mdContent.includes('R001'), 'REQUIREMENTS.md contains requirement ID');
|
|
407
|
+
assert.ok(mdContent.includes('validated'), 'REQUIREMENTS.md shows updated status');
|
|
408
|
+
|
|
409
|
+
// Verify round-trip
|
|
410
|
+
const parsed = parseRequirementsSections(mdContent);
|
|
411
|
+
assert.deepStrictEqual(parsed.length, 1, 'parsed 1 requirement from written file');
|
|
412
|
+
assert.deepStrictEqual(parsed[0].status, 'validated', 'parsed status matches update');
|
|
413
|
+
} finally {
|
|
414
|
+
closeDatabase();
|
|
415
|
+
cleanupDir(tmpDir);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
326
418
|
|
|
327
|
-
{
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
419
|
+
test('updateRequirementInDb — not found', async () => {
|
|
420
|
+
const tmpDir = makeTmpDir();
|
|
421
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
422
|
+
openDatabase(dbPath);
|
|
331
423
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
}
|
|
424
|
+
try {
|
|
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
|
+
});
|
|
380
441
|
|
|
381
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
382
|
-
//
|
|
383
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
442
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
443
|
+
// saveArtifactToDb Tests
|
|
444
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
384
445
|
|
|
385
|
-
|
|
446
|
+
test('saveArtifactToDb', async () => {
|
|
447
|
+
const tmpDir = makeTmpDir();
|
|
448
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
449
|
+
openDatabase(dbPath);
|
|
386
450
|
|
|
387
|
-
{
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
451
|
+
try {
|
|
452
|
+
const content = '# Task Summary\n\nTest content\n';
|
|
453
|
+
await saveArtifactToDb({
|
|
454
|
+
path: 'milestones/M001/slices/S06/tasks/T01-SUMMARY.md',
|
|
455
|
+
artifact_type: 'SUMMARY',
|
|
456
|
+
content,
|
|
457
|
+
milestone_id: 'M001',
|
|
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',
|
|
477
|
+
);
|
|
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
|
+
}
|
|
484
|
+
});
|
|
391
485
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
});
|
|
486
|
+
test('saveArtifactToDb — shrinkage guard preserves larger existing file', async () => {
|
|
487
|
+
const tmpDir = makeTmpDir();
|
|
488
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
489
|
+
openDatabase(dbPath);
|
|
408
490
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
}
|
|
491
|
+
try {
|
|
492
|
+
const fullContent = '# Full Research\n\n' + 'x'.repeat(20000) + '\n';
|
|
493
|
+
const abbreviatedContent = '# Summary\n\nShort version.\n';
|
|
494
|
+
|
|
495
|
+
// Pre-create the file with full content (simulating a prior `write` tool call)
|
|
496
|
+
const relPath = 'milestones/M001/M001-RESEARCH.md';
|
|
497
|
+
const filePath = path.join(tmpDir, '.gsd', relPath);
|
|
498
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
499
|
+
fs.writeFileSync(filePath, fullContent);
|
|
500
|
+
|
|
501
|
+
// Call saveArtifactToDb with abbreviated content — should trigger shrinkage guard
|
|
502
|
+
await saveArtifactToDb({
|
|
503
|
+
path: relPath,
|
|
504
|
+
artifact_type: 'RESEARCH',
|
|
505
|
+
content: abbreviatedContent,
|
|
506
|
+
milestone_id: 'M001',
|
|
507
|
+
}, tmpDir);
|
|
508
|
+
|
|
509
|
+
// Disk file should be preserved (not overwritten)
|
|
510
|
+
assert.deepStrictEqual(
|
|
511
|
+
fs.readFileSync(filePath, 'utf-8'),
|
|
512
|
+
fullContent,
|
|
513
|
+
'disk file preserved — shrinkage guard prevented overwrite',
|
|
514
|
+
);
|
|
440
515
|
|
|
441
|
-
|
|
516
|
+
// DB should contain the full disk content, not the abbreviated content
|
|
517
|
+
const adapter = _getAdapter();
|
|
518
|
+
const row = adapter!
|
|
519
|
+
.prepare('SELECT full_content FROM artifacts WHERE path = ?')
|
|
520
|
+
.get(relPath);
|
|
521
|
+
assert.deepStrictEqual(
|
|
522
|
+
row!['full_content'],
|
|
523
|
+
fullContent,
|
|
524
|
+
'DB stores the richer disk content instead of abbreviated content',
|
|
525
|
+
);
|
|
526
|
+
} finally {
|
|
527
|
+
closeDatabase();
|
|
528
|
+
cleanupDir(tmpDir);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
442
531
|
|
|
443
|
-
{
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
532
|
+
test('saveArtifactToDb — allows overwrite when new content is similar size', async () => {
|
|
533
|
+
const tmpDir = makeTmpDir();
|
|
534
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
535
|
+
openDatabase(dbPath);
|
|
447
536
|
|
|
448
|
-
try {
|
|
449
|
-
let threw = false;
|
|
450
537
|
try {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
538
|
+
const oldContent = '# Summary v1\n\nOriginal content here.\n';
|
|
539
|
+
const newContent = '# Summary v2\n\nUpdated content here with more details.\n';
|
|
540
|
+
|
|
541
|
+
const relPath = 'milestones/M001/M001-SUMMARY.md';
|
|
542
|
+
const filePath = path.join(tmpDir, '.gsd', relPath);
|
|
543
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
544
|
+
fs.writeFileSync(filePath, oldContent);
|
|
545
|
+
|
|
546
|
+
await saveArtifactToDb({
|
|
547
|
+
path: relPath,
|
|
548
|
+
artifact_type: 'SUMMARY',
|
|
549
|
+
content: newContent,
|
|
550
|
+
milestone_id: 'M001',
|
|
551
|
+
}, tmpDir);
|
|
552
|
+
|
|
553
|
+
// Disk file should be updated (new content is >=50% of old size)
|
|
554
|
+
assert.deepStrictEqual(
|
|
555
|
+
fs.readFileSync(filePath, 'utf-8'),
|
|
556
|
+
newContent,
|
|
557
|
+
'disk file updated when new content is similar size',
|
|
457
558
|
);
|
|
559
|
+
} finally {
|
|
560
|
+
closeDatabase();
|
|
561
|
+
cleanupDir(tmpDir);
|
|
458
562
|
}
|
|
459
|
-
|
|
460
|
-
} finally {
|
|
461
|
-
closeDatabase();
|
|
462
|
-
cleanupDir(tmpDir);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
467
|
-
// saveArtifactToDb Tests
|
|
468
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
563
|
+
});
|
|
469
564
|
|
|
470
|
-
|
|
565
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
566
|
+
// Full Round-Trip: DB → Markdown → Parse → Compare
|
|
567
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
568
|
+
|
|
569
|
+
test('Full DB round-trip: decisions', () => {
|
|
570
|
+
openDatabase(':memory:');
|
|
571
|
+
|
|
572
|
+
// Insert via DB
|
|
573
|
+
for (const d of SAMPLE_DECISIONS) {
|
|
574
|
+
upsertDecision({
|
|
575
|
+
id: d.id,
|
|
576
|
+
when_context: d.when_context,
|
|
577
|
+
scope: d.scope,
|
|
578
|
+
decision: d.decision,
|
|
579
|
+
choice: d.choice,
|
|
580
|
+
rationale: d.rationale,
|
|
581
|
+
revisable: d.revisable,
|
|
582
|
+
made_by: d.made_by,
|
|
583
|
+
superseded_by: d.superseded_by,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
471
586
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
587
|
+
// Generate markdown from DB state
|
|
588
|
+
const adapter = _getAdapter()!;
|
|
589
|
+
const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
|
|
590
|
+
const dbDecisions: Decision[] = rows.map(row => ({
|
|
591
|
+
seq: row['seq'] as number,
|
|
592
|
+
id: row['id'] as string,
|
|
593
|
+
when_context: row['when_context'] as string,
|
|
594
|
+
scope: row['scope'] as string,
|
|
595
|
+
decision: row['decision'] as string,
|
|
596
|
+
choice: row['choice'] as string,
|
|
597
|
+
rationale: row['rationale'] as string,
|
|
598
|
+
revisable: row['revisable'] as string,
|
|
599
|
+
made_by: (row['made_by'] as string as import('../types.js').DecisionMadeBy) ?? 'agent',
|
|
600
|
+
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
601
|
+
}));
|
|
602
|
+
|
|
603
|
+
const md = generateDecisionsMd(dbDecisions);
|
|
604
|
+
const parsed = parseDecisionsTable(md);
|
|
605
|
+
|
|
606
|
+
assert.deepStrictEqual(parsed.length, SAMPLE_DECISIONS.length, 'DB round-trip decision count');
|
|
607
|
+
for (const orig of SAMPLE_DECISIONS) {
|
|
608
|
+
const rt = parsed.find(p => p.id === orig.id);
|
|
609
|
+
assert.ok(!!rt, `DB round-trip: ${orig.id} found`);
|
|
610
|
+
if (rt) {
|
|
611
|
+
assert.deepStrictEqual(rt.scope, orig.scope, `DB round-trip: ${orig.id} scope`);
|
|
612
|
+
assert.deepStrictEqual(rt.choice, orig.choice, `DB round-trip: ${orig.id} choice`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
476
615
|
|
|
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 {
|
|
507
616
|
closeDatabase();
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
513
|
-
// Full Round-Trip: DB → Markdown → Parse → Compare
|
|
514
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
515
|
-
|
|
516
|
-
console.log('\n── Full DB round-trip: decisions ──');
|
|
617
|
+
});
|
|
517
618
|
|
|
518
|
-
{
|
|
519
|
-
|
|
619
|
+
test('Full DB round-trip: requirements', () => {
|
|
620
|
+
openDatabase(':memory:');
|
|
520
621
|
|
|
521
|
-
|
|
522
|
-
|
|
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`);
|
|
622
|
+
for (const r of SAMPLE_REQUIREMENTS) {
|
|
623
|
+
upsertRequirement(r);
|
|
562
624
|
}
|
|
563
|
-
}
|
|
564
625
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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`);
|
|
626
|
+
const adapter = _getAdapter()!;
|
|
627
|
+
const rows = adapter.prepare('SELECT * FROM requirements ORDER BY id').all();
|
|
628
|
+
const dbReqs: Requirement[] = rows.map(row => ({
|
|
629
|
+
id: row['id'] as string,
|
|
630
|
+
class: row['class'] as string,
|
|
631
|
+
status: row['status'] as string,
|
|
632
|
+
description: row['description'] as string,
|
|
633
|
+
why: row['why'] as string,
|
|
634
|
+
source: row['source'] as string,
|
|
635
|
+
primary_owner: row['primary_owner'] as string,
|
|
636
|
+
supporting_slices: row['supporting_slices'] as string,
|
|
637
|
+
validation: row['validation'] as string,
|
|
638
|
+
notes: row['notes'] as string,
|
|
639
|
+
full_content: row['full_content'] as string,
|
|
640
|
+
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
641
|
+
}));
|
|
642
|
+
|
|
643
|
+
const md = generateRequirementsMd(dbReqs);
|
|
644
|
+
const parsed = parseRequirementsSections(md);
|
|
645
|
+
|
|
646
|
+
assert.deepStrictEqual(parsed.length, SAMPLE_REQUIREMENTS.length, 'DB round-trip requirement count');
|
|
647
|
+
for (const orig of SAMPLE_REQUIREMENTS) {
|
|
648
|
+
const rt = parsed.find(p => p.id === orig.id);
|
|
649
|
+
assert.ok(!!rt, `DB round-trip: ${orig.id} found`);
|
|
650
|
+
if (rt) {
|
|
651
|
+
assert.deepStrictEqual(rt.class, orig.class, `DB round-trip: ${orig.id} class`);
|
|
652
|
+
assert.deepStrictEqual(rt.description, orig.description, `DB round-trip: ${orig.id} description`);
|
|
653
|
+
}
|
|
604
654
|
}
|
|
605
|
-
}
|
|
606
655
|
|
|
607
|
-
|
|
608
|
-
}
|
|
656
|
+
closeDatabase();
|
|
657
|
+
});
|
|
609
658
|
|
|
610
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
659
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
611
660
|
|
|
612
|
-
|
|
661
|
+
});
|