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,14 +1,14 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
import { parseRoadmap, parsePlan } from '../parsers-legacy.ts';
|
|
2
4
|
import { parseTaskPlanFile, parseSummary, parseContinue, parseRequirementCounts, parseSecretsManifest, formatSecretsManifest } from '../files.ts';
|
|
3
|
-
import { createTestContext } from './test-helpers.ts';
|
|
4
|
-
|
|
5
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
6
5
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
6
|
// parseRoadmap tests
|
|
8
7
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
{
|
|
9
|
+
|
|
10
|
+
describe('parsers', () => {
|
|
11
|
+
test('parseRoadmap: full roadmap', () => {
|
|
12
12
|
const content = `# M001: GSD Extension — Hierarchical Planning
|
|
13
13
|
|
|
14
14
|
**Vision:** Build a structured planning system for coding agents.
|
|
@@ -57,44 +57,43 @@ Consumes from S03:
|
|
|
57
57
|
|
|
58
58
|
const r = parseRoadmap(content);
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
assert.deepStrictEqual(r.title, 'M001: GSD Extension — Hierarchical Planning', 'roadmap title');
|
|
61
|
+
assert.deepStrictEqual(r.vision, 'Build a structured planning system for coding agents.', 'roadmap vision');
|
|
62
|
+
assert.deepStrictEqual(r.successCriteria.length, 3, 'success criteria count');
|
|
63
|
+
assert.deepStrictEqual(r.successCriteria[0], 'All parsers have test coverage', 'first success criterion');
|
|
64
|
+
assert.deepStrictEqual(r.successCriteria[2], 'State derivation works correctly', 'third success criterion');
|
|
65
65
|
|
|
66
66
|
// Slices
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
67
|
+
assert.deepStrictEqual(r.slices.length, 3, 'slice count');
|
|
68
|
+
|
|
69
|
+
assert.deepStrictEqual(r.slices[0].id, 'S01', 'S01 id');
|
|
70
|
+
assert.deepStrictEqual(r.slices[0].title, 'Types + File I/O', 'S01 title');
|
|
71
|
+
assert.deepStrictEqual(r.slices[0].risk, 'low', 'S01 risk');
|
|
72
|
+
assert.deepStrictEqual(r.slices[0].depends, [], 'S01 depends');
|
|
73
|
+
assert.deepStrictEqual(r.slices[0].done, true, 'S01 done');
|
|
74
|
+
assert.deepStrictEqual(r.slices[0].demo, 'All types defined and parsers work.', 'S01 demo');
|
|
75
|
+
|
|
76
|
+
assert.deepStrictEqual(r.slices[1].id, 'S02', 'S02 id');
|
|
77
|
+
assert.deepStrictEqual(r.slices[1].title, 'State Derivation', 'S02 title');
|
|
78
|
+
assert.deepStrictEqual(r.slices[1].risk, 'medium', 'S02 risk');
|
|
79
|
+
assert.deepStrictEqual(r.slices[1].depends, ['S01'], 'S02 depends');
|
|
80
|
+
assert.deepStrictEqual(r.slices[1].done, false, 'S02 done');
|
|
81
|
+
|
|
82
|
+
assert.deepStrictEqual(r.slices[2].id, 'S03', 'S03 id');
|
|
83
|
+
assert.deepStrictEqual(r.slices[2].risk, 'high', 'S03 risk');
|
|
84
|
+
assert.deepStrictEqual(r.slices[2].depends, ['S01', 'S02'], 'S03 depends');
|
|
85
|
+
assert.deepStrictEqual(r.slices[2].done, false, 'S03 done');
|
|
86
86
|
|
|
87
87
|
// Boundary map
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
88
|
+
assert.deepStrictEqual(r.boundaryMap.length, 2, 'boundary map entry count');
|
|
89
|
+
assert.deepStrictEqual(r.boundaryMap[0].fromSlice, 'S01', 'bm[0] from');
|
|
90
|
+
assert.deepStrictEqual(r.boundaryMap[0].toSlice, 'S02', 'bm[0] to');
|
|
91
|
+
assert.ok(r.boundaryMap[0].produces.includes('types.ts'), 'bm[0] produces mentions types.ts');
|
|
92
|
+
assert.deepStrictEqual(r.boundaryMap[1].fromSlice, 'S02', 'bm[1] from');
|
|
93
|
+
assert.deepStrictEqual(r.boundaryMap[1].toSlice, 'S03', 'bm[1] to');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('parseRoadmap: empty slices section', () => {
|
|
98
97
|
const content = `# M002: Empty Milestone
|
|
99
98
|
|
|
100
99
|
**Vision:** Nothing yet.
|
|
@@ -105,13 +104,12 @@ console.log('\n=== parseRoadmap: empty slices section ===');
|
|
|
105
104
|
`;
|
|
106
105
|
|
|
107
106
|
const r = parseRoadmap(content);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
107
|
+
assert.deepStrictEqual(r.title, 'M002: Empty Milestone', 'title with empty slices');
|
|
108
|
+
assert.deepStrictEqual(r.slices.length, 0, 'no slices parsed');
|
|
109
|
+
assert.deepStrictEqual(r.boundaryMap.length, 0, 'no boundary map entries');
|
|
110
|
+
});
|
|
112
111
|
|
|
113
|
-
|
|
114
|
-
{
|
|
112
|
+
test('parseRoadmap: malformed checkbox lines', () => {
|
|
115
113
|
// Lines that don't match the expected bold pattern should be skipped
|
|
116
114
|
const content = `# M003: Malformed
|
|
117
115
|
|
|
@@ -130,15 +128,14 @@ console.log('\n=== parseRoadmap: malformed checkbox lines ===');
|
|
|
130
128
|
|
|
131
129
|
const r = parseRoadmap(content);
|
|
132
130
|
// Only S02 and S03 should be parsed (malformed lines without bold markers are skipped)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
{
|
|
131
|
+
assert.deepStrictEqual(r.slices.length, 2, 'only valid slices parsed from malformed input');
|
|
132
|
+
assert.deepStrictEqual(r.slices[0].id, 'S02', 'first valid slice is S02');
|
|
133
|
+
assert.deepStrictEqual(r.slices[0].done, true, 'S02 done');
|
|
134
|
+
assert.deepStrictEqual(r.slices[1].id, 'S03', 'second valid slice is S03');
|
|
135
|
+
assert.deepStrictEqual(r.slices[1].depends, ['S02'], 'S03 depends on S02');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('parseRoadmap: lowercase vs uppercase X for done', () => {
|
|
142
139
|
const content = `# M004: Case Test
|
|
143
140
|
|
|
144
141
|
**Vision:** Test X case sensitivity.
|
|
@@ -156,14 +153,13 @@ console.log('\n=== parseRoadmap: lowercase vs uppercase X for done ===');
|
|
|
156
153
|
`;
|
|
157
154
|
|
|
158
155
|
const r = parseRoadmap(content);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
{
|
|
156
|
+
assert.deepStrictEqual(r.slices.length, 3, 'all three slices parsed');
|
|
157
|
+
assert.deepStrictEqual(r.slices[0].done, true, 'lowercase x is done');
|
|
158
|
+
assert.deepStrictEqual(r.slices[1].done, true, 'uppercase X is done');
|
|
159
|
+
assert.deepStrictEqual(r.slices[2].done, false, 'space is not done');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('parseRoadmap: missing boundary map', () => {
|
|
167
163
|
const content = `# M005: No Boundary Map
|
|
168
164
|
|
|
169
165
|
**Vision:** A roadmap without a boundary map section.
|
|
@@ -180,29 +176,27 @@ console.log('\n=== parseRoadmap: missing boundary map ===');
|
|
|
180
176
|
`;
|
|
181
177
|
|
|
182
178
|
const r = parseRoadmap(content);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
{
|
|
179
|
+
assert.deepStrictEqual(r.title, 'M005: No Boundary Map', 'title');
|
|
180
|
+
assert.deepStrictEqual(r.slices.length, 1, 'one slice');
|
|
181
|
+
assert.deepStrictEqual(r.boundaryMap.length, 0, 'empty boundary map when section missing');
|
|
182
|
+
assert.deepStrictEqual(r.successCriteria.length, 1, 'one success criterion');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('parseRoadmap: no sections at all', () => {
|
|
191
186
|
const content = `# M006: Bare Minimum
|
|
192
187
|
|
|
193
188
|
Just a title and nothing else.
|
|
194
189
|
`;
|
|
195
190
|
|
|
196
191
|
const r = parseRoadmap(content);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
{
|
|
192
|
+
assert.deepStrictEqual(r.title, 'M006: Bare Minimum', 'title from bare roadmap');
|
|
193
|
+
assert.deepStrictEqual(r.vision, '', 'empty vision');
|
|
194
|
+
assert.deepStrictEqual(r.successCriteria.length, 0, 'no success criteria');
|
|
195
|
+
assert.deepStrictEqual(r.slices.length, 0, 'no slices');
|
|
196
|
+
assert.deepStrictEqual(r.boundaryMap.length, 0, 'no boundary map');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('parseRoadmap: slice with no demo blockquote', () => {
|
|
206
200
|
const content = `# M007: No Demo
|
|
207
201
|
|
|
208
202
|
**Vision:** Testing slices without demo lines.
|
|
@@ -214,13 +208,12 @@ console.log('\n=== parseRoadmap: slice with no demo blockquote ===');
|
|
|
214
208
|
`;
|
|
215
209
|
|
|
216
210
|
const r = parseRoadmap(content);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
211
|
+
assert.deepStrictEqual(r.slices.length, 2, 'two slices without demos');
|
|
212
|
+
assert.deepStrictEqual(r.slices[0].demo, '', 'S01 demo empty');
|
|
213
|
+
assert.deepStrictEqual(r.slices[1].demo, '', 'S02 demo empty');
|
|
214
|
+
});
|
|
221
215
|
|
|
222
|
-
|
|
223
|
-
{
|
|
216
|
+
test('parseRoadmap: missing risk defaults to low', () => {
|
|
224
217
|
const content = `# M008: Default Risk
|
|
225
218
|
|
|
226
219
|
**Vision:** Test default risk.
|
|
@@ -232,16 +225,14 @@ console.log('\n=== parseRoadmap: missing risk defaults to low ===');
|
|
|
232
225
|
`;
|
|
233
226
|
|
|
234
227
|
const r = parseRoadmap(content);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
228
|
+
assert.deepStrictEqual(r.slices.length, 1, 'one slice');
|
|
229
|
+
assert.deepStrictEqual(r.slices[0].risk, 'low', 'default risk is low');
|
|
230
|
+
});
|
|
238
231
|
|
|
239
232
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
240
233
|
// parsePlan tests
|
|
241
234
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
242
|
-
|
|
243
|
-
console.log('\n=== parsePlan: full plan ===');
|
|
244
|
-
{
|
|
235
|
+
test('parsePlan: full plan', () => {
|
|
245
236
|
const content = `---
|
|
246
237
|
estimated_steps: 6
|
|
247
238
|
estimated_files: 3
|
|
@@ -277,42 +268,41 @@ skills_used:
|
|
|
277
268
|
`;
|
|
278
269
|
|
|
279
270
|
const taskPlan = parseTaskPlanFile(content);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
271
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, 6, 'task plan frontmatter estimated_steps');
|
|
272
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, 3, 'task plan frontmatter estimated_files');
|
|
273
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 2, 'task plan frontmatter skills_used count');
|
|
274
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used[0], 'typescript', 'first task plan skill');
|
|
275
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used[1], 'testing', 'second task plan skill');
|
|
285
276
|
|
|
286
277
|
const p = parsePlan(content);
|
|
287
278
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
279
|
+
assert.deepStrictEqual(p.id, 'S01', 'plan id');
|
|
280
|
+
assert.deepStrictEqual(p.title, 'Parser Test Suite', 'plan title');
|
|
281
|
+
assert.deepStrictEqual(p.goal, 'All 5 parsers have test coverage with edge cases.', 'plan goal');
|
|
282
|
+
assert.deepStrictEqual(p.demo, '`node --test tests/parsers.test.ts` passes with zero failures.', 'plan demo');
|
|
292
283
|
|
|
293
284
|
// Must-haves
|
|
294
|
-
|
|
295
|
-
|
|
285
|
+
assert.deepStrictEqual(p.mustHaves.length, 3, 'must-have count');
|
|
286
|
+
assert.deepStrictEqual(p.mustHaves[0], 'parseRoadmap tests cover happy path and edge cases', 'first must-have');
|
|
296
287
|
|
|
297
288
|
// Tasks
|
|
298
|
-
|
|
289
|
+
assert.deepStrictEqual(p.tasks.length, 2, 'task count');
|
|
299
290
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
291
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'T01 id');
|
|
292
|
+
assert.deepStrictEqual(p.tasks[0].title, 'Test parseRoadmap and parsePlan', 'T01 title');
|
|
293
|
+
assert.deepStrictEqual(p.tasks[0].done, false, 'T01 not done');
|
|
294
|
+
assert.ok(p.tasks[0].description.includes('comprehensive tests'), 'T01 description content');
|
|
304
295
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
296
|
+
assert.deepStrictEqual(p.tasks[1].id, 'T02', 'T02 id');
|
|
297
|
+
assert.deepStrictEqual(p.tasks[1].title, 'Test parseSummary and parseContinue', 'T02 title');
|
|
298
|
+
assert.deepStrictEqual(p.tasks[1].done, true, 'T02 done');
|
|
308
299
|
|
|
309
300
|
// Files likely touched
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
301
|
+
assert.deepStrictEqual(p.filesLikelyTouched.length, 3, 'files likely touched count');
|
|
302
|
+
assert.ok(p.filesLikelyTouched[0].includes('tests/parsers.test.ts'), 'first file');
|
|
303
|
+
});
|
|
313
304
|
|
|
314
|
-
|
|
315
|
-
{
|
|
305
|
+
test('parseTaskPlanFile: defaults missing frontmatter fields', () => {
|
|
316
306
|
const content = `# T01: Minimal task plan
|
|
317
307
|
|
|
318
308
|
## Description
|
|
@@ -321,13 +311,12 @@ No frontmatter here.
|
|
|
321
311
|
`;
|
|
322
312
|
|
|
323
313
|
const taskPlan = parseTaskPlanFile(content);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
314
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, undefined, 'estimated_steps defaults undefined');
|
|
315
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, undefined, 'estimated_files defaults undefined');
|
|
316
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 0, 'skills_used defaults empty array');
|
|
317
|
+
});
|
|
328
318
|
|
|
329
|
-
|
|
330
|
-
{
|
|
319
|
+
test('parseTaskPlanFile: accepts scalar skills_used and numeric strings', () => {
|
|
331
320
|
const content = `---
|
|
332
321
|
estimated_steps: "9"
|
|
333
322
|
estimated_files: "4"
|
|
@@ -338,14 +327,13 @@ skills_used: react-best-practices
|
|
|
338
327
|
`;
|
|
339
328
|
|
|
340
329
|
const taskPlan = parseTaskPlanFile(content);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
{
|
|
330
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, 9, 'string estimated_steps parsed');
|
|
331
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, 4, 'string estimated_files parsed');
|
|
332
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 1, 'scalar skills_used normalized to array');
|
|
333
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used[0], 'react-best-practices', 'scalar skill preserved');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('parseTaskPlanFile: filters blank skills_used items', () => {
|
|
349
337
|
const content = `---
|
|
350
338
|
skills_used:
|
|
351
339
|
- react
|
|
@@ -357,13 +345,12 @@ skills_used:
|
|
|
357
345
|
`;
|
|
358
346
|
|
|
359
347
|
const taskPlan = parseTaskPlanFile(content);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
348
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used.length, 2, 'blank skill entries removed');
|
|
349
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used[0], 'react', 'first remaining skill');
|
|
350
|
+
assert.deepStrictEqual(taskPlan.frontmatter.skills_used[1], 'testing', 'second remaining skill');
|
|
351
|
+
});
|
|
364
352
|
|
|
365
|
-
|
|
366
|
-
{
|
|
353
|
+
test('parseTaskPlanFile: invalid numeric frontmatter ignored', () => {
|
|
367
354
|
const content = `---
|
|
368
355
|
estimated_steps: many
|
|
369
356
|
estimated_files: unknown
|
|
@@ -373,12 +360,11 @@ estimated_files: unknown
|
|
|
373
360
|
`;
|
|
374
361
|
|
|
375
362
|
const taskPlan = parseTaskPlanFile(content);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
363
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_steps, undefined, 'invalid estimated_steps ignored');
|
|
364
|
+
assert.deepStrictEqual(taskPlan.frontmatter.estimated_files, undefined, 'invalid estimated_files ignored');
|
|
365
|
+
});
|
|
379
366
|
|
|
380
|
-
|
|
381
|
-
{
|
|
367
|
+
test('parseTaskPlanFile: parsePlan ignores task-plan frontmatter', () => {
|
|
382
368
|
const content = `---
|
|
383
369
|
estimated_steps: 2
|
|
384
370
|
estimated_files: 1
|
|
@@ -398,12 +384,11 @@ skills_used:
|
|
|
398
384
|
`;
|
|
399
385
|
|
|
400
386
|
const p = parsePlan(content);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
387
|
+
assert.deepStrictEqual(p.id, 'S11', 'plan id still parsed with frontmatter');
|
|
388
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'task still parsed with frontmatter');
|
|
389
|
+
});
|
|
404
390
|
|
|
405
|
-
|
|
406
|
-
{
|
|
391
|
+
test('parsePlan: multi-line task description concatenation', () => {
|
|
407
392
|
const content = `# S02: Multi-line Test
|
|
408
393
|
|
|
409
394
|
**Goal:** Test multi-line descriptions.
|
|
@@ -430,16 +415,15 @@ console.log('\n=== parsePlan: multi-line task description concatenation ===');
|
|
|
430
415
|
|
|
431
416
|
const p = parsePlan(content);
|
|
432
417
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
418
|
+
assert.deepStrictEqual(p.tasks.length, 2, 'two tasks');
|
|
419
|
+
assert.ok(p.tasks[0].description.includes('First line'), 'T01 desc has first line');
|
|
420
|
+
assert.ok(p.tasks[0].description.includes('Second line'), 'T01 desc has second line');
|
|
421
|
+
assert.ok(p.tasks[0].description.includes('Third line'), 'T01 desc has third line');
|
|
422
|
+
assert.ok(p.tasks[0].description.includes('description. Second'), 'lines joined with space');
|
|
423
|
+
assert.deepStrictEqual(p.tasks[1].description, 'Just one line.', 'T02 single-line desc');
|
|
424
|
+
});
|
|
440
425
|
|
|
441
|
-
|
|
442
|
-
{
|
|
426
|
+
test('parsePlan: frontmatter does not pollute task descriptions', () => {
|
|
443
427
|
const content = `---
|
|
444
428
|
estimated_steps: 2
|
|
445
429
|
estimated_files: 1
|
|
@@ -457,12 +441,11 @@ skills_used:
|
|
|
457
441
|
`;
|
|
458
442
|
|
|
459
443
|
const p = parsePlan(content);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
444
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'one task parsed with frontmatter');
|
|
445
|
+
assert.deepStrictEqual(p.tasks[0].description, 'First line of description. Second line of description.', 'frontmatter excluded from description');
|
|
446
|
+
});
|
|
463
447
|
|
|
464
|
-
|
|
465
|
-
{
|
|
448
|
+
test('parsePlan: task with missing estimate', () => {
|
|
466
449
|
const content = `# S03: No Estimate
|
|
467
450
|
|
|
468
451
|
**Goal:** Handle tasks without estimates.
|
|
@@ -478,15 +461,14 @@ console.log('\n=== parsePlan: task with missing estimate ===');
|
|
|
478
461
|
`;
|
|
479
462
|
|
|
480
463
|
const p = parsePlan(content);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
{
|
|
464
|
+
assert.deepStrictEqual(p.tasks.length, 2, 'two tasks parsed');
|
|
465
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'T01 id');
|
|
466
|
+
assert.deepStrictEqual(p.tasks[0].title, 'No Estimate Task', 'T01 title without estimate');
|
|
467
|
+
assert.deepStrictEqual(p.tasks[0].done, false, 'T01 not done');
|
|
468
|
+
assert.deepStrictEqual(p.tasks[1].id, 'T02', 'T02 id');
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test('parsePlan: empty tasks section', () => {
|
|
490
472
|
const content = `# S04: Empty Tasks
|
|
491
473
|
|
|
492
474
|
**Goal:** No tasks yet.
|
|
@@ -504,14 +486,13 @@ console.log('\n=== parsePlan: empty tasks section ===');
|
|
|
504
486
|
`;
|
|
505
487
|
|
|
506
488
|
const p = parsePlan(content);
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
{
|
|
489
|
+
assert.deepStrictEqual(p.id, 'S04', 'plan id with empty tasks');
|
|
490
|
+
assert.deepStrictEqual(p.tasks.length, 0, 'no tasks');
|
|
491
|
+
assert.deepStrictEqual(p.mustHaves.length, 1, 'one must-have');
|
|
492
|
+
assert.deepStrictEqual(p.filesLikelyTouched.length, 1, 'one file');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test('parsePlan: no H1', () => {
|
|
515
496
|
const content = `**Goal:** A plan without a heading.
|
|
516
497
|
**Demo:** Still parses.
|
|
517
498
|
|
|
@@ -522,15 +503,14 @@ console.log('\n=== parsePlan: no H1 ===');
|
|
|
522
503
|
`;
|
|
523
504
|
|
|
524
505
|
const p = parsePlan(content);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
{
|
|
506
|
+
assert.deepStrictEqual(p.id, '', 'empty id without H1');
|
|
507
|
+
assert.deepStrictEqual(p.title, '', 'empty title without H1');
|
|
508
|
+
assert.deepStrictEqual(p.goal, 'A plan without a heading.', 'goal still parsed');
|
|
509
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'task still parsed');
|
|
510
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test('parsePlan: task estimate backtick in description', () => {
|
|
534
514
|
const content = `# S05: Estimate Handling
|
|
535
515
|
|
|
536
516
|
**Goal:** Test estimate text handling.
|
|
@@ -543,14 +523,13 @@ console.log('\n=== parsePlan: task estimate backtick in description ===');
|
|
|
543
523
|
`;
|
|
544
524
|
|
|
545
525
|
const p = parsePlan(content);
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
{
|
|
526
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'one task');
|
|
527
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
|
|
528
|
+
assert.deepStrictEqual(p.tasks[0].title, 'With Estimate', 'title excludes estimate');
|
|
529
|
+
assert.ok(p.tasks[0].description.includes('Main description'), 'description from continuation line');
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('parsePlan: uppercase X for done', () => {
|
|
554
533
|
const content = `# S06: Case Test
|
|
555
534
|
|
|
556
535
|
**Goal:** Test case.
|
|
@@ -566,12 +545,11 @@ console.log('\n=== parsePlan: uppercase X for done ===');
|
|
|
566
545
|
`;
|
|
567
546
|
|
|
568
547
|
const p = parsePlan(content);
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
548
|
+
assert.deepStrictEqual(p.tasks[0].done, true, 'uppercase X is done');
|
|
549
|
+
assert.deepStrictEqual(p.tasks[1].done, true, 'lowercase x is done');
|
|
550
|
+
});
|
|
572
551
|
|
|
573
|
-
|
|
574
|
-
{
|
|
552
|
+
test('parsePlan: no Must-Haves section', () => {
|
|
575
553
|
const content = `# S07: No Must-Haves
|
|
576
554
|
|
|
577
555
|
**Goal:** Test missing must-haves.
|
|
@@ -584,12 +562,11 @@ console.log('\n=== parsePlan: no Must-Haves section ===');
|
|
|
584
562
|
`;
|
|
585
563
|
|
|
586
564
|
const p = parsePlan(content);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
565
|
+
assert.deepStrictEqual(p.mustHaves.length, 0, 'empty must-haves');
|
|
566
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'task still parsed');
|
|
567
|
+
});
|
|
590
568
|
|
|
591
|
-
|
|
592
|
-
{
|
|
569
|
+
test('parsePlan: no Files Likely Touched section', () => {
|
|
593
570
|
const content = `# S08: No Files
|
|
594
571
|
|
|
595
572
|
**Goal:** Test missing files section.
|
|
@@ -602,11 +579,10 @@ console.log('\n=== parsePlan: no Files Likely Touched section ===');
|
|
|
602
579
|
`;
|
|
603
580
|
|
|
604
581
|
const p = parsePlan(content);
|
|
605
|
-
|
|
606
|
-
}
|
|
582
|
+
assert.deepStrictEqual(p.filesLikelyTouched.length, 0, 'empty files likely touched');
|
|
583
|
+
});
|
|
607
584
|
|
|
608
|
-
|
|
609
|
-
{
|
|
585
|
+
test('parsePlan: old-format task entries (no sublines)', () => {
|
|
610
586
|
const content = `# S09: Old Format
|
|
611
587
|
|
|
612
588
|
**Goal:** Test old-format compatibility.
|
|
@@ -619,16 +595,15 @@ console.log('\n=== parsePlan: old-format task entries (no sublines) ===');
|
|
|
619
595
|
`;
|
|
620
596
|
|
|
621
597
|
const p = parsePlan(content);
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
{
|
|
598
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'one task parsed');
|
|
599
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
|
|
600
|
+
assert.deepStrictEqual(p.tasks[0].title, 'Classic Task', 'task title');
|
|
601
|
+
assert.deepStrictEqual(p.tasks[0].done, false, 'task not done');
|
|
602
|
+
assert.deepStrictEqual(p.tasks[0].files, undefined, 'files is undefined for old-format entry');
|
|
603
|
+
assert.deepStrictEqual(p.tasks[0].verify, undefined, 'verify is undefined for old-format entry');
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test('parsePlan: new-format task entries with Files and Verify sublines', () => {
|
|
632
607
|
const content = `# S10: New Format
|
|
633
608
|
|
|
634
609
|
**Goal:** Test new-format subline extraction.
|
|
@@ -643,18 +618,17 @@ console.log('\n=== parsePlan: new-format task entries with Files and Verify subl
|
|
|
643
618
|
`;
|
|
644
619
|
|
|
645
620
|
const p = parsePlan(content);
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
{
|
|
621
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'one task parsed');
|
|
622
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'task id');
|
|
623
|
+
assert.ok(Array.isArray(p.tasks[0].files), 'files is an array');
|
|
624
|
+
assert.deepStrictEqual(p.tasks[0].files!.length, 2, 'files array has two entries');
|
|
625
|
+
assert.deepStrictEqual(p.tasks[0].files![0], 'types.ts', 'first file is types.ts');
|
|
626
|
+
assert.deepStrictEqual(p.tasks[0].files![1], 'files.ts', 'second file is files.ts');
|
|
627
|
+
assert.deepStrictEqual(p.tasks[0].verify, 'run the test suite', 'verify string extracted correctly');
|
|
628
|
+
assert.ok(p.tasks[0].description.includes('Why: because we need typed plan entries'), 'Why line accumulates into description');
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test('parsePlan: heading-style task entries (### T01 -- Title)', () => {
|
|
658
632
|
const content = `# S11: Heading Style
|
|
659
633
|
|
|
660
634
|
**Goal:** Test heading-style task parsing.
|
|
@@ -674,20 +648,19 @@ Some description for the second task.
|
|
|
674
648
|
`;
|
|
675
649
|
|
|
676
650
|
const p = parsePlan(content);
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
{
|
|
651
|
+
assert.deepStrictEqual(p.tasks.length, 2, 'heading-style task count');
|
|
652
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'heading T01 id');
|
|
653
|
+
assert.deepStrictEqual(p.tasks[0].title, 'Implement feature', 'heading T01 title');
|
|
654
|
+
assert.deepStrictEqual(p.tasks[0].done, false, 'heading T01 not done (headings have no checkbox)');
|
|
655
|
+
assert.deepStrictEqual(p.tasks[0].files![0], 'src/feature.ts', 'heading T01 files extracted');
|
|
656
|
+
assert.deepStrictEqual(p.tasks[0].verify, 'npm test', 'heading T01 verify extracted');
|
|
657
|
+
assert.deepStrictEqual(p.tasks[1].id, 'T02', 'heading T02 id');
|
|
658
|
+
assert.deepStrictEqual(p.tasks[1].title, 'Write tests', 'heading T02 title');
|
|
659
|
+
assert.deepStrictEqual(p.tasks[1].estimate, '1h', 'heading T02 estimate');
|
|
660
|
+
assert.ok(p.tasks[1].description.includes('Some description'), 'heading T02 description');
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test('parsePlan: heading-style with colon separator (### T01: Title)', () => {
|
|
691
664
|
const content = `# S12: Heading Colon Style
|
|
692
665
|
|
|
693
666
|
**Goal:** Test colon-separated heading tasks.
|
|
@@ -703,16 +676,15 @@ console.log('\n=== parsePlan: heading-style with colon separator (### T01: Title
|
|
|
703
676
|
`;
|
|
704
677
|
|
|
705
678
|
const p = parsePlan(content);
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
{
|
|
679
|
+
assert.deepStrictEqual(p.tasks.length, 2, 'colon heading task count');
|
|
680
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'colon heading T01 id');
|
|
681
|
+
assert.deepStrictEqual(p.tasks[0].title, 'Setup project', 'colon heading T01 title');
|
|
682
|
+
assert.deepStrictEqual(p.tasks[1].id, 'T02', 'colon heading T02 id');
|
|
683
|
+
assert.deepStrictEqual(p.tasks[1].title, 'Add CI pipeline', 'colon heading T02 title');
|
|
684
|
+
assert.deepStrictEqual(p.tasks[1].estimate, '30m', 'colon heading T02 estimate');
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
test('parsePlan: heading-style with em-dash separator (### T01 — Title)', () => {
|
|
716
688
|
const content = `# S13: Em-Dash Style
|
|
717
689
|
|
|
718
690
|
**Goal:** Test em-dash separated heading tasks.
|
|
@@ -726,13 +698,12 @@ Widget description.
|
|
|
726
698
|
`;
|
|
727
699
|
|
|
728
700
|
const p = parsePlan(content);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
701
|
+
assert.deepStrictEqual(p.tasks.length, 1, 'em-dash heading task count');
|
|
702
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'em-dash heading T01 id');
|
|
703
|
+
assert.deepStrictEqual(p.tasks[0].title, 'Build the widget', 'em-dash heading T01 title');
|
|
704
|
+
});
|
|
733
705
|
|
|
734
|
-
|
|
735
|
-
{
|
|
706
|
+
test('parsePlan: mixed checkbox and heading-style tasks', () => {
|
|
736
707
|
const content = `# S14: Mixed Format
|
|
737
708
|
|
|
738
709
|
**Goal:** Test mixed formats.
|
|
@@ -752,23 +723,21 @@ A heading-style task.
|
|
|
752
723
|
`;
|
|
753
724
|
|
|
754
725
|
const p = parsePlan(content);
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
726
|
+
assert.deepStrictEqual(p.tasks.length, 3, 'mixed format task count');
|
|
727
|
+
assert.deepStrictEqual(p.tasks[0].id, 'T01', 'mixed T01 id');
|
|
728
|
+
assert.deepStrictEqual(p.tasks[0].done, false, 'mixed T01 not done');
|
|
729
|
+
assert.deepStrictEqual(p.tasks[1].id, 'T02', 'mixed T02 id');
|
|
730
|
+
assert.deepStrictEqual(p.tasks[1].title, 'Heading task', 'mixed T02 title');
|
|
731
|
+
assert.deepStrictEqual(p.tasks[1].estimate, '15m', 'mixed T02 estimate');
|
|
732
|
+
assert.deepStrictEqual(p.tasks[1].done, false, 'mixed T02 not done (heading style)');
|
|
733
|
+
assert.deepStrictEqual(p.tasks[2].id, 'T03', 'mixed T03 id');
|
|
734
|
+
assert.deepStrictEqual(p.tasks[2].done, true, 'mixed T03 done');
|
|
735
|
+
});
|
|
765
736
|
|
|
766
737
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
767
738
|
// parseSummary tests
|
|
768
739
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
769
|
-
|
|
770
|
-
console.log('\n=== parseSummary: full summary with all frontmatter fields ===');
|
|
771
|
-
{
|
|
740
|
+
test('parseSummary: full summary with all frontmatter fields', () => {
|
|
772
741
|
const content = `---
|
|
773
742
|
id: T01
|
|
774
743
|
parent: S01
|
|
@@ -823,52 +792,51 @@ None.
|
|
|
823
792
|
const s = parseSummary(content);
|
|
824
793
|
|
|
825
794
|
// Frontmatter fields
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
795
|
+
assert.deepStrictEqual(s.frontmatter.id, 'T01', 'summary id');
|
|
796
|
+
assert.deepStrictEqual(s.frontmatter.parent, 'S01', 'summary parent');
|
|
797
|
+
assert.deepStrictEqual(s.frontmatter.milestone, 'M001', 'summary milestone');
|
|
798
|
+
assert.deepStrictEqual(s.frontmatter.provides.length, 2, 'provides count');
|
|
799
|
+
assert.deepStrictEqual(s.frontmatter.provides[0], 'parseRoadmap test coverage', 'first provides');
|
|
800
|
+
assert.deepStrictEqual(s.frontmatter.provides[1], 'parsePlan test coverage', 'second provides');
|
|
832
801
|
|
|
833
802
|
// requires (nested objects)
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
803
|
+
assert.deepStrictEqual(s.frontmatter.requires.length, 2, 'requires count');
|
|
804
|
+
assert.deepStrictEqual(s.frontmatter.requires[0].slice, 'S00', 'first requires slice');
|
|
805
|
+
assert.deepStrictEqual(s.frontmatter.requires[0].provides, 'type definitions', 'first requires provides');
|
|
806
|
+
assert.deepStrictEqual(s.frontmatter.requires[1].slice, 'S02', 'second requires slice');
|
|
807
|
+
assert.deepStrictEqual(s.frontmatter.requires[1].provides, 'state derivation', 'second requires provides');
|
|
808
|
+
|
|
809
|
+
assert.deepStrictEqual(s.frontmatter.affects.length, 1, 'affects count');
|
|
810
|
+
assert.deepStrictEqual(s.frontmatter.affects[0], 'auto-mode dispatch', 'affects value');
|
|
811
|
+
assert.deepStrictEqual(s.frontmatter.key_files.length, 2, 'key_files count');
|
|
812
|
+
assert.deepStrictEqual(s.frontmatter.key_decisions.length, 1, 'key_decisions count');
|
|
813
|
+
assert.deepStrictEqual(s.frontmatter.patterns_established.length, 1, 'patterns_established count');
|
|
814
|
+
assert.deepStrictEqual(s.frontmatter.drill_down_paths.length, 1, 'drill_down_paths count');
|
|
846
815
|
|
|
847
816
|
// observability_surfaces extraction
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
817
|
+
assert.deepStrictEqual(s.frontmatter.observability_surfaces.length, 2, 'observability_surfaces count');
|
|
818
|
+
assert.deepStrictEqual(s.frontmatter.observability_surfaces[0], 'test pass/fail output from node --test', 'first observability surface');
|
|
819
|
+
assert.deepStrictEqual(s.frontmatter.observability_surfaces[1], 'exit code 1 on failure', 'second observability surface');
|
|
851
820
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
821
|
+
assert.deepStrictEqual(s.frontmatter.duration, '23min', 'duration');
|
|
822
|
+
assert.deepStrictEqual(s.frontmatter.verification_result, 'pass', 'verification_result');
|
|
823
|
+
assert.deepStrictEqual(s.frontmatter.completed_at, '2025-03-10T08:00:00Z', 'completed_at');
|
|
855
824
|
|
|
856
825
|
// Body fields
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
826
|
+
assert.deepStrictEqual(s.title, 'T01: Test parseRoadmap and parsePlan', 'summary title');
|
|
827
|
+
assert.deepStrictEqual(s.oneLiner, 'Created parsers.test.ts with 98 assertions across 16 test groups.', 'one-liner');
|
|
828
|
+
assert.ok(s.whatHappened.includes('comprehensive tests'), 'whatHappened content');
|
|
829
|
+
assert.deepStrictEqual(s.deviations, 'None.', 'deviations');
|
|
861
830
|
|
|
862
831
|
// Files modified
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
{
|
|
832
|
+
assert.deepStrictEqual(s.filesModified.length, 3, 'filesModified count');
|
|
833
|
+
assert.deepStrictEqual(s.filesModified[0].path, 'tests/parsers.test.ts', 'first file path');
|
|
834
|
+
assert.ok(s.filesModified[0].description.includes('98 assertions'), 'first file description');
|
|
835
|
+
assert.deepStrictEqual(s.filesModified[1].path, 'types.ts', 'second file path');
|
|
836
|
+
assert.deepStrictEqual(s.filesModified[2].path, 'files.ts', 'third file path');
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test('parseSummary: one-liner extraction (bold-wrapped line after H1)', () => {
|
|
872
840
|
const content = `# S01: Parser Test Suite
|
|
873
841
|
|
|
874
842
|
**All 5 parsers have test coverage with edge cases.**
|
|
@@ -879,12 +847,11 @@ Things happened.
|
|
|
879
847
|
`;
|
|
880
848
|
|
|
881
849
|
const s = parseSummary(content);
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
850
|
+
assert.deepStrictEqual(s.title, 'S01: Parser Test Suite', 'title');
|
|
851
|
+
assert.deepStrictEqual(s.oneLiner, 'All 5 parsers have test coverage with edge cases.', 'bold one-liner');
|
|
852
|
+
});
|
|
885
853
|
|
|
886
|
-
|
|
887
|
-
{
|
|
854
|
+
test('parseSummary: non-bold paragraph after H1 (empty one-liner)', () => {
|
|
888
855
|
const content = `# T02: Some Task
|
|
889
856
|
|
|
890
857
|
This is just a regular paragraph, not bold.
|
|
@@ -895,12 +862,11 @@ Did stuff.
|
|
|
895
862
|
`;
|
|
896
863
|
|
|
897
864
|
const s = parseSummary(content);
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
865
|
+
assert.deepStrictEqual(s.title, 'T02: Some Task', 'title');
|
|
866
|
+
assert.deepStrictEqual(s.oneLiner, '', 'non-bold line results in empty one-liner');
|
|
867
|
+
});
|
|
901
868
|
|
|
902
|
-
|
|
903
|
-
{
|
|
869
|
+
test('parseSummary: files-modified parsing (backtick path — description format)', () => {
|
|
904
870
|
const content = `# T03: File Changes
|
|
905
871
|
|
|
906
872
|
**One-liner.**
|
|
@@ -913,15 +879,14 @@ console.log('\n=== parseSummary: files-modified parsing (backtick path — descr
|
|
|
913
879
|
`;
|
|
914
880
|
|
|
915
881
|
const s = parseSummary(content);
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
{
|
|
882
|
+
assert.deepStrictEqual(s.filesModified.length, 3, 'three files');
|
|
883
|
+
assert.deepStrictEqual(s.filesModified[0].path, 'src/index.ts', 'first path');
|
|
884
|
+
assert.deepStrictEqual(s.filesModified[0].description, 'main entry point', 'first description');
|
|
885
|
+
assert.deepStrictEqual(s.filesModified[1].path, 'src/utils.ts', 'second path');
|
|
886
|
+
assert.deepStrictEqual(s.filesModified[2].path, 'README.md', 'third path');
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
test('parseSummary: missing frontmatter (safe defaults)', () => {
|
|
925
890
|
const content = `# T04: No Frontmatter
|
|
926
891
|
|
|
927
892
|
**Did something.**
|
|
@@ -932,26 +897,25 @@ No frontmatter at all.
|
|
|
932
897
|
`;
|
|
933
898
|
|
|
934
899
|
const s = parseSummary(content);
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
{
|
|
900
|
+
assert.deepStrictEqual(s.frontmatter.id, '', 'default id empty');
|
|
901
|
+
assert.deepStrictEqual(s.frontmatter.parent, '', 'default parent empty');
|
|
902
|
+
assert.deepStrictEqual(s.frontmatter.milestone, '', 'default milestone empty');
|
|
903
|
+
assert.deepStrictEqual(s.frontmatter.provides.length, 0, 'default provides empty');
|
|
904
|
+
assert.deepStrictEqual(s.frontmatter.requires.length, 0, 'default requires empty');
|
|
905
|
+
assert.deepStrictEqual(s.frontmatter.affects.length, 0, 'default affects empty');
|
|
906
|
+
assert.deepStrictEqual(s.frontmatter.key_files.length, 0, 'default key_files empty');
|
|
907
|
+
assert.deepStrictEqual(s.frontmatter.key_decisions.length, 0, 'default key_decisions empty');
|
|
908
|
+
assert.deepStrictEqual(s.frontmatter.patterns_established.length, 0, 'default patterns_established empty');
|
|
909
|
+
assert.deepStrictEqual(s.frontmatter.drill_down_paths.length, 0, 'default drill_down_paths empty');
|
|
910
|
+
assert.deepStrictEqual(s.frontmatter.observability_surfaces.length, 0, 'default observability_surfaces empty');
|
|
911
|
+
assert.deepStrictEqual(s.frontmatter.duration, '', 'default duration empty');
|
|
912
|
+
assert.deepStrictEqual(s.frontmatter.verification_result, 'untested', 'default verification_result');
|
|
913
|
+
assert.deepStrictEqual(s.frontmatter.completed_at, '', 'default completed_at empty');
|
|
914
|
+
assert.deepStrictEqual(s.title, 'T04: No Frontmatter', 'title still parsed');
|
|
915
|
+
assert.deepStrictEqual(s.oneLiner, 'Did something.', 'one-liner still parsed');
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
test('parseSummary: empty body', () => {
|
|
955
919
|
const content = `---
|
|
956
920
|
id: T05
|
|
957
921
|
parent: S01
|
|
@@ -960,16 +924,15 @@ milestone: M001
|
|
|
960
924
|
`;
|
|
961
925
|
|
|
962
926
|
const s = parseSummary(content);
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
{
|
|
927
|
+
assert.deepStrictEqual(s.frontmatter.id, 'T05', 'id from frontmatter');
|
|
928
|
+
assert.deepStrictEqual(s.title, '', 'empty title');
|
|
929
|
+
assert.deepStrictEqual(s.oneLiner, '', 'empty one-liner');
|
|
930
|
+
assert.deepStrictEqual(s.whatHappened, '', 'empty whatHappened');
|
|
931
|
+
assert.deepStrictEqual(s.deviations, '', 'empty deviations');
|
|
932
|
+
assert.deepStrictEqual(s.filesModified.length, 0, 'no files modified');
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
test('parseSummary: summary with requires array (nested objects)', () => {
|
|
973
936
|
const content = `---
|
|
974
937
|
id: T06
|
|
975
938
|
parent: S02
|
|
@@ -1004,20 +967,18 @@ Tested.
|
|
|
1004
967
|
`;
|
|
1005
968
|
|
|
1006
969
|
const s = parseSummary(content);
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
970
|
+
assert.deepStrictEqual(s.frontmatter.requires.length, 3, 'three requires entries');
|
|
971
|
+
assert.deepStrictEqual(s.frontmatter.requires[0].slice, 'S01', 'first requires slice');
|
|
972
|
+
assert.deepStrictEqual(s.frontmatter.requires[0].provides, 'parser functions', 'first requires provides');
|
|
973
|
+
assert.deepStrictEqual(s.frontmatter.requires[1].slice, 'S00', 'second requires slice');
|
|
974
|
+
assert.deepStrictEqual(s.frontmatter.requires[2].slice, 'S03', 'third requires slice');
|
|
975
|
+
assert.deepStrictEqual(s.frontmatter.requires[2].provides, 'state engine', 'third requires provides');
|
|
976
|
+
});
|
|
1014
977
|
|
|
1015
978
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1016
979
|
// parseContinue tests
|
|
1017
980
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1018
|
-
|
|
1019
|
-
console.log('\n=== parseContinue: full continue file with all frontmatter fields ===');
|
|
1020
|
-
{
|
|
981
|
+
test('parseContinue: full continue file with all frontmatter fields', () => {
|
|
1021
982
|
const content = `---
|
|
1022
983
|
milestone: M001
|
|
1023
984
|
slice: S01
|
|
@@ -1052,24 +1013,23 @@ Run the full test suite with node --test.
|
|
|
1052
1013
|
const c = parseContinue(content);
|
|
1053
1014
|
|
|
1054
1015
|
// Frontmatter
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1016
|
+
assert.deepStrictEqual(c.frontmatter.milestone, 'M001', 'continue milestone');
|
|
1017
|
+
assert.deepStrictEqual(c.frontmatter.slice, 'S01', 'continue slice');
|
|
1018
|
+
assert.deepStrictEqual(c.frontmatter.task, 'T02', 'continue task');
|
|
1019
|
+
assert.deepStrictEqual(c.frontmatter.step, 3, 'continue step');
|
|
1020
|
+
assert.deepStrictEqual(c.frontmatter.totalSteps, 5, 'continue totalSteps');
|
|
1021
|
+
assert.deepStrictEqual(c.frontmatter.status, 'in_progress', 'continue status');
|
|
1022
|
+
assert.deepStrictEqual(c.frontmatter.savedAt, '2025-03-10T08:30:00Z', 'continue savedAt');
|
|
1062
1023
|
|
|
1063
1024
|
// Body sections
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
{
|
|
1025
|
+
assert.ok(c.completedWork.includes('Steps 1-3 are done'), 'completedWork content');
|
|
1026
|
+
assert.ok(c.remainingWork.includes('Steps 4-5'), 'remainingWork content');
|
|
1027
|
+
assert.ok(c.decisions.includes('manual assert pattern'), 'decisions content');
|
|
1028
|
+
assert.ok(c.context.includes('gsd-s01 worktree'), 'context content');
|
|
1029
|
+
assert.ok(c.nextAction.includes('node --test'), 'nextAction content');
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
test('parseContinue: string step/totalSteps parsed as integers', () => {
|
|
1073
1033
|
const content = `---
|
|
1074
1034
|
milestone: M002
|
|
1075
1035
|
slice: S03
|
|
@@ -1102,14 +1062,13 @@ Continue.
|
|
|
1102
1062
|
`;
|
|
1103
1063
|
|
|
1104
1064
|
const c = parseContinue(content);
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
{
|
|
1065
|
+
assert.deepStrictEqual(c.frontmatter.step, 7, 'step parsed as integer 7');
|
|
1066
|
+
assert.deepStrictEqual(c.frontmatter.totalSteps, 12, 'totalSteps parsed as integer 12');
|
|
1067
|
+
assert.deepStrictEqual(typeof c.frontmatter.step, 'number', 'step is number type');
|
|
1068
|
+
assert.deepStrictEqual(typeof c.frontmatter.totalSteps, 'number', 'totalSteps is number type');
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
test('parseContinue: NaN step values (non-numeric strings)', () => {
|
|
1113
1072
|
const content = `---
|
|
1114
1073
|
milestone: M001
|
|
1115
1074
|
slice: S01
|
|
@@ -1151,12 +1110,11 @@ Do things.
|
|
|
1151
1110
|
const totalIsNaN = Number.isNaN(c.frontmatter.totalSteps);
|
|
1152
1111
|
// The parser does parseInt which returns NaN for non-numeric strings
|
|
1153
1112
|
// There's no || 0 fallback on the parseInt path, so NaN is expected
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1113
|
+
assert.ok(stepIsNaN, 'NaN step when non-numeric string');
|
|
1114
|
+
assert.ok(totalIsNaN, 'NaN totalSteps when non-numeric string');
|
|
1115
|
+
});
|
|
1157
1116
|
|
|
1158
|
-
|
|
1159
|
-
{
|
|
1117
|
+
test('parseContinue: all three status variants', () => {
|
|
1160
1118
|
for (const status of ['in_progress', 'interrupted', 'compacted'] as const) {
|
|
1161
1119
|
const content = `---
|
|
1162
1120
|
milestone: M001
|
|
@@ -1174,12 +1132,11 @@ Work.
|
|
|
1174
1132
|
`;
|
|
1175
1133
|
|
|
1176
1134
|
const c = parseContinue(content);
|
|
1177
|
-
|
|
1135
|
+
assert.deepStrictEqual(c.frontmatter.status, status, `status variant: ${status}`);
|
|
1178
1136
|
}
|
|
1179
|
-
}
|
|
1137
|
+
});
|
|
1180
1138
|
|
|
1181
|
-
|
|
1182
|
-
{
|
|
1139
|
+
test('parseContinue: missing frontmatter', () => {
|
|
1183
1140
|
const content = `## Completed Work
|
|
1184
1141
|
|
|
1185
1142
|
Some work done.
|
|
@@ -1202,24 +1159,23 @@ Next thing.
|
|
|
1202
1159
|
`;
|
|
1203
1160
|
|
|
1204
1161
|
const c = parseContinue(content);
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1162
|
+
assert.deepStrictEqual(c.frontmatter.milestone, '', 'default milestone empty');
|
|
1163
|
+
assert.deepStrictEqual(c.frontmatter.slice, '', 'default slice empty');
|
|
1164
|
+
assert.deepStrictEqual(c.frontmatter.task, '', 'default task empty');
|
|
1165
|
+
assert.deepStrictEqual(c.frontmatter.step, 0, 'default step 0');
|
|
1166
|
+
assert.deepStrictEqual(c.frontmatter.totalSteps, 0, 'default totalSteps 0');
|
|
1167
|
+
assert.deepStrictEqual(c.frontmatter.status, 'in_progress', 'default status in_progress');
|
|
1168
|
+
assert.deepStrictEqual(c.frontmatter.savedAt, '', 'default savedAt empty');
|
|
1212
1169
|
|
|
1213
1170
|
// Body sections still parse
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
{
|
|
1171
|
+
assert.ok(c.completedWork.includes('Some work done'), 'completedWork without frontmatter');
|
|
1172
|
+
assert.ok(c.remainingWork.includes('More to do'), 'remainingWork without frontmatter');
|
|
1173
|
+
assert.ok(c.decisions.includes('A decision'), 'decisions without frontmatter');
|
|
1174
|
+
assert.ok(c.context.includes('Some context'), 'context without frontmatter');
|
|
1175
|
+
assert.ok(c.nextAction.includes('Next thing'), 'nextAction without frontmatter');
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
test('parseContinue: body section extraction', () => {
|
|
1223
1179
|
const content = `---
|
|
1224
1180
|
milestone: M001
|
|
1225
1181
|
slice: S01
|
|
@@ -1253,16 +1209,15 @@ Pick up at step 3: run the integration tests.
|
|
|
1253
1209
|
`;
|
|
1254
1210
|
|
|
1255
1211
|
const c = parseContinue(content);
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
{
|
|
1212
|
+
assert.ok(c.completedWork.includes('First paragraph'), 'completedWork first paragraph');
|
|
1213
|
+
assert.ok(c.completedWork.includes('Second paragraph'), 'completedWork second paragraph');
|
|
1214
|
+
assert.ok(c.remainingWork.includes('step 3 and step 4'), 'remainingWork detail');
|
|
1215
|
+
assert.ok(c.decisions.includes('approach A over approach B'), 'decisions detail');
|
|
1216
|
+
assert.ok(c.context.includes('Node 22 required'), 'context detail');
|
|
1217
|
+
assert.ok(c.nextAction.includes('step 3: run the integration tests'), 'nextAction detail');
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
test('parseContinue: total_steps vs totalSteps key support', () => {
|
|
1266
1221
|
// Test total_steps (snake_case) — the primary format
|
|
1267
1222
|
const content1 = `---
|
|
1268
1223
|
milestone: M001
|
|
@@ -1280,7 +1235,7 @@ Work.
|
|
|
1280
1235
|
`;
|
|
1281
1236
|
|
|
1282
1237
|
const c1 = parseContinue(content1);
|
|
1283
|
-
|
|
1238
|
+
assert.deepStrictEqual(c1.frontmatter.totalSteps, 8, 'total_steps snake_case works');
|
|
1284
1239
|
|
|
1285
1240
|
// Test totalSteps (camelCase) — the fallback
|
|
1286
1241
|
const content2 = `---
|
|
@@ -1299,15 +1254,13 @@ Work.
|
|
|
1299
1254
|
`;
|
|
1300
1255
|
|
|
1301
1256
|
const c2 = parseContinue(content2);
|
|
1302
|
-
|
|
1303
|
-
}
|
|
1257
|
+
assert.deepStrictEqual(c2.frontmatter.totalSteps, 6, 'totalSteps camelCase works');
|
|
1258
|
+
});
|
|
1304
1259
|
|
|
1305
1260
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1306
1261
|
// parseRequirementCounts tests
|
|
1307
1262
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1308
|
-
|
|
1309
|
-
console.log('\n=== parseRequirementCounts: full requirements file ===');
|
|
1310
|
-
{
|
|
1263
|
+
test('parseRequirementCounts: full requirements file', () => {
|
|
1311
1264
|
const content = `# Requirements
|
|
1312
1265
|
|
|
1313
1266
|
## Active
|
|
@@ -1344,27 +1297,25 @@ console.log('\n=== parseRequirementCounts: full requirements file ===');
|
|
|
1344
1297
|
`;
|
|
1345
1298
|
|
|
1346
1299
|
const counts = parseRequirementCounts(content);
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
{
|
|
1300
|
+
assert.deepStrictEqual(counts.active, 3, 'active count');
|
|
1301
|
+
assert.deepStrictEqual(counts.validated, 2, 'validated count');
|
|
1302
|
+
assert.deepStrictEqual(counts.deferred, 1, 'deferred count');
|
|
1303
|
+
assert.deepStrictEqual(counts.outOfScope, 2, 'outOfScope count');
|
|
1304
|
+
assert.deepStrictEqual(counts.blocked, 1, 'blocked count');
|
|
1305
|
+
assert.deepStrictEqual(counts.total, 8, 'total is sum of active+validated+deferred+outOfScope');
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
test('parseRequirementCounts: null input returns all zeros', () => {
|
|
1357
1309
|
const counts = parseRequirementCounts(null);
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
{
|
|
1310
|
+
assert.deepStrictEqual(counts.active, 0, 'null active');
|
|
1311
|
+
assert.deepStrictEqual(counts.validated, 0, 'null validated');
|
|
1312
|
+
assert.deepStrictEqual(counts.deferred, 0, 'null deferred');
|
|
1313
|
+
assert.deepStrictEqual(counts.outOfScope, 0, 'null outOfScope');
|
|
1314
|
+
assert.deepStrictEqual(counts.blocked, 0, 'null blocked');
|
|
1315
|
+
assert.deepStrictEqual(counts.total, 0, 'null total');
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
test('parseRequirementCounts: empty sections return zero counts', () => {
|
|
1368
1319
|
const content = `# Requirements
|
|
1369
1320
|
|
|
1370
1321
|
## Active
|
|
@@ -1377,16 +1328,15 @@ console.log('\n=== parseRequirementCounts: empty sections return zero counts ===
|
|
|
1377
1328
|
`;
|
|
1378
1329
|
|
|
1379
1330
|
const counts = parseRequirementCounts(content);
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
{
|
|
1331
|
+
assert.deepStrictEqual(counts.active, 0, 'empty active');
|
|
1332
|
+
assert.deepStrictEqual(counts.validated, 0, 'empty validated');
|
|
1333
|
+
assert.deepStrictEqual(counts.deferred, 0, 'empty deferred');
|
|
1334
|
+
assert.deepStrictEqual(counts.outOfScope, 0, 'empty outOfScope');
|
|
1335
|
+
assert.deepStrictEqual(counts.blocked, 0, 'empty blocked');
|
|
1336
|
+
assert.deepStrictEqual(counts.total, 0, 'empty total');
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
test('parseRequirementCounts: blocked status counting', () => {
|
|
1390
1340
|
const content = `# Requirements
|
|
1391
1341
|
|
|
1392
1342
|
## Active
|
|
@@ -1411,13 +1361,12 @@ console.log('\n=== parseRequirementCounts: blocked status counting ===');
|
|
|
1411
1361
|
`;
|
|
1412
1362
|
|
|
1413
1363
|
const counts = parseRequirementCounts(content);
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
}
|
|
1364
|
+
assert.deepStrictEqual(counts.active, 3, 'active includes blocked items in Active section');
|
|
1365
|
+
assert.deepStrictEqual(counts.blocked, 3, 'blocked counts all blocked statuses across sections');
|
|
1366
|
+
assert.deepStrictEqual(counts.deferred, 1, 'deferred section count');
|
|
1367
|
+
});
|
|
1418
1368
|
|
|
1419
|
-
|
|
1420
|
-
{
|
|
1369
|
+
test('parseRequirementCounts: total is sum of all section counts', () => {
|
|
1421
1370
|
const content = `# Requirements
|
|
1422
1371
|
|
|
1423
1372
|
## Active
|
|
@@ -1451,20 +1400,18 @@ console.log('\n=== parseRequirementCounts: total is sum of all section counts ==
|
|
|
1451
1400
|
`;
|
|
1452
1401
|
|
|
1453
1402
|
const counts = parseRequirementCounts(content);
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1403
|
+
assert.deepStrictEqual(counts.active, 1, 'one active');
|
|
1404
|
+
assert.deepStrictEqual(counts.validated, 2, 'two validated');
|
|
1405
|
+
assert.deepStrictEqual(counts.deferred, 3, 'three deferred');
|
|
1406
|
+
assert.deepStrictEqual(counts.outOfScope, 1, 'one outOfScope');
|
|
1407
|
+
assert.deepStrictEqual(counts.total, 7, 'total = 1 + 2 + 3 + 1');
|
|
1408
|
+
assert.deepStrictEqual(counts.total, counts.active + counts.validated + counts.deferred + counts.outOfScope, 'total is exact sum');
|
|
1409
|
+
});
|
|
1461
1410
|
|
|
1462
1411
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1463
1412
|
// parseSecretsManifest / formatSecretsManifest tests
|
|
1464
1413
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1465
|
-
|
|
1466
|
-
console.log('\n=== parseSecretsManifest: full manifest with 3 keys ===');
|
|
1467
|
-
{
|
|
1414
|
+
test('parseSecretsManifest: full manifest with 3 keys', () => {
|
|
1468
1415
|
const content = `# Secrets Manifest
|
|
1469
1416
|
|
|
1470
1417
|
**Milestone:** M003
|
|
@@ -1508,37 +1455,36 @@ console.log('\n=== parseSecretsManifest: full manifest with 3 keys ===');
|
|
|
1508
1455
|
|
|
1509
1456
|
const m = parseSecretsManifest(content);
|
|
1510
1457
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1458
|
+
assert.deepStrictEqual(m.milestone, 'M003', 'manifest milestone');
|
|
1459
|
+
assert.deepStrictEqual(m.generatedAt, '2025-06-15T10:00:00Z', 'manifest generatedAt');
|
|
1460
|
+
assert.deepStrictEqual(m.entries.length, 3, 'three entries');
|
|
1514
1461
|
|
|
1515
1462
|
// First entry
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1463
|
+
assert.deepStrictEqual(m.entries[0].key, 'OPENAI_API_KEY', 'entry 0 key');
|
|
1464
|
+
assert.deepStrictEqual(m.entries[0].service, 'OpenAI', 'entry 0 service');
|
|
1465
|
+
assert.deepStrictEqual(m.entries[0].dashboardUrl, 'https://platform.openai.com/api-keys', 'entry 0 dashboardUrl');
|
|
1466
|
+
assert.deepStrictEqual(m.entries[0].formatHint, 'starts with sk-', 'entry 0 formatHint');
|
|
1467
|
+
assert.deepStrictEqual(m.entries[0].status, 'pending', 'entry 0 status');
|
|
1468
|
+
assert.deepStrictEqual(m.entries[0].destination, 'dotenv', 'entry 0 destination');
|
|
1469
|
+
assert.deepStrictEqual(m.entries[0].guidance.length, 3, 'entry 0 guidance count');
|
|
1470
|
+
assert.deepStrictEqual(m.entries[0].guidance[0], 'Go to https://platform.openai.com/api-keys', 'entry 0 guidance[0]');
|
|
1471
|
+
assert.deepStrictEqual(m.entries[0].guidance[2], 'Copy the key immediately — it won\'t be shown again', 'entry 0 guidance[2]');
|
|
1525
1472
|
|
|
1526
1473
|
// Second entry
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1474
|
+
assert.deepStrictEqual(m.entries[1].key, 'STRIPE_SECRET_KEY', 'entry 1 key');
|
|
1475
|
+
assert.deepStrictEqual(m.entries[1].service, 'Stripe', 'entry 1 service');
|
|
1476
|
+
assert.deepStrictEqual(m.entries[1].status, 'collected', 'entry 1 status');
|
|
1477
|
+
assert.deepStrictEqual(m.entries[1].formatHint, 'starts with sk_test_ or sk_live_', 'entry 1 formatHint');
|
|
1478
|
+
assert.deepStrictEqual(m.entries[1].guidance.length, 3, 'entry 1 guidance count');
|
|
1532
1479
|
|
|
1533
1480
|
// Third entry
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
{
|
|
1481
|
+
assert.deepStrictEqual(m.entries[2].key, 'SUPABASE_URL', 'entry 2 key');
|
|
1482
|
+
assert.deepStrictEqual(m.entries[2].status, 'skipped', 'entry 2 status');
|
|
1483
|
+
assert.deepStrictEqual(m.entries[2].destination, 'vercel', 'entry 2 destination');
|
|
1484
|
+
assert.deepStrictEqual(m.entries[2].guidance.length, 2, 'entry 2 guidance count');
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
test('parseSecretsManifest: single-key manifest', () => {
|
|
1542
1488
|
const content = `# Secrets Manifest
|
|
1543
1489
|
|
|
1544
1490
|
**Milestone:** M001
|
|
@@ -1557,15 +1503,14 @@ console.log('\n=== parseSecretsManifest: single-key manifest ===');
|
|
|
1557
1503
|
`;
|
|
1558
1504
|
|
|
1559
1505
|
const m = parseSecretsManifest(content);
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
{
|
|
1506
|
+
assert.deepStrictEqual(m.milestone, 'M001', 'single-key milestone');
|
|
1507
|
+
assert.deepStrictEqual(m.entries.length, 1, 'single entry');
|
|
1508
|
+
assert.deepStrictEqual(m.entries[0].key, 'DATABASE_URL', 'single entry key');
|
|
1509
|
+
assert.deepStrictEqual(m.entries[0].service, 'PostgreSQL', 'single entry service');
|
|
1510
|
+
assert.deepStrictEqual(m.entries[0].guidance.length, 2, 'single entry guidance count');
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1513
|
+
test('parseSecretsManifest: empty/no-secrets manifest', () => {
|
|
1569
1514
|
const content = `# Secrets Manifest
|
|
1570
1515
|
|
|
1571
1516
|
**Milestone:** M002
|
|
@@ -1573,13 +1518,12 @@ console.log('\n=== parseSecretsManifest: empty/no-secrets manifest ===');
|
|
|
1573
1518
|
`;
|
|
1574
1519
|
|
|
1575
1520
|
const m = parseSecretsManifest(content);
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1521
|
+
assert.deepStrictEqual(m.milestone, 'M002', 'empty manifest milestone');
|
|
1522
|
+
assert.deepStrictEqual(m.generatedAt, '2025-06-15T14:00:00Z', 'empty manifest generatedAt');
|
|
1523
|
+
assert.deepStrictEqual(m.entries.length, 0, 'no entries in empty manifest');
|
|
1524
|
+
});
|
|
1580
1525
|
|
|
1581
|
-
|
|
1582
|
-
{
|
|
1526
|
+
test('parseSecretsManifest: missing optional fields default correctly', () => {
|
|
1583
1527
|
const content = `# Secrets Manifest
|
|
1584
1528
|
|
|
1585
1529
|
**Milestone:** M004
|
|
@@ -1593,18 +1537,17 @@ console.log('\n=== parseSecretsManifest: missing optional fields default correct
|
|
|
1593
1537
|
`;
|
|
1594
1538
|
|
|
1595
1539
|
const m = parseSecretsManifest(content);
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
{
|
|
1540
|
+
assert.deepStrictEqual(m.entries.length, 1, 'one entry with missing fields');
|
|
1541
|
+
assert.deepStrictEqual(m.entries[0].key, 'SOME_API_KEY', 'key parsed');
|
|
1542
|
+
assert.deepStrictEqual(m.entries[0].service, 'SomeService', 'service parsed');
|
|
1543
|
+
assert.deepStrictEqual(m.entries[0].dashboardUrl, '', 'missing dashboardUrl defaults to empty string');
|
|
1544
|
+
assert.deepStrictEqual(m.entries[0].formatHint, '', 'missing formatHint defaults to empty string');
|
|
1545
|
+
assert.deepStrictEqual(m.entries[0].status, 'pending', 'missing status defaults to pending');
|
|
1546
|
+
assert.deepStrictEqual(m.entries[0].destination, 'dotenv', 'missing destination defaults to dotenv');
|
|
1547
|
+
assert.deepStrictEqual(m.entries[0].guidance.length, 1, 'guidance still parsed');
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
test('parseSecretsManifest: all three status values parse', () => {
|
|
1608
1551
|
for (const status of ['pending', 'collected', 'skipped'] as const) {
|
|
1609
1552
|
const content = `# Secrets Manifest
|
|
1610
1553
|
|
|
@@ -1620,12 +1563,11 @@ console.log('\n=== parseSecretsManifest: all three status values parse ===');
|
|
|
1620
1563
|
`;
|
|
1621
1564
|
|
|
1622
1565
|
const m = parseSecretsManifest(content);
|
|
1623
|
-
|
|
1566
|
+
assert.deepStrictEqual(m.entries[0].status, status, `status variant: ${status}`);
|
|
1624
1567
|
}
|
|
1625
|
-
}
|
|
1568
|
+
});
|
|
1626
1569
|
|
|
1627
|
-
|
|
1628
|
-
{
|
|
1570
|
+
test('parseSecretsManifest: invalid status defaults to pending', () => {
|
|
1629
1571
|
const content = `# Secrets Manifest
|
|
1630
1572
|
|
|
1631
1573
|
**Milestone:** M006
|
|
@@ -1640,11 +1582,10 @@ console.log('\n=== parseSecretsManifest: invalid status defaults to pending ==='
|
|
|
1640
1582
|
`;
|
|
1641
1583
|
|
|
1642
1584
|
const m = parseSecretsManifest(content);
|
|
1643
|
-
|
|
1644
|
-
}
|
|
1585
|
+
assert.deepStrictEqual(m.entries[0].status, 'pending', 'invalid status defaults to pending');
|
|
1586
|
+
});
|
|
1645
1587
|
|
|
1646
|
-
|
|
1647
|
-
{
|
|
1588
|
+
test('parseSecretsManifest + formatSecretsManifest: round-trip', () => {
|
|
1648
1589
|
const original = `# Secrets Manifest
|
|
1649
1590
|
|
|
1650
1591
|
**Milestone:** M007
|
|
@@ -1679,32 +1620,30 @@ console.log('\n=== parseSecretsManifest + formatSecretsManifest: round-trip ==='
|
|
|
1679
1620
|
const parsed2 = parseSecretsManifest(formatted);
|
|
1680
1621
|
|
|
1681
1622
|
// Verify semantic equality after round-trip
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1623
|
+
assert.deepStrictEqual(parsed2.milestone, parsed1.milestone, 'round-trip milestone');
|
|
1624
|
+
assert.deepStrictEqual(parsed2.generatedAt, parsed1.generatedAt, 'round-trip generatedAt');
|
|
1625
|
+
assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'round-trip entry count');
|
|
1685
1626
|
|
|
1686
1627
|
for (let i = 0; i < parsed1.entries.length; i++) {
|
|
1687
1628
|
const e1 = parsed1.entries[i];
|
|
1688
1629
|
const e2 = parsed2.entries[i];
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1630
|
+
assert.deepStrictEqual(e2.key, e1.key, `round-trip entry ${i} key`);
|
|
1631
|
+
assert.deepStrictEqual(e2.service, e1.service, `round-trip entry ${i} service`);
|
|
1632
|
+
assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `round-trip entry ${i} dashboardUrl`);
|
|
1633
|
+
assert.deepStrictEqual(e2.formatHint, e1.formatHint, `round-trip entry ${i} formatHint`);
|
|
1634
|
+
assert.deepStrictEqual(e2.status, e1.status, `round-trip entry ${i} status`);
|
|
1635
|
+
assert.deepStrictEqual(e2.destination, e1.destination, `round-trip entry ${i} destination`);
|
|
1636
|
+
assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `round-trip entry ${i} guidance length`);
|
|
1696
1637
|
for (let j = 0; j < e1.guidance.length; j++) {
|
|
1697
|
-
|
|
1638
|
+
assert.deepStrictEqual(e2.guidance[j], e1.guidance[j], `round-trip entry ${i} guidance[${j}]`);
|
|
1698
1639
|
}
|
|
1699
1640
|
}
|
|
1700
|
-
}
|
|
1641
|
+
});
|
|
1701
1642
|
|
|
1702
1643
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1703
1644
|
// LLM-style round-trip tests — realistic manifest variations
|
|
1704
1645
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1705
|
-
|
|
1706
|
-
console.log('\n=== LLM round-trip: extra whitespace ===');
|
|
1707
|
-
{
|
|
1646
|
+
test('LLM round-trip: extra whitespace', () => {
|
|
1708
1647
|
// LLMs often produce inconsistent indentation and trailing spaces
|
|
1709
1648
|
const messy = `# Secrets Manifest
|
|
1710
1649
|
|
|
@@ -1735,34 +1674,33 @@ console.log('\n=== LLM round-trip: extra whitespace ===');
|
|
|
1735
1674
|
const formatted = formatSecretsManifest(parsed1);
|
|
1736
1675
|
const parsed2 = parseSecretsManifest(formatted);
|
|
1737
1676
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1677
|
+
assert.deepStrictEqual(parsed2.milestone, parsed1.milestone, 'whitespace round-trip milestone');
|
|
1678
|
+
assert.deepStrictEqual(parsed2.generatedAt, parsed1.generatedAt, 'whitespace round-trip generatedAt');
|
|
1679
|
+
assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'whitespace round-trip entry count');
|
|
1680
|
+
assert.deepStrictEqual(parsed2.entries.length, 2, 'whitespace: two entries parsed');
|
|
1742
1681
|
|
|
1743
1682
|
for (let i = 0; i < parsed1.entries.length; i++) {
|
|
1744
1683
|
const e1 = parsed1.entries[i];
|
|
1745
1684
|
const e2 = parsed2.entries[i];
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1685
|
+
assert.deepStrictEqual(e2.key, e1.key, `whitespace round-trip entry ${i} key`);
|
|
1686
|
+
assert.deepStrictEqual(e2.service, e1.service, `whitespace round-trip entry ${i} service`);
|
|
1687
|
+
assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `whitespace round-trip entry ${i} dashboardUrl`);
|
|
1688
|
+
assert.deepStrictEqual(e2.formatHint, e1.formatHint, `whitespace round-trip entry ${i} formatHint`);
|
|
1689
|
+
assert.deepStrictEqual(e2.status, e1.status, `whitespace round-trip entry ${i} status`);
|
|
1690
|
+
assert.deepStrictEqual(e2.destination, e1.destination, `whitespace round-trip entry ${i} destination`);
|
|
1691
|
+
assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `whitespace round-trip entry ${i} guidance length`);
|
|
1753
1692
|
for (let j = 0; j < e1.guidance.length; j++) {
|
|
1754
|
-
|
|
1693
|
+
assert.deepStrictEqual(e2.guidance[j], e1.guidance[j], `whitespace round-trip entry ${i} guidance[${j}]`);
|
|
1755
1694
|
}
|
|
1756
1695
|
}
|
|
1757
1696
|
|
|
1758
1697
|
// Verify the parser correctly stripped trailing whitespace
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
}
|
|
1698
|
+
assert.deepStrictEqual(parsed1.milestone, 'M010', 'whitespace: milestone trimmed');
|
|
1699
|
+
assert.deepStrictEqual(parsed1.entries[0].key, 'OPENAI_API_KEY', 'whitespace: key trimmed');
|
|
1700
|
+
assert.deepStrictEqual(parsed1.entries[0].service, 'OpenAI', 'whitespace: service trimmed');
|
|
1701
|
+
});
|
|
1763
1702
|
|
|
1764
|
-
|
|
1765
|
-
{
|
|
1703
|
+
test('LLM round-trip: missing optional fields', () => {
|
|
1766
1704
|
// LLMs may omit Dashboard and Format hint lines entirely
|
|
1767
1705
|
const minimal = `# Secrets Manifest
|
|
1768
1706
|
|
|
@@ -1790,32 +1728,31 @@ console.log('\n=== LLM round-trip: missing optional fields ===');
|
|
|
1790
1728
|
const parsed1 = parseSecretsManifest(minimal);
|
|
1791
1729
|
|
|
1792
1730
|
// Verify missing optional fields get defaults
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1731
|
+
assert.deepStrictEqual(parsed1.entries[0].dashboardUrl, '', 'missing-optional: no dashboard → empty string');
|
|
1732
|
+
assert.deepStrictEqual(parsed1.entries[0].formatHint, '', 'missing-optional: no format hint → empty string');
|
|
1733
|
+
assert.deepStrictEqual(parsed1.entries[1].dashboardUrl, '', 'missing-optional: entry 2 no dashboard → empty string');
|
|
1734
|
+
assert.deepStrictEqual(parsed1.entries[1].formatHint, '', 'missing-optional: entry 2 no format hint → empty string');
|
|
1797
1735
|
|
|
1798
1736
|
// Round-trip: formatter omits empty optional fields, re-parse preserves defaults
|
|
1799
1737
|
const formatted = formatSecretsManifest(parsed1);
|
|
1800
1738
|
const parsed2 = parseSecretsManifest(formatted);
|
|
1801
1739
|
|
|
1802
|
-
|
|
1740
|
+
assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'missing-optional round-trip entry count');
|
|
1803
1741
|
|
|
1804
1742
|
for (let i = 0; i < parsed1.entries.length; i++) {
|
|
1805
1743
|
const e1 = parsed1.entries[i];
|
|
1806
1744
|
const e2 = parsed2.entries[i];
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1745
|
+
assert.deepStrictEqual(e2.key, e1.key, `missing-optional round-trip entry ${i} key`);
|
|
1746
|
+
assert.deepStrictEqual(e2.service, e1.service, `missing-optional round-trip entry ${i} service`);
|
|
1747
|
+
assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `missing-optional round-trip entry ${i} dashboardUrl`);
|
|
1748
|
+
assert.deepStrictEqual(e2.formatHint, e1.formatHint, `missing-optional round-trip entry ${i} formatHint`);
|
|
1749
|
+
assert.deepStrictEqual(e2.status, e1.status, `missing-optional round-trip entry ${i} status`);
|
|
1750
|
+
assert.deepStrictEqual(e2.destination, e1.destination, `missing-optional round-trip entry ${i} destination`);
|
|
1751
|
+
assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `missing-optional round-trip entry ${i} guidance length`);
|
|
1814
1752
|
}
|
|
1815
|
-
}
|
|
1753
|
+
});
|
|
1816
1754
|
|
|
1817
|
-
|
|
1818
|
-
{
|
|
1755
|
+
test('LLM round-trip: extra blank lines', () => {
|
|
1819
1756
|
// LLMs sometimes insert excessive blank lines between sections
|
|
1820
1757
|
const blanky = `# Secrets Manifest
|
|
1821
1758
|
|
|
@@ -1859,42 +1796,40 @@ console.log('\n=== LLM round-trip: extra blank lines ===');
|
|
|
1859
1796
|
|
|
1860
1797
|
const parsed1 = parseSecretsManifest(blanky);
|
|
1861
1798
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1799
|
+
assert.deepStrictEqual(parsed1.entries.length, 2, 'blank-lines: two entries parsed');
|
|
1800
|
+
assert.deepStrictEqual(parsed1.milestone, 'M012', 'blank-lines: milestone parsed');
|
|
1801
|
+
assert.deepStrictEqual(parsed1.entries[0].key, 'API_KEY_ONE', 'blank-lines: first key');
|
|
1802
|
+
assert.deepStrictEqual(parsed1.entries[0].guidance.length, 2, 'blank-lines: first entry guidance count');
|
|
1803
|
+
assert.deepStrictEqual(parsed1.entries[1].key, 'API_KEY_TWO', 'blank-lines: second key');
|
|
1804
|
+
assert.deepStrictEqual(parsed1.entries[1].status, 'skipped', 'blank-lines: second entry status');
|
|
1868
1805
|
|
|
1869
1806
|
// Round-trip produces clean output
|
|
1870
1807
|
const formatted = formatSecretsManifest(parsed1);
|
|
1871
1808
|
const parsed2 = parseSecretsManifest(formatted);
|
|
1872
1809
|
|
|
1873
|
-
|
|
1810
|
+
assert.deepStrictEqual(parsed2.entries.length, parsed1.entries.length, 'blank-lines round-trip entry count');
|
|
1874
1811
|
|
|
1875
1812
|
for (let i = 0; i < parsed1.entries.length; i++) {
|
|
1876
1813
|
const e1 = parsed1.entries[i];
|
|
1877
1814
|
const e2 = parsed2.entries[i];
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1815
|
+
assert.deepStrictEqual(e2.key, e1.key, `blank-lines round-trip entry ${i} key`);
|
|
1816
|
+
assert.deepStrictEqual(e2.service, e1.service, `blank-lines round-trip entry ${i} service`);
|
|
1817
|
+
assert.deepStrictEqual(e2.dashboardUrl, e1.dashboardUrl, `blank-lines round-trip entry ${i} dashboardUrl`);
|
|
1818
|
+
assert.deepStrictEqual(e2.formatHint, e1.formatHint, `blank-lines round-trip entry ${i} formatHint`);
|
|
1819
|
+
assert.deepStrictEqual(e2.status, e1.status, `blank-lines round-trip entry ${i} status`);
|
|
1820
|
+
assert.deepStrictEqual(e2.destination, e1.destination, `blank-lines round-trip entry ${i} destination`);
|
|
1821
|
+
assert.deepStrictEqual(e2.guidance.length, e1.guidance.length, `blank-lines round-trip entry ${i} guidance length`);
|
|
1885
1822
|
}
|
|
1886
1823
|
|
|
1887
1824
|
// Verify the formatted output is cleaner (fewer consecutive blank lines)
|
|
1888
1825
|
const consecutiveBlanks = formatted.match(/\n{4,}/g);
|
|
1889
|
-
|
|
1890
|
-
}
|
|
1826
|
+
assert.ok(consecutiveBlanks === null, 'blank-lines: formatted output has no 4+ consecutive newlines');
|
|
1827
|
+
});
|
|
1891
1828
|
|
|
1892
1829
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1893
1830
|
// parseRoadmap: boundary map with embedded code fences (#468)
|
|
1894
1831
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1895
|
-
|
|
1896
|
-
console.log('\n=== parseRoadmap: boundary map with code fences (#468) ===');
|
|
1897
|
-
{
|
|
1832
|
+
test('parseRoadmap: boundary map with code fences (#468)', () => {
|
|
1898
1833
|
const content = `# M001: Test
|
|
1899
1834
|
|
|
1900
1835
|
**Vision:** Test
|
|
@@ -1923,10 +1858,10 @@ Consumes: nothing
|
|
|
1923
1858
|
const r = parseRoadmap(content);
|
|
1924
1859
|
const elapsed = Date.now() - start;
|
|
1925
1860
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1861
|
+
assert.ok(elapsed < 1000, `boundary map with code fences parsed in ${elapsed}ms (should be < 1s)`);
|
|
1862
|
+
assert.deepStrictEqual(r.slices.length, 2, 'code-fence roadmap: slice count');
|
|
1928
1863
|
// Boundary map should still parse (may not capture perfectly with code fences, but must not hang)
|
|
1929
|
-
|
|
1930
|
-
}
|
|
1864
|
+
assert.ok(r.boundaryMap.length >= 0, 'code-fence roadmap: boundary map parsed without hanging');
|
|
1865
|
+
});
|
|
1931
1866
|
|
|
1932
|
-
|
|
1867
|
+
});
|