gsd-pi 2.44.0-dev.848dd4c → 2.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -30
- package/dist/resources/extensions/gsd/auto-start.js +0 -10
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +0 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/229.js +1 -1
- package/dist/web/standalone/.next/server/chunks/471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-7e9530a7122506c5.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +8 -6
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +26 -24
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +48 -29
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +44 -34
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +34 -30
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +12 -10
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +47 -43
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +43 -31
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +45 -40
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto-start.ts +0 -14
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +0 -8
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +16 -14
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +57 -43
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +13 -11
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +523 -465
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +75 -73
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +56 -34
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +656 -533
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +143 -165
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +52 -29
- package/src/resources/extensions/gsd/tests/captures.test.ts +176 -148
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +33 -32
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +143 -141
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +59 -38
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +263 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +302 -250
- package/src/resources/extensions/gsd/tests/context-store.test.ts +367 -354
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +72 -68
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +106 -92
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +35 -27
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +237 -220
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +420 -390
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +92 -76
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +83 -68
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +183 -152
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +101 -78
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +227 -192
- package/src/resources/extensions/gsd/tests/detection.test.ts +278 -232
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +34 -30
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +180 -164
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +49 -43
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +32 -28
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +29 -27
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +38 -34
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +75 -54
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +32 -21
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +97 -72
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +44 -38
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +145 -104
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +106 -84
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +60 -54
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +93 -72
- package/src/resources/extensions/gsd/tests/doctor.test.ts +134 -104
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +131 -123
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +24 -20
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +57 -48
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +42 -30
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +206 -198
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +27 -13
- package/src/resources/extensions/gsd/tests/git-service.test.ts +388 -285
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +39 -31
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +69 -63
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +264 -255
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +119 -108
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +103 -81
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +262 -229
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +37 -29
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +102 -81
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +18 -16
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +46 -41
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +53 -42
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +91 -75
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +194 -150
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +125 -101
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +54 -45
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +93 -80
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +66 -57
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +93 -83
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +170 -161
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +141 -125
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +131 -107
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +96 -87
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +164 -125
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +94 -81
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +36 -35
- package/src/resources/extensions/gsd/tests/overrides.test.ts +106 -99
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +47 -40
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +28 -25
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +83 -66
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +77 -54
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +115 -68
- package/src/resources/extensions/gsd/tests/parsers.test.ts +611 -546
- package/src/resources/extensions/gsd/tests/paths.test.ts +87 -72
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +117 -77
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +119 -93
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +82 -70
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +55 -42
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +73 -45
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +38 -28
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +80 -73
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +74 -71
- package/src/resources/extensions/gsd/tests/requirements.test.ts +75 -70
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +66 -44
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +181 -114
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +65 -63
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +128 -66
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +25 -18
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +44 -37
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +26 -19
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +28 -22
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +56 -54
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +25 -23
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +11 -9
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +82 -66
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +47 -46
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +22 -20
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +86 -84
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +43 -41
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +96 -94
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +13 -11
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +29 -27
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +52 -50
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +13 -10
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +18 -14
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +39 -38
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +21 -17
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +30 -25
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +37 -30
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +22 -15
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +66 -59
- package/src/resources/extensions/gsd/tests/worktree.test.ts +50 -44
- package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +0 -100
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +0 -63
- /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → mgkxN0mGP6gSUmGPEzbk_}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { describe, test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
1
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
2
|
import { join, dirname } from 'node:path';
|
|
5
3
|
import { tmpdir } from 'node:os';
|
|
@@ -24,6 +22,7 @@ function loadPromptFromWorktree(name: string, vars: Record<string, string> = {})
|
|
|
24
22
|
return content.trim();
|
|
25
23
|
}
|
|
26
24
|
|
|
25
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
27
26
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
28
27
|
|
|
29
28
|
function createFixtureBase(): string {
|
|
@@ -162,7 +161,7 @@ Found a blocker.
|
|
|
162
161
|
`;
|
|
163
162
|
|
|
164
163
|
const s = parseSummary(content);
|
|
165
|
-
|
|
164
|
+
assertEq(s.frontmatter.blocker_discovered, true, 'blocker_discovered: true (string) extracts as true');
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
console.log('\n=== parseSummary: blocker_discovered false (string) ===');
|
|
@@ -185,7 +184,7 @@ No blocker.
|
|
|
185
184
|
`;
|
|
186
185
|
|
|
187
186
|
const s = parseSummary(content);
|
|
188
|
-
|
|
187
|
+
assertEq(s.frontmatter.blocker_discovered, false, 'blocker_discovered: false extracts as false');
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
console.log('\n=== parseSummary: blocker_discovered missing (defaults to false) ===');
|
|
@@ -207,7 +206,7 @@ No blocker field at all.
|
|
|
207
206
|
`;
|
|
208
207
|
|
|
209
208
|
const s = parseSummary(content);
|
|
210
|
-
|
|
209
|
+
assertEq(s.frontmatter.blocker_discovered, false, 'blocker_discovered missing defaults to false');
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
console.log('\n=== parseSummary: blocker_discovered true (boolean from YAML) ===');
|
|
@@ -233,7 +232,7 @@ Blocker as boolean.
|
|
|
233
232
|
`;
|
|
234
233
|
|
|
235
234
|
const s = parseSummary(content);
|
|
236
|
-
|
|
235
|
+
assertEq(s.frontmatter.blocker_discovered, true, 'blocker_discovered: true (YAML boolean) extracts as true');
|
|
237
236
|
}
|
|
238
237
|
|
|
239
238
|
console.log('\n=== parseSummary: blocker_discovered with full frontmatter ===');
|
|
@@ -276,10 +275,10 @@ Major deviation from plan.
|
|
|
276
275
|
`;
|
|
277
276
|
|
|
278
277
|
const s = parseSummary(content);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
assertEq(s.frontmatter.blocker_discovered, true, 'blocker_discovered true with full frontmatter');
|
|
279
|
+
assertEq(s.frontmatter.id, 'T05', 'other fields still parse correctly alongside blocker_discovered');
|
|
280
|
+
assertEq(s.frontmatter.duration, '15min', 'duration still parsed');
|
|
281
|
+
assertEq(s.frontmatter.provides[0], 'something', 'provides still parsed');
|
|
283
282
|
}
|
|
284
283
|
|
|
285
284
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -295,11 +294,11 @@ console.log('\n=== deriveState: blocker found, no REPLAN → replanning-slice ==
|
|
|
295
294
|
writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', true));
|
|
296
295
|
|
|
297
296
|
const state = await deriveState(base);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
297
|
+
assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice when blocker found and no REPLAN.md');
|
|
298
|
+
assertTrue(state.nextAction.includes('T01'), 'nextAction mentions blocker task T01');
|
|
299
|
+
assertTrue(state.nextAction.includes('blocker_discovered'), 'nextAction mentions blocker_discovered');
|
|
300
|
+
assertEq(state.activeTask?.id, 'T02', 'activeTask is still T02 (the next incomplete task)');
|
|
301
|
+
assertTrue(state.blockers.length > 0, 'blockers array is non-empty');
|
|
303
302
|
rmSync(base, { recursive: true, force: true });
|
|
304
303
|
}
|
|
305
304
|
|
|
@@ -313,8 +312,8 @@ console.log('\n=== deriveState: blocker found + REPLAN exists → executing (loo
|
|
|
313
312
|
writeReplanFile(base, 'M001', 'S01', '# Replan\n\nAlready replanned.');
|
|
314
313
|
|
|
315
314
|
const state = await deriveState(base);
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
assertEq(state.phase, 'executing', 'phase is executing when REPLAN.md exists (loop protection)');
|
|
316
|
+
assertEq(state.activeTask?.id, 'T02', 'activeTask is T02');
|
|
318
317
|
rmSync(base, { recursive: true, force: true });
|
|
319
318
|
}
|
|
320
319
|
|
|
@@ -327,8 +326,8 @@ console.log('\n=== deriveState: no blocker in completed tasks → executing ==='
|
|
|
327
326
|
writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', false));
|
|
328
327
|
|
|
329
328
|
const state = await deriveState(base);
|
|
330
|
-
|
|
331
|
-
|
|
329
|
+
assertEq(state.phase, 'executing', 'phase is executing when no blocker found');
|
|
330
|
+
assertEq(state.activeTask?.id, 'T02', 'activeTask is T02');
|
|
332
331
|
rmSync(base, { recursive: true, force: true });
|
|
333
332
|
}
|
|
334
333
|
|
|
@@ -342,9 +341,9 @@ console.log('\n=== deriveState: multiple completed tasks, one blocker → replan
|
|
|
342
341
|
writeTaskSummary(base, 'M001', 'S01', 'T02', makeTaskSummary('T02', true));
|
|
343
342
|
|
|
344
343
|
const state = await deriveState(base);
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
344
|
+
assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice when T02 has blocker');
|
|
345
|
+
assertTrue(state.nextAction.includes('T02'), 'nextAction mentions blocker task T02');
|
|
346
|
+
assertEq(state.activeTask?.id, 'T03', 'activeTask is T03 (next incomplete)');
|
|
348
347
|
rmSync(base, { recursive: true, force: true });
|
|
349
348
|
}
|
|
350
349
|
|
|
@@ -357,7 +356,7 @@ console.log('\n=== deriveState: completed task with no summary file → executin
|
|
|
357
356
|
// No summary file written for T01
|
|
358
357
|
|
|
359
358
|
const state = await deriveState(base);
|
|
360
|
-
|
|
359
|
+
assertEq(state.phase, 'executing', 'phase is executing when completed task has no summary');
|
|
361
360
|
rmSync(base, { recursive: true, force: true });
|
|
362
361
|
}
|
|
363
362
|
|
|
@@ -377,11 +376,11 @@ console.log('\n=== prompt: replan-slice template loads and substitutes variables
|
|
|
377
376
|
inlinedContext: '## Inlined Context\n\nTest context here.',
|
|
378
377
|
});
|
|
379
378
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
379
|
+
assertTrue(prompt.includes('M001'), 'prompt contains milestoneId');
|
|
380
|
+
assertTrue(prompt.includes('S01'), 'prompt contains sliceId');
|
|
381
|
+
assertTrue(prompt.includes('Test Slice'), 'prompt contains sliceTitle');
|
|
382
|
+
assertTrue(prompt.includes('.gsd/milestones/M001/slices/S01/S01-PLAN.md'), 'prompt contains planPath');
|
|
383
|
+
assertTrue(prompt.includes('Test context here'), 'prompt contains inlined context');
|
|
385
384
|
}
|
|
386
385
|
|
|
387
386
|
console.log('\n=== prompt: replan-slice contains preserve-completed-tasks instruction ===');
|
|
@@ -398,10 +397,10 @@ console.log('\n=== prompt: replan-slice contains preserve-completed-tasks instru
|
|
|
398
397
|
inlinedContext: '',
|
|
399
398
|
});
|
|
400
399
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
400
|
+
assertTrue(prompt.includes('Do NOT renumber or remove completed tasks'), 'prompt contains preserve-completed-tasks instruction');
|
|
401
|
+
assertTrue(prompt.includes('[x]'), 'prompt mentions [x] checkmarks');
|
|
402
|
+
assertTrue(prompt.includes('REPLAN'), 'prompt references replan output path');
|
|
403
|
+
assertTrue(prompt.includes('blocker_discovered'), 'prompt mentions blocker_discovered');
|
|
405
404
|
}
|
|
406
405
|
|
|
407
406
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -422,8 +421,8 @@ console.log('\n=== dispatch: diagnoseExpectedArtifact returns REPLAN.md path ===
|
|
|
422
421
|
writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', true));
|
|
423
422
|
|
|
424
423
|
const state = await deriveState(base);
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
assertEq(state.phase, 'replanning-slice', 'dispatch: state routes to replanning-slice when blocker found');
|
|
425
|
+
assertTrue(state.activeSlice?.id === 'S01', 'dispatch: activeSlice is S01');
|
|
427
426
|
rmSync(base, { recursive: true, force: true });
|
|
428
427
|
}
|
|
429
428
|
|
|
@@ -444,8 +443,8 @@ console.log('\n=== display: replan-slice prompt template has correct unit header
|
|
|
444
443
|
inlinedContext: '',
|
|
445
444
|
});
|
|
446
445
|
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
assertTrue(prompt.includes('UNIT: Replan Slice'), 'prompt has Replan Slice unit header');
|
|
447
|
+
assertTrue(prompt.includes('Slice S01 replanned'), 'prompt has completion message');
|
|
449
448
|
}
|
|
450
449
|
|
|
451
450
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -453,6 +452,8 @@ console.log('\n=== display: replan-slice prompt template has correct unit header
|
|
|
453
452
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
454
453
|
|
|
455
454
|
import { runGSDDoctor } from '../doctor.ts';
|
|
455
|
+
import { createTestContext } from './test-helpers.ts';
|
|
456
|
+
|
|
456
457
|
// (a) blocker + no REPLAN.md → issue emitted
|
|
457
458
|
console.log('\n=== doctor: blocker + no REPLAN.md → blocker_discovered_no_replan issue ===');
|
|
458
459
|
{
|
|
@@ -463,10 +464,10 @@ console.log('\n=== doctor: blocker + no REPLAN.md → blocker_discovered_no_repl
|
|
|
463
464
|
|
|
464
465
|
const report = await runGSDDoctor(base, { fix: false, scope: 'M001/S01' });
|
|
465
466
|
const blockerIssues = report.issues.filter(i => i.code === 'blocker_discovered_no_replan');
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
467
|
+
assertTrue(blockerIssues.length > 0, 'doctor emits blocker_discovered_no_replan when blocker + no REPLAN');
|
|
468
|
+
assertTrue(blockerIssues[0]?.message.includes('T01'), 'issue message mentions the blocker task T01');
|
|
469
|
+
assertEq(blockerIssues[0]?.severity, 'warning', 'blocker_discovered_no_replan is warning severity');
|
|
470
|
+
assertEq(blockerIssues[0]?.scope, 'slice', 'blocker_discovered_no_replan has slice scope');
|
|
470
471
|
rmSync(base, { recursive: true, force: true });
|
|
471
472
|
}
|
|
472
473
|
|
|
@@ -481,7 +482,7 @@ console.log('\n=== doctor: blocker + REPLAN.md exists → no blocker_discovered_
|
|
|
481
482
|
|
|
482
483
|
const report = await runGSDDoctor(base, { fix: false, scope: 'M001/S01' });
|
|
483
484
|
const blockerIssues = report.issues.filter(i => i.code === 'blocker_discovered_no_replan');
|
|
484
|
-
|
|
485
|
+
assertEq(blockerIssues.length, 0, 'no blocker_discovered_no_replan when REPLAN.md exists');
|
|
485
486
|
rmSync(base, { recursive: true, force: true });
|
|
486
487
|
}
|
|
487
488
|
|
|
@@ -495,7 +496,7 @@ console.log('\n=== doctor: no blocker → no blocker_discovered_no_replan issue
|
|
|
495
496
|
|
|
496
497
|
const report = await runGSDDoctor(base, { fix: false, scope: 'M001/S01' });
|
|
497
498
|
const blockerIssues = report.issues.filter(i => i.code === 'blocker_discovered_no_replan');
|
|
498
|
-
|
|
499
|
+
assertEq(blockerIssues.length, 0, 'no blocker_discovered_no_replan when no blocker');
|
|
499
500
|
rmSync(base, { recursive: true, force: true });
|
|
500
501
|
}
|
|
501
502
|
|
|
@@ -505,45 +506,48 @@ console.log('\n=== doctor: no blocker → no blocker_discovered_no_replan issue
|
|
|
505
506
|
|
|
506
507
|
import { resolveExpectedArtifactPath, verifyExpectedArtifact } from '../auto-recovery.ts';
|
|
507
508
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
test('artifact: resolveExpectedArtifactPath returns REPLAN.md path for replan-slice', () => {
|
|
509
|
+
console.log('\n=== artifact: resolveExpectedArtifactPath returns REPLAN.md path for replan-slice ===');
|
|
510
|
+
{
|
|
511
511
|
const base = createFixtureBase();
|
|
512
512
|
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
|
|
513
513
|
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
|
|
514
514
|
|
|
515
515
|
const path = resolveExpectedArtifactPath('replan-slice', 'M001/S01', base);
|
|
516
|
-
|
|
517
|
-
|
|
516
|
+
assertTrue(path !== null, 'resolveExpectedArtifactPath returns non-null for replan-slice');
|
|
517
|
+
assertTrue(path!.endsWith('S01-REPLAN.md'), 'path ends with S01-REPLAN.md');
|
|
518
518
|
rmSync(base, { recursive: true, force: true });
|
|
519
|
-
}
|
|
519
|
+
}
|
|
520
520
|
|
|
521
|
-
|
|
521
|
+
console.log('\n=== artifact: verifyExpectedArtifact fails when REPLAN.md missing (#858) ===');
|
|
522
|
+
{
|
|
522
523
|
const base = createFixtureBase();
|
|
523
524
|
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
|
|
524
525
|
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
|
|
525
526
|
|
|
526
527
|
const result = verifyExpectedArtifact('replan-slice', 'M001/S01', base);
|
|
527
|
-
|
|
528
|
+
assertEq(result, false, 'verifyExpectedArtifact returns false when REPLAN.md is missing');
|
|
528
529
|
rmSync(base, { recursive: true, force: true });
|
|
529
|
-
}
|
|
530
|
+
}
|
|
530
531
|
|
|
531
|
-
|
|
532
|
+
console.log('\n=== artifact: verifyExpectedArtifact passes when REPLAN.md exists (#858) ===');
|
|
533
|
+
{
|
|
532
534
|
const base = createFixtureBase();
|
|
533
535
|
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
|
|
534
536
|
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
|
|
535
537
|
writeReplanFile(base, 'M001', 'S01', '# Replan\n\nBlocker addressed.');
|
|
536
538
|
|
|
537
539
|
const result = verifyExpectedArtifact('replan-slice', 'M001/S01', base);
|
|
538
|
-
|
|
540
|
+
assertEq(result, true, 'verifyExpectedArtifact returns true when REPLAN.md exists');
|
|
539
541
|
rmSync(base, { recursive: true, force: true });
|
|
540
|
-
}
|
|
542
|
+
}
|
|
541
543
|
|
|
542
544
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
543
545
|
// REPLAN-TRIGGER.md detection (triage-initiated replan, #1701)
|
|
544
546
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
547
|
+
|
|
545
548
|
// (a) REPLAN-TRIGGER.md exists + no REPLAN.md → replanning-slice
|
|
546
|
-
|
|
549
|
+
console.log('\n=== deriveState: REPLAN-TRIGGER.md exists, no REPLAN → replanning-slice (#1701) ===');
|
|
550
|
+
{
|
|
547
551
|
const base = createFixtureBase();
|
|
548
552
|
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
|
|
549
553
|
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
|
|
@@ -552,16 +556,17 @@ test('deriveState: REPLAN-TRIGGER.md exists, no REPLAN → replanning-slice (#17
|
|
|
552
556
|
writeReplanTrigger(base, 'M001', 'S01', '# Replan Trigger\n\n**Source:** Capture C001\n');
|
|
553
557
|
|
|
554
558
|
const state = await deriveState(base);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
559
|
+
assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice when REPLAN-TRIGGER.md exists');
|
|
560
|
+
assertTrue(state.blockers.length > 0, 'blockers array is non-empty for triage replan trigger');
|
|
561
|
+
assertTrue(state.nextAction.includes('Triage replan'), 'nextAction mentions triage replan');
|
|
562
|
+
assertEq(state.activeSlice?.id, 'S01', 'activeSlice is S01');
|
|
563
|
+
assertEq(state.activeTask?.id, 'T02', 'activeTask is T02 (next incomplete task)');
|
|
560
564
|
rmSync(base, { recursive: true, force: true });
|
|
561
|
-
}
|
|
565
|
+
}
|
|
562
566
|
|
|
563
567
|
// (b) REPLAN-TRIGGER.md + REPLAN.md both exist → executing (loop protection)
|
|
564
|
-
|
|
568
|
+
console.log('\n=== deriveState: REPLAN-TRIGGER.md + REPLAN.md → executing (loop protection, #1701) ===');
|
|
569
|
+
{
|
|
565
570
|
const base = createFixtureBase();
|
|
566
571
|
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
|
|
567
572
|
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
|
|
@@ -570,25 +575,27 @@ test('deriveState: REPLAN-TRIGGER.md + REPLAN.md → executing (loop protection,
|
|
|
570
575
|
writeReplanFile(base, 'M001', 'S01', '# Replan\n\nAlready replanned.');
|
|
571
576
|
|
|
572
577
|
const state = await deriveState(base);
|
|
573
|
-
|
|
574
|
-
|
|
578
|
+
assertEq(state.phase, 'executing', 'phase is executing when REPLAN.md exists (loop protection)');
|
|
579
|
+
assertEq(state.activeTask?.id, 'T02', 'activeTask is T02');
|
|
575
580
|
rmSync(base, { recursive: true, force: true });
|
|
576
|
-
}
|
|
581
|
+
}
|
|
577
582
|
|
|
578
583
|
// (c) No REPLAN-TRIGGER.md, no blocker → executing (no false positive)
|
|
579
|
-
|
|
584
|
+
console.log('\n=== deriveState: no REPLAN-TRIGGER.md, no blocker → executing (#1701) ===');
|
|
585
|
+
{
|
|
580
586
|
const base = createFixtureBase();
|
|
581
587
|
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
|
|
582
588
|
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
|
|
583
589
|
writeTaskSummary(base, 'M001', 'S01', 'T01', makeTaskSummary('T01', false));
|
|
584
590
|
|
|
585
591
|
const state = await deriveState(base);
|
|
586
|
-
|
|
592
|
+
assertEq(state.phase, 'executing', 'phase is executing when no trigger and no blocker');
|
|
587
593
|
rmSync(base, { recursive: true, force: true });
|
|
588
|
-
}
|
|
594
|
+
}
|
|
589
595
|
|
|
590
596
|
// (d) blocker_discovered takes priority over REPLAN-TRIGGER.md
|
|
591
|
-
|
|
597
|
+
console.log('\n=== deriveState: blocker_discovered takes priority over REPLAN-TRIGGER.md (#1701) ===');
|
|
598
|
+
{
|
|
592
599
|
const base = createFixtureBase();
|
|
593
600
|
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
|
|
594
601
|
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
|
|
@@ -596,10 +603,10 @@ test('deriveState: blocker_discovered takes priority over REPLAN-TRIGGER.md (#17
|
|
|
596
603
|
writeReplanTrigger(base, 'M001', 'S01', '# Replan Trigger\n\n**Source:** Capture C001\n');
|
|
597
604
|
|
|
598
605
|
const state = await deriveState(base);
|
|
599
|
-
|
|
606
|
+
assertEq(state.phase, 'replanning-slice', 'phase is replanning-slice');
|
|
600
607
|
// blocker_discovered path should fire first (blockerTaskId is set, so REPLAN-TRIGGER check is skipped)
|
|
601
|
-
|
|
608
|
+
assertTrue(state.nextAction.includes('T01'), 'nextAction mentions blocker task T01 (blocker path, not trigger path)');
|
|
602
609
|
rmSync(base, { recursive: true, force: true });
|
|
603
|
-
}
|
|
610
|
+
}
|
|
604
611
|
|
|
605
|
-
|
|
612
|
+
report();
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { describe, test, before, after } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
1
|
import { mkdtempSync, rmSync, writeFileSync, existsSync, lstatSync, realpathSync, mkdirSync, symlinkSync, renameSync } from "node:fs";
|
|
4
2
|
import { join } from "node:path";
|
|
5
3
|
import { tmpdir } from "node:os";
|
|
6
4
|
import { execSync } from "node:child_process";
|
|
7
5
|
|
|
8
6
|
import { repoIdentity, externalGsdRoot, ensureGsdSymlink, validateProjectId, readRepoMeta, isInheritedRepo } from "../repo-identity.ts";
|
|
7
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
8
|
+
|
|
9
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Normalize a path for reliable comparison on Windows CI runners.
|
|
11
13
|
* `os.tmpdir()` may return the 8.3 short-path form (e.g. `C:\Users\RUNNER~1`)
|
|
@@ -21,15 +23,11 @@ function run(command: string, cwd: string): string {
|
|
|
21
23
|
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
let worktreePath: string;
|
|
28
|
-
let expectedExternalState: string;
|
|
26
|
+
async function main(): Promise<void> {
|
|
27
|
+
const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-identity-")));
|
|
28
|
+
const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-")));
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-identity-")));
|
|
32
|
-
stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-")));
|
|
30
|
+
try {
|
|
33
31
|
process.env.GSD_STATE_DIR = stateDir;
|
|
34
32
|
|
|
35
33
|
run("git init -b main", base);
|
|
@@ -40,69 +38,57 @@ describe('repo-identity-worktree', () => {
|
|
|
40
38
|
run("git add README.md", base);
|
|
41
39
|
run('git commit -m "chore: init"', base);
|
|
42
40
|
|
|
43
|
-
worktreePath = join(base, ".gsd", "worktrees", "M001");
|
|
41
|
+
const worktreePath = join(base, ".gsd", "worktrees", "M001");
|
|
44
42
|
run(`git worktree add -b milestone/M001 ${worktreePath}`, base);
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
after(() => {
|
|
50
|
-
delete process.env.GSD_PROJECT_ID;
|
|
51
|
-
delete process.env.GSD_STATE_DIR;
|
|
52
|
-
rmSync(base, { recursive: true, force: true });
|
|
53
|
-
rmSync(stateDir, { recursive: true, force: true });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('ensureGsdSymlink points worktree at main repo external state dir', () => {
|
|
44
|
+
console.log("\n=== ensureGsdSymlink points worktree at main repo external state dir ===");
|
|
45
|
+
const expectedExternalState = externalGsdRoot(base);
|
|
57
46
|
const mainState = ensureGsdSymlink(base);
|
|
58
|
-
|
|
47
|
+
assertEq(mainState, realpathSync(join(base, ".gsd")), "ensureGsdSymlink(base) returns the current main repo .gsd target");
|
|
59
48
|
const worktreeState = ensureGsdSymlink(worktreePath);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
49
|
+
assertEq(worktreeState, expectedExternalState, "worktree symlink target matches main repo external state dir");
|
|
50
|
+
assertTrue(existsSync(join(worktreePath, ".gsd")), "worktree .gsd exists");
|
|
51
|
+
assertTrue(lstatSync(join(worktreePath, ".gsd")).isSymbolicLink(), "worktree .gsd is a symlink");
|
|
52
|
+
assertEq(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "worktree .gsd symlink resolves to main repo external state dir");
|
|
65
53
|
|
|
66
|
-
|
|
54
|
+
console.log("\n=== ensureGsdSymlink heals stale worktree symlinks ===");
|
|
67
55
|
const staleState = join(stateDir, "projects", "stale-worktree-state");
|
|
68
56
|
mkdirSync(staleState, { recursive: true });
|
|
69
57
|
rmSync(join(worktreePath, ".gsd"), { recursive: true, force: true });
|
|
70
58
|
symlinkSync(staleState, join(worktreePath, ".gsd"), "junction");
|
|
71
59
|
const healedState = ensureGsdSymlink(worktreePath);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
60
|
+
assertEq(healedState, expectedExternalState, "stale worktree symlink is repaired to canonical external state dir");
|
|
61
|
+
assertEq(realpathSync(join(worktreePath, ".gsd")), realpathSync(expectedExternalState), "healed worktree symlink resolves to canonical external state dir");
|
|
75
62
|
|
|
76
|
-
|
|
63
|
+
console.log("\n=== ensureGsdSymlink preserves worktree .gsd directories ===");
|
|
77
64
|
rmSync(join(worktreePath, ".gsd"), { recursive: true, force: true });
|
|
78
65
|
mkdirSync(join(worktreePath, ".gsd", "milestones"), { recursive: true });
|
|
79
66
|
writeFileSync(join(worktreePath, ".gsd", "milestones", "stale.txt"), "stale\n", "utf-8");
|
|
80
67
|
const preservedDirState = ensureGsdSymlink(worktreePath);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
68
|
+
assertEq(preservedDirState, join(worktreePath, ".gsd"), "worktree .gsd directory is left in place for sync-based refresh");
|
|
69
|
+
assertTrue(lstatSync(join(worktreePath, ".gsd")).isDirectory(), "worktree .gsd directory remains a directory");
|
|
70
|
+
assertTrue(existsSync(join(worktreePath, ".gsd", "milestones", "stale.txt")), "existing worktree .gsd directory contents remain available for sync logic");
|
|
85
71
|
|
|
86
|
-
|
|
72
|
+
console.log("\n=== GSD_PROJECT_ID overrides computed repo hash ===");
|
|
87
73
|
process.env.GSD_PROJECT_ID = "my-project";
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
assertEq(repoIdentity(base), "my-project", "repoIdentity returns GSD_PROJECT_ID when set");
|
|
75
|
+
assertEq(externalGsdRoot(base), join(stateDir, "projects", "my-project"), "externalGsdRoot uses GSD_PROJECT_ID");
|
|
90
76
|
delete process.env.GSD_PROJECT_ID;
|
|
91
|
-
});
|
|
92
77
|
|
|
93
|
-
|
|
78
|
+
console.log("\n=== GSD_PROJECT_ID falls back to hash when unset ===");
|
|
94
79
|
const hashIdentity = repoIdentity(base);
|
|
95
|
-
|
|
96
|
-
});
|
|
80
|
+
assertTrue(/^[0-9a-f]{12}$/.test(hashIdentity), "repoIdentity returns 12-char hex hash when GSD_PROJECT_ID is unset");
|
|
97
81
|
|
|
98
|
-
|
|
82
|
+
console.log("\n=== readRepoMeta returns null for malformed metadata ===");
|
|
83
|
+
{
|
|
99
84
|
const malformedPath = join(stateDir, "projects", "malformed");
|
|
100
85
|
mkdirSync(malformedPath, { recursive: true });
|
|
101
86
|
writeFileSync(join(malformedPath, "repo-meta.json"), JSON.stringify({ version: 1 }) + "\n", "utf-8");
|
|
102
|
-
|
|
103
|
-
}
|
|
87
|
+
assertEq(readRepoMeta(malformedPath), null, "malformed repo-meta.json is treated as unknown metadata");
|
|
88
|
+
}
|
|
104
89
|
|
|
105
|
-
|
|
90
|
+
console.log("\n=== ensureGsdSymlink refreshes repo-meta gitRoot after repo move with fixed project id ===");
|
|
91
|
+
{
|
|
106
92
|
const moveRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-identity-move-")));
|
|
107
93
|
run("git init -b main", moveRepo);
|
|
108
94
|
run('git config user.name "Pi Test"', moveRepo);
|
|
@@ -114,25 +100,26 @@ test('ensureGsdSymlink refreshes repo-meta gitRoot after repo move with fixed pr
|
|
|
114
100
|
process.env.GSD_PROJECT_ID = "fixed-project";
|
|
115
101
|
const fixedExternal = ensureGsdSymlink(moveRepo);
|
|
116
102
|
const before = readRepoMeta(fixedExternal);
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
assertTrue(before !== null, "repo metadata exists before repo move");
|
|
104
|
+
assertEq(normalizePath(before!.gitRoot), normalizePath(moveRepo), "repo metadata tracks current git root before move");
|
|
119
105
|
|
|
120
106
|
const movedBaseRaw = join(tmpdir(), `gsd-repo-identity-moved-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
121
107
|
renameSync(moveRepo, movedBaseRaw);
|
|
122
108
|
const movedBase = realpathSync(movedBaseRaw);
|
|
123
109
|
const movedExternal = ensureGsdSymlink(movedBase);
|
|
124
|
-
|
|
110
|
+
assertEq(realpathSync(movedExternal), realpathSync(fixedExternal), "fixed project id keeps the same external state dir");
|
|
125
111
|
|
|
126
112
|
const after = readRepoMeta(movedExternal);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
113
|
+
assertTrue(after !== null, "repo metadata exists after repo move");
|
|
114
|
+
assertEq(normalizePath(after!.gitRoot), normalizePath(movedBase), "repo metadata gitRoot is refreshed to moved repo path");
|
|
115
|
+
assertEq(after!.createdAt, before!.createdAt, "repo metadata preserves createdAt on refresh");
|
|
130
116
|
|
|
131
117
|
rmSync(movedBase, { recursive: true, force: true });
|
|
132
118
|
delete process.env.GSD_PROJECT_ID;
|
|
133
|
-
}
|
|
119
|
+
}
|
|
134
120
|
|
|
135
|
-
|
|
121
|
+
console.log("\n=== isInheritedRepo detects subdirectory of parent repo without .gsd (#1639) ===");
|
|
122
|
+
{
|
|
136
123
|
const parentRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-inherited-parent-")));
|
|
137
124
|
run("git init -b main", parentRepo);
|
|
138
125
|
run('git config user.name "Pi Test"', parentRepo);
|
|
@@ -141,26 +128,31 @@ test('isInheritedRepo detects subdirectory of parent repo without .gsd (#1639)',
|
|
|
141
128
|
run("git add README.md", parentRepo);
|
|
142
129
|
run('git commit -m "init"', parentRepo);
|
|
143
130
|
|
|
131
|
+
// Create a subdirectory — no .gsd at parent
|
|
144
132
|
const subdir = join(parentRepo, "newproject");
|
|
145
133
|
mkdirSync(subdir, { recursive: true });
|
|
146
|
-
|
|
134
|
+
assertTrue(isInheritedRepo(subdir), "subdirectory of parent repo without .gsd is inherited");
|
|
147
135
|
|
|
136
|
+
// After adding .gsd at parent, subdirectory is a legitimate child
|
|
148
137
|
mkdirSync(join(parentRepo, ".gsd"), { recursive: true });
|
|
149
|
-
|
|
138
|
+
assertTrue(!isInheritedRepo(subdir), "subdirectory of parent repo WITH .gsd is NOT inherited");
|
|
150
139
|
|
|
151
|
-
|
|
140
|
+
// The git root itself is never inherited
|
|
141
|
+
assertTrue(!isInheritedRepo(parentRepo), "git root is not inherited");
|
|
152
142
|
|
|
143
|
+
// A standalone repo (not a subdir) is not inherited
|
|
153
144
|
const standaloneRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-inherited-standalone-")));
|
|
154
145
|
run("git init -b main", standaloneRepo);
|
|
155
146
|
run('git config user.name "Pi Test"', standaloneRepo);
|
|
156
147
|
run('git config user.email "pi@example.com"', standaloneRepo);
|
|
157
|
-
|
|
148
|
+
assertTrue(!isInheritedRepo(standaloneRepo), "standalone repo is not inherited");
|
|
158
149
|
|
|
159
150
|
rmSync(parentRepo, { recursive: true, force: true });
|
|
160
151
|
rmSync(standaloneRepo, { recursive: true, force: true });
|
|
161
|
-
}
|
|
152
|
+
}
|
|
162
153
|
|
|
163
|
-
|
|
154
|
+
console.log("\n=== subdirectory of parent repo gets unique identity after git init (#1639) ===");
|
|
155
|
+
{
|
|
164
156
|
const parentRepo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-identity-parent-")));
|
|
165
157
|
run("git init -b main", parentRepo);
|
|
166
158
|
run('git config user.name "Pi Test"', parentRepo);
|
|
@@ -173,27 +165,38 @@ test('subdirectory of parent repo gets unique identity after git init (#1639)',
|
|
|
173
165
|
const subdir = join(parentRepo, "childproject");
|
|
174
166
|
mkdirSync(subdir, { recursive: true });
|
|
175
167
|
|
|
168
|
+
// Before git init, subdirectory shares parent's identity
|
|
176
169
|
const parentIdentity = repoIdentity(parentRepo);
|
|
177
170
|
const subdirIdentityBefore = repoIdentity(subdir);
|
|
178
|
-
|
|
171
|
+
assertEq(subdirIdentityBefore, parentIdentity, "subdirectory shares parent identity before its own git init");
|
|
179
172
|
|
|
173
|
+
// After git init, subdirectory gets its own identity
|
|
180
174
|
run("git init -b main", subdir);
|
|
181
175
|
const subdirIdentityAfter = repoIdentity(subdir);
|
|
182
|
-
|
|
176
|
+
assertTrue(subdirIdentityAfter !== parentIdentity, "subdirectory gets unique identity after git init");
|
|
183
177
|
|
|
184
178
|
rmSync(parentRepo, { recursive: true, force: true });
|
|
185
|
-
}
|
|
179
|
+
}
|
|
186
180
|
|
|
187
|
-
|
|
181
|
+
console.log("\n=== validateProjectId rejects invalid values ===");
|
|
188
182
|
for (const invalid of ["has spaces", "path/traversal", "dot..dot", "back\\slash"]) {
|
|
189
|
-
|
|
183
|
+
assertTrue(!validateProjectId(invalid), `validateProjectId rejects invalid value: "${invalid}"`);
|
|
190
184
|
}
|
|
191
|
-
});
|
|
192
185
|
|
|
193
|
-
|
|
186
|
+
console.log("\n=== validateProjectId accepts valid values ===");
|
|
194
187
|
for (const valid of ["my-project", "foo_bar", "abc123", "A-Z_0-9"]) {
|
|
195
|
-
|
|
188
|
+
assertTrue(validateProjectId(valid), `validateProjectId accepts valid value: "${valid}"`);
|
|
196
189
|
}
|
|
197
|
-
}
|
|
190
|
+
} finally {
|
|
191
|
+
delete process.env.GSD_PROJECT_ID;
|
|
192
|
+
delete process.env.GSD_STATE_DIR;
|
|
193
|
+
rmSync(base, { recursive: true, force: true });
|
|
194
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
195
|
+
report();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
198
|
|
|
199
|
+
main().catch((error) => {
|
|
200
|
+
console.error(error);
|
|
201
|
+
process.exit(1);
|
|
199
202
|
});
|