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