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
|
@@ -39,400 +39,444 @@ function cleanup(base: string): void {
|
|
|
39
39
|
|
|
40
40
|
// ─── resolveExpectedArtifactPath ──────────────────────────────────────────
|
|
41
41
|
|
|
42
|
-
test("resolveExpectedArtifactPath returns correct path for research-milestone", (
|
|
42
|
+
test("resolveExpectedArtifactPath returns correct path for research-milestone", () => {
|
|
43
43
|
const base = makeTmpBase();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
try {
|
|
45
|
+
const result = resolveExpectedArtifactPath("research-milestone", "M001", base);
|
|
46
|
+
assert.ok(result);
|
|
47
|
+
assert.ok(result!.includes("M001"));
|
|
48
|
+
assert.ok(result!.includes("RESEARCH"));
|
|
49
|
+
} finally {
|
|
50
|
+
cleanup(base);
|
|
51
|
+
}
|
|
50
52
|
});
|
|
51
53
|
|
|
52
|
-
test("resolveExpectedArtifactPath returns correct path for execute-task", (
|
|
54
|
+
test("resolveExpectedArtifactPath returns correct path for execute-task", () => {
|
|
53
55
|
const base = makeTmpBase();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
try {
|
|
57
|
+
const result = resolveExpectedArtifactPath("execute-task", "M001/S01/T01", base);
|
|
58
|
+
assert.ok(result);
|
|
59
|
+
assert.ok(result!.includes("tasks"));
|
|
60
|
+
assert.ok(result!.includes("SUMMARY"));
|
|
61
|
+
} finally {
|
|
62
|
+
cleanup(base);
|
|
63
|
+
}
|
|
60
64
|
});
|
|
61
65
|
|
|
62
|
-
test("resolveExpectedArtifactPath returns correct path for complete-slice", (
|
|
66
|
+
test("resolveExpectedArtifactPath returns correct path for complete-slice", () => {
|
|
63
67
|
const base = makeTmpBase();
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
try {
|
|
69
|
+
const result = resolveExpectedArtifactPath("complete-slice", "M001/S01", base);
|
|
70
|
+
assert.ok(result);
|
|
71
|
+
assert.ok(result!.includes("SUMMARY"));
|
|
72
|
+
} finally {
|
|
73
|
+
cleanup(base);
|
|
74
|
+
}
|
|
69
75
|
});
|
|
70
76
|
|
|
71
|
-
test("resolveExpectedArtifactPath returns correct path for plan-slice", (
|
|
77
|
+
test("resolveExpectedArtifactPath returns correct path for plan-slice", () => {
|
|
72
78
|
const base = makeTmpBase();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
try {
|
|
80
|
+
const result = resolveExpectedArtifactPath("plan-slice", "M001/S01", base);
|
|
81
|
+
assert.ok(result);
|
|
82
|
+
assert.ok(result!.includes("PLAN"));
|
|
83
|
+
} finally {
|
|
84
|
+
cleanup(base);
|
|
85
|
+
}
|
|
78
86
|
});
|
|
79
87
|
|
|
80
|
-
test("resolveExpectedArtifactPath returns null for unknown type", (
|
|
88
|
+
test("resolveExpectedArtifactPath returns null for unknown type", () => {
|
|
81
89
|
const base = makeTmpBase();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
try {
|
|
91
|
+
const result = resolveExpectedArtifactPath("unknown-type", "M001", base);
|
|
92
|
+
assert.equal(result, null);
|
|
93
|
+
} finally {
|
|
94
|
+
cleanup(base);
|
|
95
|
+
}
|
|
86
96
|
});
|
|
87
97
|
|
|
88
|
-
test("resolveExpectedArtifactPath returns correct path for all milestone-level types", (
|
|
98
|
+
test("resolveExpectedArtifactPath returns correct path for all milestone-level types", () => {
|
|
89
99
|
const base = makeTmpBase();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
assert.ok(planResult!.includes("ROADMAP"));
|
|
100
|
+
try {
|
|
101
|
+
const planResult = resolveExpectedArtifactPath("plan-milestone", "M001", base);
|
|
102
|
+
assert.ok(planResult);
|
|
103
|
+
assert.ok(planResult!.includes("ROADMAP"));
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
const completeResult = resolveExpectedArtifactPath("complete-milestone", "M001", base);
|
|
106
|
+
assert.ok(completeResult);
|
|
107
|
+
assert.ok(completeResult!.includes("SUMMARY"));
|
|
108
|
+
} finally {
|
|
109
|
+
cleanup(base);
|
|
110
|
+
}
|
|
99
111
|
});
|
|
100
112
|
|
|
101
|
-
test("resolveExpectedArtifactPath returns correct path for all slice-level types", (
|
|
113
|
+
test("resolveExpectedArtifactPath returns correct path for all slice-level types", () => {
|
|
102
114
|
const base = makeTmpBase();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
assert.ok(researchResult!.includes("RESEARCH"));
|
|
115
|
+
try {
|
|
116
|
+
const researchResult = resolveExpectedArtifactPath("research-slice", "M001/S01", base);
|
|
117
|
+
assert.ok(researchResult);
|
|
118
|
+
assert.ok(researchResult!.includes("RESEARCH"));
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
const assessResult = resolveExpectedArtifactPath("reassess-roadmap", "M001/S01", base);
|
|
121
|
+
assert.ok(assessResult);
|
|
122
|
+
assert.ok(assessResult!.includes("ASSESSMENT"));
|
|
112
123
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
const uatResult = resolveExpectedArtifactPath("run-uat", "M001/S01", base);
|
|
125
|
+
assert.ok(uatResult);
|
|
126
|
+
assert.ok(uatResult!.includes("UAT-RESULT"));
|
|
127
|
+
} finally {
|
|
128
|
+
cleanup(base);
|
|
129
|
+
}
|
|
116
130
|
});
|
|
117
131
|
|
|
118
132
|
// ─── diagnoseExpectedArtifact ─────────────────────────────────────────────
|
|
119
133
|
|
|
120
|
-
test("diagnoseExpectedArtifact returns description for known types", (
|
|
134
|
+
test("diagnoseExpectedArtifact returns description for known types", () => {
|
|
121
135
|
const base = makeTmpBase();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
assert.ok(research!.includes("research"));
|
|
136
|
+
try {
|
|
137
|
+
const research = diagnoseExpectedArtifact("research-milestone", "M001", base);
|
|
138
|
+
assert.ok(research);
|
|
139
|
+
assert.ok(research!.includes("research"));
|
|
127
140
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
const plan = diagnoseExpectedArtifact("plan-slice", "M001/S01", base);
|
|
142
|
+
assert.ok(plan);
|
|
143
|
+
assert.ok(plan!.includes("plan"));
|
|
131
144
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
const task = diagnoseExpectedArtifact("execute-task", "M001/S01/T01", base);
|
|
146
|
+
assert.ok(task);
|
|
147
|
+
assert.ok(task!.includes("T01"));
|
|
148
|
+
} finally {
|
|
149
|
+
cleanup(base);
|
|
150
|
+
}
|
|
135
151
|
});
|
|
136
152
|
|
|
137
|
-
test("diagnoseExpectedArtifact returns null for unknown type", (
|
|
153
|
+
test("diagnoseExpectedArtifact returns null for unknown type", () => {
|
|
138
154
|
const base = makeTmpBase();
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
try {
|
|
156
|
+
assert.equal(diagnoseExpectedArtifact("unknown", "M001", base), null);
|
|
157
|
+
} finally {
|
|
158
|
+
cleanup(base);
|
|
159
|
+
}
|
|
142
160
|
});
|
|
143
161
|
|
|
144
162
|
// ─── buildLoopRemediationSteps ────────────────────────────────────────────
|
|
145
163
|
|
|
146
|
-
test("buildLoopRemediationSteps returns steps for execute-task", (
|
|
164
|
+
test("buildLoopRemediationSteps returns steps for execute-task", () => {
|
|
147
165
|
const base = makeTmpBase();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
try {
|
|
167
|
+
const steps = buildLoopRemediationSteps("execute-task", "M001/S01/T01", base);
|
|
168
|
+
assert.ok(steps);
|
|
169
|
+
assert.ok(steps!.includes("T01"));
|
|
170
|
+
assert.ok(steps!.includes("gsd undo-task"));
|
|
171
|
+
} finally {
|
|
172
|
+
cleanup(base);
|
|
173
|
+
}
|
|
154
174
|
});
|
|
155
175
|
|
|
156
|
-
test("buildLoopRemediationSteps returns steps for plan-slice", (
|
|
176
|
+
test("buildLoopRemediationSteps returns steps for plan-slice", () => {
|
|
157
177
|
const base = makeTmpBase();
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
178
|
+
try {
|
|
179
|
+
const steps = buildLoopRemediationSteps("plan-slice", "M001/S01", base);
|
|
180
|
+
assert.ok(steps);
|
|
181
|
+
assert.ok(steps!.includes("PLAN"));
|
|
182
|
+
assert.ok(steps!.includes("gsd recover"));
|
|
183
|
+
} finally {
|
|
184
|
+
cleanup(base);
|
|
185
|
+
}
|
|
164
186
|
});
|
|
165
187
|
|
|
166
|
-
test("buildLoopRemediationSteps returns steps for complete-slice", (
|
|
188
|
+
test("buildLoopRemediationSteps returns steps for complete-slice", () => {
|
|
167
189
|
const base = makeTmpBase();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
try {
|
|
191
|
+
const steps = buildLoopRemediationSteps("complete-slice", "M001/S01", base);
|
|
192
|
+
assert.ok(steps);
|
|
193
|
+
assert.ok(steps!.includes("S01"));
|
|
194
|
+
assert.ok(steps!.includes("gsd reset-slice"));
|
|
195
|
+
} finally {
|
|
196
|
+
cleanup(base);
|
|
197
|
+
}
|
|
174
198
|
});
|
|
175
199
|
|
|
176
|
-
test("buildLoopRemediationSteps returns null for unknown type", (
|
|
200
|
+
test("buildLoopRemediationSteps returns null for unknown type", () => {
|
|
177
201
|
const base = makeTmpBase();
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
202
|
+
try {
|
|
203
|
+
assert.equal(buildLoopRemediationSteps("unknown", "M001", base), null);
|
|
204
|
+
} finally {
|
|
205
|
+
cleanup(base);
|
|
206
|
+
}
|
|
181
207
|
});
|
|
182
208
|
|
|
183
209
|
// ─── verifyExpectedArtifact: parse cache collision regression ─────────────
|
|
184
210
|
|
|
185
|
-
test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", (
|
|
211
|
+
test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", () => {
|
|
186
212
|
// Regression test: cacheKey collision when [ ] → [x] doesn't change
|
|
187
213
|
// file length or first/last 100 chars. Without the fix, parseRoadmap
|
|
188
214
|
// returns stale cached data with done=false even though the file has [x].
|
|
189
215
|
const base = makeTmpBase();
|
|
190
|
-
|
|
216
|
+
try {
|
|
217
|
+
// Build a roadmap long enough that the [x] change is outside the first/last 100 chars
|
|
218
|
+
const padding = "A".repeat(200);
|
|
219
|
+
const roadmapBefore = [
|
|
220
|
+
`# M001: Test Milestone ${padding}`,
|
|
221
|
+
"",
|
|
222
|
+
"## Slices",
|
|
223
|
+
"",
|
|
224
|
+
"- [ ] **S01: First slice** `risk:low`",
|
|
225
|
+
"",
|
|
226
|
+
`## Footer ${padding}`,
|
|
227
|
+
].join("\n");
|
|
228
|
+
const roadmapAfter = roadmapBefore.replace("- [ ] **S01:", "- [x] **S01:");
|
|
229
|
+
|
|
230
|
+
// Verify lengths are identical (the key collision condition)
|
|
231
|
+
assert.equal(roadmapBefore.length, roadmapAfter.length);
|
|
232
|
+
|
|
233
|
+
// Populate parse cache with the pre-edit roadmap
|
|
234
|
+
const before = parseRoadmap(roadmapBefore);
|
|
235
|
+
const sliceBefore = before.slices.find(s => s.id === "S01");
|
|
236
|
+
assert.ok(sliceBefore);
|
|
237
|
+
assert.equal(sliceBefore!.done, false);
|
|
238
|
+
|
|
239
|
+
// Now write the post-edit roadmap to disk and create required artifacts
|
|
240
|
+
const roadmapPath = join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
|
|
241
|
+
writeFileSync(roadmapPath, roadmapAfter);
|
|
242
|
+
const summaryPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
|
|
243
|
+
writeFileSync(summaryPath, "# Summary\nDone.");
|
|
244
|
+
const uatPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT.md");
|
|
245
|
+
writeFileSync(uatPath, "# UAT\nPassed.");
|
|
246
|
+
|
|
247
|
+
// verifyExpectedArtifact should see the [x] despite the parse cache
|
|
248
|
+
// having the [ ] version. The fix clears the parse cache inside verify.
|
|
249
|
+
const verified = verifyExpectedArtifact("complete-slice", "M001/S01", base);
|
|
250
|
+
assert.equal(verified, true, "verifyExpectedArtifact should return true when roadmap has [x]");
|
|
251
|
+
} finally {
|
|
191
252
|
clearParseCache();
|
|
192
253
|
cleanup(base);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Build a roadmap long enough that the [x] change is outside the first/last 100 chars
|
|
196
|
-
const padding = "A".repeat(200);
|
|
197
|
-
const roadmapBefore = [
|
|
198
|
-
`# M001: Test Milestone ${padding}`,
|
|
199
|
-
"",
|
|
200
|
-
"## Slices",
|
|
201
|
-
"",
|
|
202
|
-
"- [ ] **S01: First slice** `risk:low`",
|
|
203
|
-
"",
|
|
204
|
-
`## Footer ${padding}`,
|
|
205
|
-
].join("\n");
|
|
206
|
-
const roadmapAfter = roadmapBefore.replace("- [ ] **S01:", "- [x] **S01:");
|
|
207
|
-
|
|
208
|
-
// Verify lengths are identical (the key collision condition)
|
|
209
|
-
assert.equal(roadmapBefore.length, roadmapAfter.length);
|
|
210
|
-
|
|
211
|
-
// Populate parse cache with the pre-edit roadmap
|
|
212
|
-
const before = parseRoadmap(roadmapBefore);
|
|
213
|
-
const sliceBefore = before.slices.find(s => s.id === "S01");
|
|
214
|
-
assert.ok(sliceBefore);
|
|
215
|
-
assert.equal(sliceBefore!.done, false);
|
|
216
|
-
|
|
217
|
-
// Now write the post-edit roadmap to disk and create required artifacts
|
|
218
|
-
const roadmapPath = join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
|
|
219
|
-
writeFileSync(roadmapPath, roadmapAfter);
|
|
220
|
-
const summaryPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
|
|
221
|
-
writeFileSync(summaryPath, "# Summary\nDone.");
|
|
222
|
-
const uatPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT.md");
|
|
223
|
-
writeFileSync(uatPath, "# UAT\nPassed.");
|
|
224
|
-
|
|
225
|
-
// verifyExpectedArtifact should see the [x] despite the parse cache
|
|
226
|
-
// having the [ ] version. The fix clears the parse cache inside verify.
|
|
227
|
-
const verified = verifyExpectedArtifact("complete-slice", "M001/S01", base);
|
|
228
|
-
assert.equal(verified, true, "verifyExpectedArtifact should return true when roadmap has [x]");
|
|
254
|
+
}
|
|
229
255
|
});
|
|
230
256
|
|
|
231
257
|
// ─── verifyExpectedArtifact: plan-slice empty scaffold regression (#699) ──
|
|
232
258
|
|
|
233
|
-
test("verifyExpectedArtifact rejects plan-slice with empty scaffold", (
|
|
259
|
+
test("verifyExpectedArtifact rejects plan-slice with empty scaffold", () => {
|
|
234
260
|
const base = makeTmpBase();
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
261
|
+
try {
|
|
262
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
263
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
264
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), "# S01: Test Slice\n\n## Tasks\n\n");
|
|
265
|
+
assert.strictEqual(
|
|
266
|
+
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
267
|
+
false,
|
|
268
|
+
"Empty scaffold should not be treated as completed artifact",
|
|
269
|
+
);
|
|
270
|
+
} finally {
|
|
271
|
+
cleanup(base);
|
|
272
|
+
}
|
|
245
273
|
});
|
|
246
274
|
|
|
247
|
-
test("verifyExpectedArtifact accepts plan-slice with actual tasks", (
|
|
275
|
+
test("verifyExpectedArtifact accepts plan-slice with actual tasks", () => {
|
|
248
276
|
const base = makeTmpBase();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
try {
|
|
278
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
279
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
280
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
281
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
282
|
+
"# S01: Test Slice",
|
|
283
|
+
"",
|
|
284
|
+
"## Tasks",
|
|
285
|
+
"",
|
|
286
|
+
"- [ ] **T01: Implement feature** `est:2h`",
|
|
287
|
+
"- [ ] **T02: Write tests** `est:1h`",
|
|
288
|
+
].join("\n"));
|
|
289
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
290
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
|
|
291
|
+
assert.strictEqual(
|
|
292
|
+
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
293
|
+
true,
|
|
294
|
+
"Plan with task entries should be treated as completed artifact",
|
|
295
|
+
);
|
|
296
|
+
} finally {
|
|
297
|
+
cleanup(base);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("verifyExpectedArtifact accepts plan-slice with completed tasks", () => {
|
|
272
302
|
const base = makeTmpBase();
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
try {
|
|
304
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
305
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
306
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
307
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
308
|
+
"# S01: Test Slice",
|
|
309
|
+
"",
|
|
310
|
+
"## Tasks",
|
|
311
|
+
"",
|
|
312
|
+
"- [x] **T01: Implement feature** `est:2h`",
|
|
313
|
+
"- [ ] **T02: Write tests** `est:1h`",
|
|
314
|
+
].join("\n"));
|
|
315
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
316
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
|
|
317
|
+
assert.strictEqual(
|
|
318
|
+
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
319
|
+
true,
|
|
320
|
+
"Plan with completed task entries should be treated as completed artifact",
|
|
321
|
+
);
|
|
322
|
+
} finally {
|
|
323
|
+
cleanup(base);
|
|
324
|
+
}
|
|
293
325
|
});
|
|
294
326
|
|
|
295
327
|
// ─── verifyExpectedArtifact: plan-slice task plan check (#739) ────────────
|
|
296
328
|
|
|
297
|
-
test("verifyExpectedArtifact plan-slice passes when all task plan files exist", (
|
|
298
|
-
const base = makeTmpBase();
|
|
299
|
-
t.after(() => cleanup(base));
|
|
300
|
-
|
|
301
|
-
const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
302
|
-
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
303
|
-
const planContent = [
|
|
304
|
-
"# S01: Test Slice",
|
|
305
|
-
"",
|
|
306
|
-
"## Tasks",
|
|
307
|
-
"",
|
|
308
|
-
"- [ ] **T01: First task** `est:1h`",
|
|
309
|
-
"- [ ] **T02: Second task** `est:2h`",
|
|
310
|
-
].join("\n");
|
|
311
|
-
writeFileSync(planPath, planContent);
|
|
312
|
-
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
|
|
313
|
-
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan\n\nDo the other thing.");
|
|
314
|
-
|
|
315
|
-
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
316
|
-
assert.equal(result, true, "should pass when all task plan files exist");
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test("verifyExpectedArtifact plan-slice fails when a task plan file is missing (#739)", (t) => {
|
|
329
|
+
test("verifyExpectedArtifact plan-slice passes when all task plan files exist", () => {
|
|
320
330
|
const base = makeTmpBase();
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
331
|
+
try {
|
|
332
|
+
const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
333
|
+
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
334
|
+
const planContent = [
|
|
335
|
+
"# S01: Test Slice",
|
|
336
|
+
"",
|
|
337
|
+
"## Tasks",
|
|
338
|
+
"",
|
|
339
|
+
"- [ ] **T01: First task** `est:1h`",
|
|
340
|
+
"- [ ] **T02: Second task** `est:2h`",
|
|
341
|
+
].join("\n");
|
|
342
|
+
writeFileSync(planPath, planContent);
|
|
343
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
|
|
344
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan\n\nDo the other thing.");
|
|
345
|
+
|
|
346
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
347
|
+
assert.equal(result, true, "should pass when all task plan files exist");
|
|
348
|
+
} finally {
|
|
349
|
+
cleanup(base);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("verifyExpectedArtifact plan-slice fails when a task plan file is missing (#739)", () => {
|
|
342
354
|
const base = makeTmpBase();
|
|
343
|
-
|
|
355
|
+
try {
|
|
356
|
+
const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
357
|
+
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
358
|
+
const planContent = [
|
|
359
|
+
"# S01: Test Slice",
|
|
360
|
+
"",
|
|
361
|
+
"## Tasks",
|
|
362
|
+
"",
|
|
363
|
+
"- [ ] **T01: First task** `est:1h`",
|
|
364
|
+
"- [ ] **T02: Second task** `est:2h`",
|
|
365
|
+
].join("\n");
|
|
366
|
+
writeFileSync(planPath, planContent);
|
|
367
|
+
// Only write T01-PLAN.md — T02 is missing
|
|
368
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
|
|
369
|
+
|
|
370
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
371
|
+
assert.equal(result, false, "should fail when T02-PLAN.md is missing");
|
|
372
|
+
} finally {
|
|
373
|
+
cleanup(base);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
344
376
|
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
"",
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", () => {
|
|
378
|
+
const base = makeTmpBase();
|
|
379
|
+
try {
|
|
380
|
+
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
381
|
+
const planContent = [
|
|
382
|
+
"# S01: Test Slice",
|
|
383
|
+
"",
|
|
384
|
+
"## Goal",
|
|
385
|
+
"",
|
|
386
|
+
"Just some documentation updates, no tasks.",
|
|
387
|
+
].join("\n");
|
|
388
|
+
writeFileSync(planPath, planContent);
|
|
354
389
|
|
|
355
|
-
|
|
356
|
-
|
|
390
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
391
|
+
assert.equal(result, false, "should fail when plan has no task entries (empty scaffold, #699)");
|
|
392
|
+
} finally {
|
|
393
|
+
cleanup(base);
|
|
394
|
+
}
|
|
357
395
|
});
|
|
358
396
|
|
|
359
397
|
// ─── verifyExpectedArtifact: heading-style plan tasks (#1691) ─────────────
|
|
360
398
|
|
|
361
|
-
test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T01 --)", (
|
|
399
|
+
test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T01 --)", () => {
|
|
362
400
|
const base = makeTmpBase();
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
401
|
+
try {
|
|
402
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
403
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
404
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
405
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
406
|
+
"# S01: Test Slice",
|
|
407
|
+
"",
|
|
408
|
+
"## Tasks",
|
|
409
|
+
"",
|
|
410
|
+
"### T01 -- Implement feature",
|
|
411
|
+
"",
|
|
412
|
+
"Feature description.",
|
|
413
|
+
"",
|
|
414
|
+
"### T02 -- Write tests",
|
|
415
|
+
"",
|
|
416
|
+
"Test description.",
|
|
417
|
+
].join("\n"));
|
|
418
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
419
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
|
|
420
|
+
assert.strictEqual(
|
|
421
|
+
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
422
|
+
true,
|
|
423
|
+
"Heading-style plan with task entries should be treated as completed artifact",
|
|
424
|
+
);
|
|
425
|
+
} finally {
|
|
426
|
+
cleanup(base);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("verifyExpectedArtifact accepts plan-slice with colon-style heading tasks (### T01:)", () => {
|
|
391
431
|
const base = makeTmpBase();
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
432
|
+
try {
|
|
433
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
434
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
435
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
436
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
437
|
+
"# S01: Test Slice",
|
|
438
|
+
"",
|
|
439
|
+
"## Tasks",
|
|
440
|
+
"",
|
|
441
|
+
"### T01: Implement feature",
|
|
442
|
+
"",
|
|
443
|
+
"Feature description.",
|
|
444
|
+
].join("\n"));
|
|
445
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
446
|
+
assert.strictEqual(
|
|
447
|
+
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
448
|
+
true,
|
|
449
|
+
"Colon heading-style plan should be treated as completed artifact",
|
|
450
|
+
);
|
|
451
|
+
} finally {
|
|
452
|
+
cleanup(base);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("verifyExpectedArtifact execute-task passes for heading-style plan entry (#1691)", () => {
|
|
415
457
|
const base = makeTmpBase();
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
458
|
+
try {
|
|
459
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
460
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
461
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
462
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
463
|
+
"# S01: Test Slice",
|
|
464
|
+
"",
|
|
465
|
+
"## Tasks",
|
|
466
|
+
"",
|
|
467
|
+
"### T01 -- Implement feature",
|
|
468
|
+
"",
|
|
469
|
+
"Feature description.",
|
|
470
|
+
].join("\n"));
|
|
471
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone.");
|
|
472
|
+
assert.strictEqual(
|
|
473
|
+
verifyExpectedArtifact("execute-task", "M001/S01/T01", base),
|
|
474
|
+
true,
|
|
475
|
+
"execute-task should pass for heading-style plan entry when summary exists",
|
|
476
|
+
);
|
|
477
|
+
} finally {
|
|
478
|
+
cleanup(base);
|
|
479
|
+
}
|
|
436
480
|
});
|
|
437
481
|
|
|
438
482
|
test("verifyExpectedArtifact plan-slice passes for rendered slice/task plan artifacts from DB", async () => {
|
|
@@ -574,81 +618,83 @@ test("verifyExpectedArtifact plan-slice fails after deleting a rendered task pla
|
|
|
574
618
|
|
|
575
619
|
// ─── selfHealRuntimeRecords — worktree base path (#769) ──────────────────
|
|
576
620
|
|
|
577
|
-
test("selfHealRuntimeRecords clears stale dispatched records (#769)", async (
|
|
621
|
+
test("selfHealRuntimeRecords clears stale dispatched records (#769)", async () => {
|
|
578
622
|
// selfHealRuntimeRecords now only clears stale dispatched records (>1h).
|
|
579
623
|
// No completedKeySet parameter — deriveState is sole authority.
|
|
580
624
|
const worktreeBase = makeTmpBase();
|
|
581
625
|
const mainBase = makeTmpBase();
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
cleanup(mainBase);
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
|
|
626
|
+
try {
|
|
627
|
+
const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
|
|
588
628
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
629
|
+
// Write a stale runtime record in the worktree .gsd/runtime/units/
|
|
630
|
+
writeUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
|
|
631
|
+
phase: "dispatched",
|
|
632
|
+
});
|
|
593
633
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
634
|
+
// Verify the runtime record exists before heal
|
|
635
|
+
const before = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
|
|
636
|
+
assert.ok(before, "runtime record should exist before heal");
|
|
597
637
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
638
|
+
// Mock ExtensionContext with minimal notify
|
|
639
|
+
const notifications: string[] = [];
|
|
640
|
+
const mockCtx = {
|
|
641
|
+
ui: { notify: (msg: string) => { notifications.push(msg); } },
|
|
642
|
+
} as any;
|
|
603
643
|
|
|
604
|
-
|
|
605
|
-
|
|
644
|
+
// Call selfHeal with worktreeBase — should clear the stale record
|
|
645
|
+
await selfHealRuntimeRecords(worktreeBase, mockCtx);
|
|
606
646
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
647
|
+
// The stale record should be cleared
|
|
648
|
+
const after = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
|
|
649
|
+
assert.equal(after, null, "runtime record should be cleared after heal");
|
|
650
|
+
assert.ok(notifications.some(n => n.includes("Self-heal")), "should emit self-heal notification");
|
|
611
651
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
652
|
+
// Write a stale record at mainBase
|
|
653
|
+
writeUnitRuntimeRecord(mainBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
|
|
654
|
+
phase: "dispatched",
|
|
655
|
+
});
|
|
656
|
+
await selfHealRuntimeRecords(mainBase, mockCtx);
|
|
617
657
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
658
|
+
// The record at mainBase should also be cleared by the stale timeout (>1h)
|
|
659
|
+
const afterMain = readUnitRuntimeRecord(mainBase, "run-uat", "M001/S01");
|
|
660
|
+
assert.equal(afterMain, null, "stale record at main base should be cleared by timeout");
|
|
661
|
+
} finally {
|
|
662
|
+
cleanup(worktreeBase);
|
|
663
|
+
cleanup(mainBase);
|
|
664
|
+
}
|
|
621
665
|
});
|
|
622
666
|
|
|
623
667
|
// ─── #1625: selfHealRuntimeRecords on resume clears paused-session leftovers ──
|
|
624
668
|
|
|
625
|
-
test("selfHealRuntimeRecords clears recently-paused dispatched records on resume (#1625)", async (
|
|
669
|
+
test("selfHealRuntimeRecords clears recently-paused dispatched records on resume (#1625)", async () => {
|
|
626
670
|
// When pauseAuto closes out a unit but clearUnitRuntimeRecord silently fails
|
|
627
671
|
// (e.g. permission error), selfHealRuntimeRecords on resume should still
|
|
628
672
|
// clean up stale dispatched records that are >1h old.
|
|
629
673
|
const base = makeTmpBase();
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
|
|
674
|
+
try {
|
|
675
|
+
const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
|
|
633
676
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
677
|
+
// Simulate a record left behind after a pause — aged >1h to be considered stale
|
|
678
|
+
writeUnitRuntimeRecord(base, "execute-task", "M001/S01/T01", Date.now() - 3700_000, {
|
|
679
|
+
phase: "dispatched",
|
|
680
|
+
});
|
|
638
681
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
682
|
+
const before = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
|
|
683
|
+
assert.ok(before, "dispatched record should exist before resume heal");
|
|
684
|
+
assert.equal(before!.phase, "dispatched");
|
|
642
685
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
686
|
+
const notifications: string[] = [];
|
|
687
|
+
const mockCtx = {
|
|
688
|
+
ui: { notify: (msg: string) => { notifications.push(msg); } },
|
|
689
|
+
} as any;
|
|
647
690
|
|
|
648
|
-
|
|
691
|
+
await selfHealRuntimeRecords(base, mockCtx);
|
|
649
692
|
|
|
650
|
-
|
|
651
|
-
|
|
693
|
+
const after = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
|
|
694
|
+
assert.equal(after, null, "stale dispatched record should be cleared on resume (#1625)");
|
|
695
|
+
} finally {
|
|
696
|
+
cleanup(base);
|
|
697
|
+
}
|
|
652
698
|
});
|
|
653
699
|
|
|
654
700
|
// ─── #793: invalidateAllCaches unblocks skip-loop ─────────────────────────
|
|
@@ -656,49 +702,51 @@ test("selfHealRuntimeRecords clears recently-paused dispatched records on resume
|
|
|
656
702
|
// just invalidateStateCache()) to clear path/parse caches that deriveState
|
|
657
703
|
// depends on. Without this, even after cache invalidation, deriveState reads
|
|
658
704
|
// stale directory listings and returns the same unit, looping forever.
|
|
659
|
-
test("#793: invalidateAllCaches clears all caches so deriveState sees fresh disk state", async (
|
|
705
|
+
test("#793: invalidateAllCaches clears all caches so deriveState sees fresh disk state", async () => {
|
|
660
706
|
const base = makeTmpBase();
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
707
|
+
try {
|
|
708
|
+
const mid = "M001";
|
|
709
|
+
const sid = "S01";
|
|
710
|
+
const planDir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
711
|
+
const tasksDir = join(planDir, "tasks");
|
|
712
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
713
|
+
mkdirSync(join(base, ".gsd", "milestones", mid), { recursive: true });
|
|
714
|
+
|
|
715
|
+
writeFileSync(
|
|
716
|
+
join(base, ".gsd", "milestones", mid, `${mid}-ROADMAP.md`),
|
|
717
|
+
`# M001: Test Milestone\n\n**Vision:** test.\n\n## Slices\n\n- [ ] **${sid}: Slice One** \`risk:low\` \`depends:[]\`\n > After this: done.\n`,
|
|
718
|
+
);
|
|
719
|
+
const planUnchecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [ ] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
|
|
720
|
+
writeFileSync(join(planDir, `${sid}-PLAN.md`), planUnchecked);
|
|
721
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01: Task One\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
|
|
722
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02: Task Two\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
|
|
723
|
+
|
|
724
|
+
// Warm all caches
|
|
725
|
+
const state1 = await deriveState(base);
|
|
726
|
+
assert.equal(state1.activeTask?.id, "T01", "initial: T01 is active");
|
|
727
|
+
|
|
728
|
+
// Simulate task completion on disk (what the LLM does)
|
|
729
|
+
const planChecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [x] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
|
|
730
|
+
writeFileSync(join(planDir, `${sid}-PLAN.md`), planChecked);
|
|
731
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# Summary\n");
|
|
732
|
+
|
|
733
|
+
// invalidateStateCache alone: _stateCache cleared but path/parse caches warm
|
|
734
|
+
invalidateStateCache();
|
|
735
|
+
|
|
736
|
+
// invalidateAllCaches: all caches cleared — deriveState must re-read disk
|
|
737
|
+
invalidateAllCaches();
|
|
738
|
+
const state2 = await deriveState(base);
|
|
739
|
+
|
|
740
|
+
// After full invalidation, T01 should be complete and T02 should be next
|
|
741
|
+
assert.notEqual(state2.activeTask?.id, "T01", "#793: T01 not re-dispatched after full invalidation");
|
|
742
|
+
|
|
743
|
+
// Verify the caches are truly cleared by calling clearParseCache and clearPathCache
|
|
744
|
+
// do not throw (they should be no-ops after invalidateAllCaches already cleared them)
|
|
745
|
+
clearParseCache(); // no-op, but should not throw
|
|
746
|
+
assert.ok(true, "clearParseCache after invalidateAllCaches is safe");
|
|
747
|
+
} finally {
|
|
748
|
+
cleanup(base);
|
|
749
|
+
}
|
|
702
750
|
});
|
|
703
751
|
|
|
704
752
|
// ─── hasImplementationArtifacts (#1703) ───────────────────────────────────
|
|
@@ -718,78 +766,88 @@ function makeGitBase(): string {
|
|
|
718
766
|
return base;
|
|
719
767
|
}
|
|
720
768
|
|
|
721
|
-
test("hasImplementationArtifacts returns false when only .gsd/ files committed (#1703)", (
|
|
769
|
+
test("hasImplementationArtifacts returns false when only .gsd/ files committed (#1703)", () => {
|
|
722
770
|
const base = makeGitBase();
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
771
|
+
try {
|
|
772
|
+
// Create a feature branch and commit only .gsd/ files
|
|
773
|
+
execFileSync("git", ["checkout", "-b", "feat/test-milestone"], { cwd: base, stdio: "ignore" });
|
|
774
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
775
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# Roadmap");
|
|
776
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Summary");
|
|
777
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
778
|
+
execFileSync("git", ["commit", "-m", "chore: add plan files"], { cwd: base, stdio: "ignore" });
|
|
779
|
+
|
|
780
|
+
const result = hasImplementationArtifacts(base);
|
|
781
|
+
assert.equal(result, false, "should return false when only .gsd/ files were committed");
|
|
782
|
+
} finally {
|
|
783
|
+
cleanup(base);
|
|
784
|
+
}
|
|
735
785
|
});
|
|
736
786
|
|
|
737
|
-
test("hasImplementationArtifacts returns true when implementation files committed (#1703)", (
|
|
787
|
+
test("hasImplementationArtifacts returns true when implementation files committed (#1703)", () => {
|
|
738
788
|
const base = makeGitBase();
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
789
|
+
try {
|
|
790
|
+
// Create a feature branch with both .gsd/ and implementation files
|
|
791
|
+
execFileSync("git", ["checkout", "-b", "feat/test-impl"], { cwd: base, stdio: "ignore" });
|
|
792
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
793
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# Roadmap");
|
|
794
|
+
mkdirSync(join(base, "src"), { recursive: true });
|
|
795
|
+
writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}");
|
|
796
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
797
|
+
execFileSync("git", ["commit", "-m", "feat: add feature"], { cwd: base, stdio: "ignore" });
|
|
798
|
+
|
|
799
|
+
const result = hasImplementationArtifacts(base);
|
|
800
|
+
assert.equal(result, true, "should return true when implementation files are present");
|
|
801
|
+
} finally {
|
|
802
|
+
cleanup(base);
|
|
803
|
+
}
|
|
752
804
|
});
|
|
753
805
|
|
|
754
|
-
test("hasImplementationArtifacts returns true on non-git directory (fail-open)", (
|
|
806
|
+
test("hasImplementationArtifacts returns true on non-git directory (fail-open)", () => {
|
|
755
807
|
const base = join(tmpdir(), `gsd-test-nogit-${randomUUID()}`);
|
|
756
808
|
mkdirSync(base, { recursive: true });
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
809
|
+
try {
|
|
810
|
+
const result = hasImplementationArtifacts(base);
|
|
811
|
+
assert.equal(result, true, "should return true (fail-open) in non-git directory");
|
|
812
|
+
} finally {
|
|
813
|
+
cleanup(base);
|
|
814
|
+
}
|
|
761
815
|
});
|
|
762
816
|
|
|
763
817
|
// ─── verifyExpectedArtifact: complete-milestone requires impl artifacts (#1703) ──
|
|
764
818
|
|
|
765
|
-
test("verifyExpectedArtifact complete-milestone fails with only .gsd/ files (#1703)", (
|
|
819
|
+
test("verifyExpectedArtifact complete-milestone fails with only .gsd/ files (#1703)", () => {
|
|
766
820
|
const base = makeGitBase();
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
821
|
+
try {
|
|
822
|
+
// Create feature branch with only .gsd/ files
|
|
823
|
+
execFileSync("git", ["checkout", "-b", "feat/ms-only-gsd"], { cwd: base, stdio: "ignore" });
|
|
824
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
825
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
|
|
826
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
827
|
+
execFileSync("git", ["commit", "-m", "chore: milestone plan files"], { cwd: base, stdio: "ignore" });
|
|
828
|
+
|
|
829
|
+
const result = verifyExpectedArtifact("complete-milestone", "M001", base);
|
|
830
|
+
assert.equal(result, false, "complete-milestone should fail verification when only .gsd/ files present");
|
|
831
|
+
} finally {
|
|
832
|
+
cleanup(base);
|
|
833
|
+
}
|
|
778
834
|
});
|
|
779
835
|
|
|
780
|
-
test("verifyExpectedArtifact complete-milestone passes with impl files (#1703)", (
|
|
836
|
+
test("verifyExpectedArtifact complete-milestone passes with impl files (#1703)", () => {
|
|
781
837
|
const base = makeGitBase();
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
838
|
+
try {
|
|
839
|
+
// Create feature branch with implementation files AND milestone summary
|
|
840
|
+
execFileSync("git", ["checkout", "-b", "feat/ms-with-impl"], { cwd: base, stdio: "ignore" });
|
|
841
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
842
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
|
|
843
|
+
mkdirSync(join(base, "src"), { recursive: true });
|
|
844
|
+
writeFileSync(join(base, "src", "app.ts"), "console.log('hello');");
|
|
845
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
846
|
+
execFileSync("git", ["commit", "-m", "feat: implementation"], { cwd: base, stdio: "ignore" });
|
|
847
|
+
|
|
848
|
+
const result = verifyExpectedArtifact("complete-milestone", "M001", base);
|
|
849
|
+
assert.equal(result, true, "complete-milestone should pass verification with implementation files");
|
|
850
|
+
} finally {
|
|
851
|
+
cleanup(base);
|
|
852
|
+
}
|
|
795
853
|
});
|