gsd-pi 2.43.0-next.8 → 2.44.0-dev.0b97ffd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -12
- package/dist/cli.js +13 -1
- package/dist/help-text.js +24 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +21 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +130 -51
- package/dist/resources/extensions/gsd/auto-start.js +10 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +16 -2
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +34 -10
- package/dist/resources/extensions/gsd/markdown-renderer.js +7 -5
- package/dist/resources/extensions/gsd/reactive-graph.js +13 -2
- package/dist/resources/extensions/gsd/skill-health.js +3 -1
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -11
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -10
- package/dist/resources/extensions/gsd/visualizer-data.js +45 -13
- package/dist/resources/extensions/gsd/workspace-index.js +46 -15
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- 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 +4 -4
- 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 +1 -1
- 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 +18 -18
- 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 +1 -0
- package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.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 +4 -4
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -3
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +11 -34
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +4 -4
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +37 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js +2 -3
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +12 -2
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +38 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +192 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +255 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +40 -3
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.d.ts +25 -0
- package/packages/pi-coding-agent/dist/core/package-commands.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.js +253 -0
- package/packages/pi-coding-agent/dist/core/package-commands.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +225 -0
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +3 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +11 -199
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -37
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +2 -2
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +3 -3
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +4 -4
- package/packages/pi-coding-agent/src/core/extensions/index.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +23 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/extensions/types.ts +44 -0
- package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +15 -2
- package/packages/pi-coding-agent/src/core/fallback-resolver.ts +2 -3
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +274 -0
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +288 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +39 -3
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +240 -0
- package/packages/pi-coding-agent/src/core/package-commands.ts +310 -0
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
- package/packages/pi-coding-agent/src/core/sdk.ts +4 -0
- 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/index.ts +7 -0
- package/packages/pi-coding-agent/src/main.ts +11 -232
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +22 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +109 -42
- package/src/resources/extensions/gsd/auto-start.ts +14 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +16 -3
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +28 -10
- package/src/resources/extensions/gsd/markdown-renderer.ts +7 -5
- package/src/resources/extensions/gsd/reactive-graph.ts +12 -2
- package/src/resources/extensions/gsd/skill-health.ts +2 -1
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
- package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
- package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
- package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +152 -183
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
- package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
- package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
- package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
- package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
- package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
- package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
- package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
- package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +1 -18
- package/src/resources/extensions/gsd/tools/plan-slice.ts +1 -15
- package/src/resources/extensions/gsd/visualizer-data.ts +46 -14
- package/src/resources/extensions/gsd/workspace-index.ts +49 -18
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
- /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → alS4hoANx0TK4UVZY27da}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{drUWS0zys9uepCfCwecJv → alS4hoANx0TK4UVZY27da}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, symlinkSync } from "node:fs";
|
|
2
4
|
import { join, dirname } from "node:path";
|
|
3
5
|
import { tmpdir } from "node:os";
|
|
@@ -20,174 +22,170 @@ import {
|
|
|
20
22
|
type TaskCommitContext,
|
|
21
23
|
} from "../git-service.ts";
|
|
22
24
|
import { nativeAddAllWithExclusions } from "../native-git-bridge.ts";
|
|
23
|
-
import { createTestContext } from './test-helpers.ts';
|
|
24
|
-
|
|
25
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
26
25
|
function run(command: string, cwd: string): string {
|
|
27
26
|
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
async
|
|
29
|
+
describe('git-service', async () => {
|
|
31
30
|
// ─── inferCommitType ───────────────────────────────────────────────────
|
|
32
31
|
|
|
33
|
-
console.log("\n=== inferCommitType ===");
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
assert.deepStrictEqual(
|
|
36
34
|
inferCommitType("Implement user authentication"),
|
|
37
35
|
"feat",
|
|
38
36
|
"generic feature title → feat"
|
|
39
37
|
);
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
assert.deepStrictEqual(
|
|
42
40
|
inferCommitType("Add dashboard page"),
|
|
43
41
|
"feat",
|
|
44
42
|
"add-style title → feat"
|
|
45
43
|
);
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
assert.deepStrictEqual(
|
|
48
46
|
inferCommitType("Fix login redirect bug"),
|
|
49
47
|
"fix",
|
|
50
48
|
"title with 'fix' → fix"
|
|
51
49
|
);
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
assert.deepStrictEqual(
|
|
54
52
|
inferCommitType("Bug in session handling"),
|
|
55
53
|
"fix",
|
|
56
54
|
"title with 'bug' → fix"
|
|
57
55
|
);
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
assert.deepStrictEqual(
|
|
60
58
|
inferCommitType("Hotfix for production crash"),
|
|
61
59
|
"fix",
|
|
62
60
|
"title with 'hotfix' → fix"
|
|
63
61
|
);
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
assert.deepStrictEqual(
|
|
66
64
|
inferCommitType("Patch memory leak"),
|
|
67
65
|
"fix",
|
|
68
66
|
"title with 'patch' → fix"
|
|
69
67
|
);
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
assert.deepStrictEqual(
|
|
72
70
|
inferCommitType("Refactor state management"),
|
|
73
71
|
"refactor",
|
|
74
72
|
"title with 'refactor' → refactor"
|
|
75
73
|
);
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
assert.deepStrictEqual(
|
|
78
76
|
inferCommitType("Restructure project layout"),
|
|
79
77
|
"refactor",
|
|
80
78
|
"title with 'restructure' → refactor"
|
|
81
79
|
);
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
assert.deepStrictEqual(
|
|
84
82
|
inferCommitType("Reorganize module imports"),
|
|
85
83
|
"refactor",
|
|
86
84
|
"title with 'reorganize' → refactor"
|
|
87
85
|
);
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
assert.deepStrictEqual(
|
|
90
88
|
inferCommitType("Update API documentation"),
|
|
91
89
|
"docs",
|
|
92
90
|
"title with 'documentation' → docs"
|
|
93
91
|
);
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
assert.deepStrictEqual(
|
|
96
94
|
inferCommitType("Add doc for setup guide"),
|
|
97
95
|
"docs",
|
|
98
96
|
"title with 'doc' → docs"
|
|
99
97
|
);
|
|
100
98
|
|
|
101
|
-
|
|
99
|
+
assert.deepStrictEqual(
|
|
102
100
|
inferCommitType("Add unit tests for auth"),
|
|
103
101
|
"test",
|
|
104
102
|
"title with 'tests' → test"
|
|
105
103
|
);
|
|
106
104
|
|
|
107
|
-
|
|
105
|
+
assert.deepStrictEqual(
|
|
108
106
|
inferCommitType("Testing infrastructure setup"),
|
|
109
107
|
"test",
|
|
110
108
|
"title with 'testing' → test"
|
|
111
109
|
);
|
|
112
110
|
|
|
113
|
-
|
|
111
|
+
assert.deepStrictEqual(
|
|
114
112
|
inferCommitType("Chore: update dependencies"),
|
|
115
113
|
"chore",
|
|
116
114
|
"title with 'chore' → chore"
|
|
117
115
|
);
|
|
118
116
|
|
|
119
|
-
|
|
117
|
+
assert.deepStrictEqual(
|
|
120
118
|
inferCommitType("Cleanup unused imports"),
|
|
121
119
|
"chore",
|
|
122
120
|
"title with 'cleanup' → chore"
|
|
123
121
|
);
|
|
124
122
|
|
|
125
|
-
|
|
123
|
+
assert.deepStrictEqual(
|
|
126
124
|
inferCommitType("Clean up stale branches"),
|
|
127
125
|
"chore",
|
|
128
126
|
"title with 'clean up' → chore"
|
|
129
127
|
);
|
|
130
128
|
|
|
131
|
-
|
|
129
|
+
assert.deepStrictEqual(
|
|
132
130
|
inferCommitType("Archive old milestones"),
|
|
133
131
|
"chore",
|
|
134
132
|
"title with 'archive' → chore"
|
|
135
133
|
);
|
|
136
134
|
|
|
137
|
-
|
|
135
|
+
assert.deepStrictEqual(
|
|
138
136
|
inferCommitType("Remove deprecated endpoints"),
|
|
139
137
|
"chore",
|
|
140
138
|
"title with 'remove' → chore"
|
|
141
139
|
);
|
|
142
140
|
|
|
143
|
-
|
|
141
|
+
assert.deepStrictEqual(
|
|
144
142
|
inferCommitType("Delete temp files"),
|
|
145
143
|
"chore",
|
|
146
144
|
"title with 'delete' → chore"
|
|
147
145
|
);
|
|
148
146
|
|
|
149
147
|
// Mixed keywords — first match wins
|
|
150
|
-
|
|
148
|
+
assert.deepStrictEqual(
|
|
151
149
|
inferCommitType("Fix and refactor the login module"),
|
|
152
150
|
"fix",
|
|
153
151
|
"mixed keywords → first match wins (fix before refactor)"
|
|
154
152
|
);
|
|
155
153
|
|
|
156
|
-
|
|
154
|
+
assert.deepStrictEqual(
|
|
157
155
|
inferCommitType("Refactor test utilities"),
|
|
158
156
|
"refactor",
|
|
159
157
|
"mixed keywords → first match wins (refactor before test)"
|
|
160
158
|
);
|
|
161
159
|
|
|
162
160
|
// Unknown / unrecognized title → feat
|
|
163
|
-
|
|
161
|
+
assert.deepStrictEqual(
|
|
164
162
|
inferCommitType("Build the new pipeline"),
|
|
165
163
|
"feat",
|
|
166
164
|
"unrecognized title → feat"
|
|
167
165
|
);
|
|
168
166
|
|
|
169
|
-
|
|
167
|
+
assert.deepStrictEqual(
|
|
170
168
|
inferCommitType(""),
|
|
171
169
|
"feat",
|
|
172
170
|
"empty title → feat"
|
|
173
171
|
);
|
|
174
172
|
|
|
175
173
|
// Word boundary: "testify" should NOT match "test"
|
|
176
|
-
|
|
174
|
+
assert.deepStrictEqual(
|
|
177
175
|
inferCommitType("Testify integration"),
|
|
178
176
|
"feat",
|
|
179
177
|
"'testify' does not match 'test' — word boundary prevents partial match"
|
|
180
178
|
);
|
|
181
179
|
|
|
182
180
|
// "documentary" should NOT match "doc" (word boundary)
|
|
183
|
-
|
|
181
|
+
assert.deepStrictEqual(
|
|
184
182
|
inferCommitType("Documentary style UI"),
|
|
185
183
|
"feat",
|
|
186
184
|
"'documentary' does not match 'doc' — word boundary prevents partial match"
|
|
187
185
|
);
|
|
188
186
|
|
|
189
187
|
// "prefix" should NOT match "fix" (word boundary)
|
|
190
|
-
|
|
188
|
+
assert.deepStrictEqual(
|
|
191
189
|
inferCommitType("Add prefix to all IDs"),
|
|
192
190
|
"feat",
|
|
193
191
|
"'prefix' does not match 'fix' — word boundary prevents partial match"
|
|
@@ -195,15 +193,14 @@ async function main(): Promise<void> {
|
|
|
195
193
|
|
|
196
194
|
// ─── inferCommitType with oneLiner ──────────────────────────────────────
|
|
197
195
|
|
|
198
|
-
console.log("\n=== inferCommitType with oneLiner ===");
|
|
199
196
|
|
|
200
|
-
|
|
197
|
+
assert.deepStrictEqual(
|
|
201
198
|
inferCommitType("implement dashboard", "Fixed rendering bug in sidebar"),
|
|
202
199
|
"fix",
|
|
203
200
|
"one-liner with 'fixed' overrides generic title → fix"
|
|
204
201
|
);
|
|
205
202
|
|
|
206
|
-
|
|
203
|
+
assert.deepStrictEqual(
|
|
207
204
|
inferCommitType("add search", "Optimized query performance with caching"),
|
|
208
205
|
"perf",
|
|
209
206
|
"one-liner with 'performance' and 'caching' → perf"
|
|
@@ -211,29 +208,27 @@ async function main(): Promise<void> {
|
|
|
211
208
|
|
|
212
209
|
// ─── buildTaskCommitMessage ─────────────────────────────────────────────
|
|
213
210
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{
|
|
211
|
+
test('buildTaskCommitMessage', () => {
|
|
217
212
|
const msg = buildTaskCommitMessage({
|
|
218
213
|
taskId: "S01/T02",
|
|
219
214
|
taskTitle: "implement user authentication",
|
|
220
215
|
oneLiner: "Added JWT-based auth with refresh token rotation",
|
|
221
216
|
keyFiles: ["src/auth.ts", "src/middleware/jwt.ts"],
|
|
222
217
|
});
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
218
|
+
assert.ok(msg.startsWith("feat(S01/T02):"), "message starts with type(scope)");
|
|
219
|
+
assert.ok(msg.includes("JWT-based auth"), "message includes one-liner content");
|
|
220
|
+
assert.ok(msg.includes("- src/auth.ts"), "message body includes key files");
|
|
221
|
+
assert.ok(msg.includes("- src/middleware/jwt.ts"), "message body includes second key file");
|
|
222
|
+
});
|
|
228
223
|
|
|
229
224
|
{
|
|
230
225
|
const msg = buildTaskCommitMessage({
|
|
231
226
|
taskId: "S02/T01",
|
|
232
227
|
taskTitle: "fix login redirect bug",
|
|
233
228
|
});
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
229
|
+
assert.ok(msg.startsWith("fix(S02/T01):"), "infers fix type from title");
|
|
230
|
+
assert.ok(msg.includes("fix login redirect bug"), "uses task title when no one-liner");
|
|
231
|
+
assert.ok(!msg.includes("\n"), "no body when no key files");
|
|
237
232
|
}
|
|
238
233
|
|
|
239
234
|
{
|
|
@@ -242,14 +237,13 @@ async function main(): Promise<void> {
|
|
|
242
237
|
taskTitle: "add tests",
|
|
243
238
|
oneLiner: "Unit tests for auth module with coverage",
|
|
244
239
|
});
|
|
245
|
-
|
|
240
|
+
assert.ok(msg.startsWith("test(S01/T03):"), "infers test type");
|
|
246
241
|
}
|
|
247
242
|
|
|
248
243
|
// ─── RUNTIME_EXCLUSION_PATHS ───────────────────────────────────────────
|
|
249
244
|
|
|
250
|
-
console.log("\n=== RUNTIME_EXCLUSION_PATHS ===");
|
|
251
245
|
|
|
252
|
-
|
|
246
|
+
assert.deepStrictEqual(
|
|
253
247
|
RUNTIME_EXCLUSION_PATHS.length,
|
|
254
248
|
13,
|
|
255
249
|
"exactly 13 runtime exclusion paths"
|
|
@@ -271,24 +265,23 @@ async function main(): Promise<void> {
|
|
|
271
265
|
".gsd/DISCUSSION-MANIFEST.json",
|
|
272
266
|
];
|
|
273
267
|
|
|
274
|
-
|
|
268
|
+
assert.deepStrictEqual(
|
|
275
269
|
[...RUNTIME_EXCLUSION_PATHS],
|
|
276
270
|
expectedPaths,
|
|
277
271
|
"paths match expected set in order"
|
|
278
272
|
);
|
|
279
273
|
|
|
280
|
-
|
|
274
|
+
assert.ok(
|
|
281
275
|
RUNTIME_EXCLUSION_PATHS.includes(".gsd/activity/"),
|
|
282
276
|
"includes .gsd/activity/"
|
|
283
277
|
);
|
|
284
|
-
|
|
278
|
+
assert.ok(
|
|
285
279
|
RUNTIME_EXCLUSION_PATHS.includes(".gsd/STATE.md"),
|
|
286
280
|
"includes .gsd/STATE.md"
|
|
287
281
|
);
|
|
288
282
|
|
|
289
283
|
// ─── runGit ────────────────────────────────────────────────────────────
|
|
290
284
|
|
|
291
|
-
console.log("\n=== runGit ===");
|
|
292
285
|
|
|
293
286
|
const tempDir = mkdtempSync(join(tmpdir(), "gsd-git-service-test-"));
|
|
294
287
|
run("git init -b main", tempDir);
|
|
@@ -297,11 +290,11 @@ async function main(): Promise<void> {
|
|
|
297
290
|
|
|
298
291
|
// runGit should work on a valid repo
|
|
299
292
|
const branch = runGit(tempDir, ["branch", "--show-current"]);
|
|
300
|
-
|
|
293
|
+
assert.deepStrictEqual(branch, "main", "runGit returns current branch");
|
|
301
294
|
|
|
302
295
|
// runGit allowFailure returns empty string on failure
|
|
303
296
|
const result = runGit(tempDir, ["log", "--oneline"], { allowFailure: true });
|
|
304
|
-
|
|
297
|
+
assert.deepStrictEqual(result, "", "runGit allowFailure returns empty on error (no commits yet)");
|
|
305
298
|
|
|
306
299
|
// runGit throws on failure without allowFailure
|
|
307
300
|
let threw = false;
|
|
@@ -309,22 +302,21 @@ async function main(): Promise<void> {
|
|
|
309
302
|
runGit(tempDir, ["log", "--oneline"]);
|
|
310
303
|
} catch (e) {
|
|
311
304
|
threw = true;
|
|
312
|
-
|
|
305
|
+
assert.ok(
|
|
313
306
|
(e as Error).message.includes("git log --oneline failed"),
|
|
314
307
|
"error message includes command and path"
|
|
315
308
|
);
|
|
316
309
|
}
|
|
317
|
-
|
|
310
|
+
assert.ok(threw, "runGit throws without allowFailure on error");
|
|
318
311
|
|
|
319
312
|
// ─── Type exports compile check ────────────────────────────────────────
|
|
320
313
|
|
|
321
|
-
console.log("\n=== Type exports ===");
|
|
322
314
|
|
|
323
315
|
// These are compile-time checks — if we got here, the types import fine
|
|
324
316
|
const _prefs: GitPreferences = { auto_push: true, remote: "origin" };
|
|
325
317
|
const _opts: CommitOptions = { message: "test" };
|
|
326
|
-
|
|
327
|
-
|
|
318
|
+
assert.ok(true, "GitPreferences type exported and usable");
|
|
319
|
+
assert.ok(true, "CommitOptions type exported and usable");
|
|
328
320
|
|
|
329
321
|
// Cleanup T01 temp dir
|
|
330
322
|
rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -351,9 +343,7 @@ async function main(): Promise<void> {
|
|
|
351
343
|
|
|
352
344
|
// ─── GitServiceImpl: smart staging ─────────────────────────────────────
|
|
353
345
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
{
|
|
346
|
+
test('GitServiceImpl: smart staging', () => {
|
|
357
347
|
const repo = initTempRepo();
|
|
358
348
|
const svc = new GitServiceImpl(repo);
|
|
359
349
|
|
|
@@ -370,34 +360,32 @@ async function main(): Promise<void> {
|
|
|
370
360
|
|
|
371
361
|
const result = svc.commit({ message: "test: smart staging" });
|
|
372
362
|
|
|
373
|
-
|
|
363
|
+
assert.deepStrictEqual(result, "test: smart staging", "commit returns the commit message");
|
|
374
364
|
|
|
375
365
|
// Verify only src/code.ts is in the commit
|
|
376
366
|
const showStat = run("git show --stat --format= HEAD", repo);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
367
|
+
assert.ok(showStat.includes("src/code.ts"), "src/code.ts is in the commit");
|
|
368
|
+
assert.ok(!showStat.includes(".gsd/activity"), ".gsd/activity/ excluded from commit");
|
|
369
|
+
assert.ok(!showStat.includes(".gsd/runtime"), ".gsd/runtime/ excluded from commit");
|
|
370
|
+
assert.ok(!showStat.includes("STATE.md"), ".gsd/STATE.md excluded from commit");
|
|
371
|
+
assert.ok(!showStat.includes("auto.lock"), ".gsd/auto.lock excluded from commit");
|
|
372
|
+
assert.ok(!showStat.includes("metrics.json"), ".gsd/metrics.json excluded from commit");
|
|
373
|
+
assert.ok(!showStat.includes(".gsd/worktrees"), ".gsd/worktrees/ excluded from commit");
|
|
384
374
|
|
|
385
375
|
// Verify runtime files are still untracked
|
|
386
376
|
// git status --short may collapse to "?? .gsd/" or show individual files
|
|
387
377
|
// Use --untracked-files=all to force individual listing
|
|
388
378
|
const statusOut = run("git status --short --untracked-files=all", repo);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
379
|
+
assert.ok(statusOut.includes(".gsd/activity/"), "activity still untracked after commit");
|
|
380
|
+
assert.ok(statusOut.includes(".gsd/runtime/"), "runtime still untracked after commit");
|
|
381
|
+
assert.ok(statusOut.includes(".gsd/STATE.md"), "STATE.md still untracked after commit");
|
|
392
382
|
|
|
393
383
|
rmSync(repo, { recursive: true, force: true });
|
|
394
|
-
}
|
|
384
|
+
});
|
|
395
385
|
|
|
396
386
|
// ─── GitServiceImpl: smart staging excludes tracked runtime files ──────
|
|
397
387
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
{
|
|
388
|
+
test('GitServiceImpl: smart staging excludes tracked runtime files', () => {
|
|
401
389
|
// Reproduces the real bug: .gsd/ runtime files that are already tracked
|
|
402
390
|
// (in the git index) must be excluded from staging even when .gsd/ is
|
|
403
391
|
// in .gitignore. The old pathspec-exclude approach failed silently in
|
|
@@ -427,9 +415,9 @@ async function main(): Promise<void> {
|
|
|
427
415
|
|
|
428
416
|
// Verify runtime files are tracked (precondition)
|
|
429
417
|
const tracked = run("git ls-files .gsd/", repo);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
418
|
+
assert.ok(tracked.includes("metrics.json"), "precondition: metrics.json tracked");
|
|
419
|
+
assert.ok(tracked.includes("completed-units.json"), "precondition: completed-units.json tracked");
|
|
420
|
+
assert.ok(tracked.includes("activity/log.jsonl"), "precondition: activity log tracked");
|
|
433
421
|
|
|
434
422
|
// Now modify both runtime and real files
|
|
435
423
|
createFile(repo, ".gsd/metrics.json", '{"version":2}');
|
|
@@ -440,15 +428,15 @@ async function main(): Promise<void> {
|
|
|
440
428
|
// autoCommit should commit real.ts. The first call also runs auto-cleanup
|
|
441
429
|
// which removes runtime files from the index via a dedicated commit.
|
|
442
430
|
const msg = svc.autoCommit("execute-task", "M001/S01/T01");
|
|
443
|
-
|
|
431
|
+
assert.ok(msg !== null, "autoCommit produces a commit");
|
|
444
432
|
|
|
445
433
|
const show = run("git show --stat HEAD", repo);
|
|
446
|
-
|
|
434
|
+
assert.ok(show.includes("src/real.ts"), "real files are committed");
|
|
447
435
|
|
|
448
436
|
// After the commit, runtime files must no longer be in the git index.
|
|
449
437
|
// They remain on disk but are untracked (protected by .gitignore).
|
|
450
438
|
const trackedAfter = run("git ls-files .gsd/", repo);
|
|
451
|
-
|
|
439
|
+
assert.deepStrictEqual(trackedAfter, "", "no .gsd/ runtime files remain in the index");
|
|
452
440
|
|
|
453
441
|
// Verify a second autoCommit with changed runtime files does NOT stage them
|
|
454
442
|
createFile(repo, ".gsd/metrics.json", '{"version":3}');
|
|
@@ -456,37 +444,33 @@ async function main(): Promise<void> {
|
|
|
456
444
|
createFile(repo, "src/real.ts", "third version");
|
|
457
445
|
|
|
458
446
|
const msg2 = svc.autoCommit("execute-task", "M001/S01/T02");
|
|
459
|
-
|
|
447
|
+
assert.ok(msg2 !== null, "second autoCommit produces a commit");
|
|
460
448
|
|
|
461
449
|
const show2 = run("git show --stat HEAD", repo);
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
450
|
+
assert.ok(show2.includes("src/real.ts"), "real files committed in second commit");
|
|
451
|
+
assert.ok(!show2.includes("metrics"), "metrics.json not in second commit");
|
|
452
|
+
assert.ok(!show2.includes("completed-units"), "completed-units.json not in second commit");
|
|
453
|
+
assert.ok(!show2.includes("activity"), "activity not in second commit");
|
|
466
454
|
|
|
467
455
|
rmSync(repo, { recursive: true, force: true });
|
|
468
|
-
}
|
|
456
|
+
});
|
|
469
457
|
|
|
470
458
|
// ─── GitServiceImpl: autoCommit on clean repo ──────────────────────────
|
|
471
459
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
{
|
|
460
|
+
test('GitServiceImpl: autoCommit', () => {
|
|
475
461
|
const repo = initTempRepo();
|
|
476
462
|
const svc = new GitServiceImpl(repo);
|
|
477
463
|
|
|
478
464
|
// Clean repo — autoCommit should return null
|
|
479
465
|
const cleanResult = svc.autoCommit("task", "T01");
|
|
480
|
-
|
|
466
|
+
assert.deepStrictEqual(cleanResult, null, "autoCommit on clean repo returns null");
|
|
481
467
|
|
|
482
468
|
rmSync(repo, { recursive: true, force: true });
|
|
483
|
-
}
|
|
469
|
+
});
|
|
484
470
|
|
|
485
471
|
// ─── GitServiceImpl: autoCommit on dirty repo ──────────────────────────
|
|
486
472
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
{
|
|
473
|
+
test('GitServiceImpl: autoCommit on dirty repo', () => {
|
|
490
474
|
const repo = initTempRepo();
|
|
491
475
|
const svc = new GitServiceImpl(repo);
|
|
492
476
|
|
|
@@ -494,10 +478,10 @@ async function main(): Promise<void> {
|
|
|
494
478
|
|
|
495
479
|
// Without task context, autoCommit uses generic chore message
|
|
496
480
|
const msg = svc.autoCommit("task", "T01");
|
|
497
|
-
|
|
481
|
+
assert.deepStrictEqual(msg, "chore(T01): auto-commit after task", "autoCommit returns generic format without task context");
|
|
498
482
|
|
|
499
483
|
const log = run("git log --oneline -1", repo);
|
|
500
|
-
|
|
484
|
+
assert.ok(log.includes("chore(T01): auto-commit after task"), "generic commit message is in git log");
|
|
501
485
|
|
|
502
486
|
// With task context, autoCommit uses meaningful message
|
|
503
487
|
createFile(repo, "src/auth.ts", "export function login() {}");
|
|
@@ -507,18 +491,16 @@ async function main(): Promise<void> {
|
|
|
507
491
|
oneLiner: "Added JWT-based auth with refresh token rotation",
|
|
508
492
|
keyFiles: ["src/auth.ts"],
|
|
509
493
|
});
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
494
|
+
assert.ok(msg2 !== null, "autoCommit with task context returns a message");
|
|
495
|
+
assert.ok(msg2!.startsWith("feat(S01/T02):"), "meaningful commit uses feat type and scope");
|
|
496
|
+
assert.ok(msg2!.includes("JWT-based auth"), "meaningful commit includes one-liner content");
|
|
513
497
|
|
|
514
498
|
rmSync(repo, { recursive: true, force: true });
|
|
515
|
-
}
|
|
499
|
+
});
|
|
516
500
|
|
|
517
501
|
// ─── GitServiceImpl: empty-after-staging guard ─────────────────────────
|
|
518
502
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
{
|
|
503
|
+
test('GitServiceImpl: empty-after-staging guard', () => {
|
|
522
504
|
const repo = initTempRepo();
|
|
523
505
|
const svc = new GitServiceImpl(repo);
|
|
524
506
|
|
|
@@ -526,20 +508,18 @@ async function main(): Promise<void> {
|
|
|
526
508
|
createFile(repo, ".gsd/activity/x.jsonl", "data");
|
|
527
509
|
|
|
528
510
|
const result = svc.autoCommit("task", "T02");
|
|
529
|
-
|
|
511
|
+
assert.deepStrictEqual(result, null, "autoCommit returns null when only runtime files are dirty");
|
|
530
512
|
|
|
531
513
|
// Verify no new commit was created (should still be at init commit)
|
|
532
514
|
const logCount = run("git rev-list --count HEAD", repo);
|
|
533
|
-
|
|
515
|
+
assert.deepStrictEqual(logCount, "1", "no new commit created when only runtime files changed");
|
|
534
516
|
|
|
535
517
|
rmSync(repo, { recursive: true, force: true });
|
|
536
|
-
}
|
|
518
|
+
});
|
|
537
519
|
|
|
538
520
|
// ─── GitServiceImpl: autoCommit with extraExclusions ───────────────────
|
|
539
521
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
{
|
|
522
|
+
test('GitServiceImpl: autoCommit with extraExclusions', () => {
|
|
543
523
|
const repo = initTempRepo();
|
|
544
524
|
const svc = new GitServiceImpl(repo);
|
|
545
525
|
|
|
@@ -549,21 +529,19 @@ async function main(): Promise<void> {
|
|
|
549
529
|
|
|
550
530
|
// Auto-commit with .gsd/ excluded (simulates pre-switch)
|
|
551
531
|
const msg = svc.autoCommit("pre-switch", "main", [".gsd/"]);
|
|
552
|
-
|
|
532
|
+
assert.deepStrictEqual(msg, "chore(main): auto-commit after pre-switch", "pre-switch autoCommit with .gsd/ exclusion commits");
|
|
553
533
|
|
|
554
534
|
// Verify .gsd/ file was NOT committed
|
|
555
535
|
const show = run("git show --stat HEAD", repo);
|
|
556
|
-
|
|
557
|
-
|
|
536
|
+
assert.ok(!show.includes("ROADMAP"), ".gsd/ files excluded from pre-switch auto-commit");
|
|
537
|
+
assert.ok(show.includes("feature.ts"), "non-.gsd/ files included in pre-switch auto-commit");
|
|
558
538
|
|
|
559
539
|
rmSync(repo, { recursive: true, force: true });
|
|
560
|
-
}
|
|
540
|
+
});
|
|
561
541
|
|
|
562
542
|
// ─── GitServiceImpl: autoCommit extraExclusions — only .gsd/ dirty ────
|
|
563
543
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
{
|
|
544
|
+
test('GitServiceImpl: autoCommit extraExclusions — only .gsd/ dirty', () => {
|
|
567
545
|
const repo = initTempRepo();
|
|
568
546
|
const svc = new GitServiceImpl(repo);
|
|
569
547
|
|
|
@@ -573,25 +551,23 @@ async function main(): Promise<void> {
|
|
|
573
551
|
|
|
574
552
|
// Auto-commit with .gsd/ excluded — nothing else to commit
|
|
575
553
|
const result = svc.autoCommit("pre-switch", "main", [".gsd/"]);
|
|
576
|
-
|
|
554
|
+
assert.deepStrictEqual(result, null, "autoCommit returns null when only .gsd/ files are dirty and excluded");
|
|
577
555
|
|
|
578
556
|
rmSync(repo, { recursive: true, force: true });
|
|
579
|
-
}
|
|
557
|
+
});
|
|
580
558
|
|
|
581
559
|
// ─── GitServiceImpl: commit returns null when nothing staged ───────────
|
|
582
560
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
{
|
|
561
|
+
test('GitServiceImpl: commit empty', () => {
|
|
586
562
|
const repo = initTempRepo();
|
|
587
563
|
const svc = new GitServiceImpl(repo);
|
|
588
564
|
|
|
589
565
|
// Nothing dirty, commit should return null
|
|
590
566
|
const result = svc.commit({ message: "should not commit" });
|
|
591
|
-
|
|
567
|
+
assert.deepStrictEqual(result, null, "commit returns null when nothing to stage");
|
|
592
568
|
|
|
593
569
|
rmSync(repo, { recursive: true, force: true });
|
|
594
|
-
}
|
|
570
|
+
});
|
|
595
571
|
|
|
596
572
|
// ─── Helper: create repo for branch tests ────────────────────────────
|
|
597
573
|
|
|
@@ -608,36 +584,32 @@ async function main(): Promise<void> {
|
|
|
608
584
|
|
|
609
585
|
// ─── getCurrentBranch ────────────────────────────────────────────────
|
|
610
586
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
{
|
|
587
|
+
test('Branch queries', () => {
|
|
614
588
|
const repo = initBranchTestRepo();
|
|
615
589
|
const svc = new GitServiceImpl(repo);
|
|
616
590
|
|
|
617
|
-
|
|
591
|
+
assert.deepStrictEqual(svc.getCurrentBranch(), "main", "getCurrentBranch returns main on main branch");
|
|
618
592
|
|
|
619
593
|
run("git checkout -b gsd/M001/S01", repo);
|
|
620
|
-
|
|
594
|
+
assert.deepStrictEqual(svc.getCurrentBranch(), "gsd/M001/S01", "getCurrentBranch returns slice branch name");
|
|
621
595
|
|
|
622
596
|
run("git checkout -b feature/foo", repo);
|
|
623
|
-
|
|
597
|
+
assert.deepStrictEqual(svc.getCurrentBranch(), "feature/foo", "getCurrentBranch returns feature branch name");
|
|
624
598
|
|
|
625
599
|
rmSync(repo, { recursive: true, force: true });
|
|
626
|
-
}
|
|
600
|
+
});
|
|
627
601
|
|
|
628
602
|
// ─── getMainBranch ────────────────────────────────────────────────────
|
|
629
603
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
{
|
|
604
|
+
test('getMainBranch', () => {
|
|
633
605
|
const repo = initBranchTestRepo();
|
|
634
606
|
const svc = new GitServiceImpl(repo);
|
|
635
607
|
|
|
636
608
|
// Basic case: repo has "main" branch
|
|
637
|
-
|
|
609
|
+
assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch returns main when main exists");
|
|
638
610
|
|
|
639
611
|
rmSync(repo, { recursive: true, force: true });
|
|
640
|
-
}
|
|
612
|
+
});
|
|
641
613
|
|
|
642
614
|
{
|
|
643
615
|
// master-only repo
|
|
@@ -650,7 +622,7 @@ async function main(): Promise<void> {
|
|
|
650
622
|
run('git commit -m "init"', repo);
|
|
651
623
|
|
|
652
624
|
const svc = new GitServiceImpl(repo);
|
|
653
|
-
|
|
625
|
+
assert.deepStrictEqual(svc.getMainBranch(), "master", "getMainBranch returns master when only master exists");
|
|
654
626
|
|
|
655
627
|
rmSync(repo, { recursive: true, force: true });
|
|
656
628
|
}
|
|
@@ -661,9 +633,7 @@ async function main(): Promise<void> {
|
|
|
661
633
|
|
|
662
634
|
// ─── createSnapshot: prefs enabled ─────────────────────────────────────
|
|
663
635
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
{
|
|
636
|
+
test('createSnapshot: enabled', () => {
|
|
667
637
|
const repo = initBranchTestRepo();
|
|
668
638
|
const svc = new GitServiceImpl(repo, { snapshots: true });
|
|
669
639
|
|
|
@@ -677,16 +647,14 @@ async function main(): Promise<void> {
|
|
|
677
647
|
|
|
678
648
|
// Verify ref exists under refs/gsd/snapshots/
|
|
679
649
|
const refs = run("git for-each-ref refs/gsd/snapshots/", repo);
|
|
680
|
-
|
|
650
|
+
assert.ok(refs.includes("refs/gsd/snapshots/gsd/M001/S01/"), "snapshot ref created under refs/gsd/snapshots/");
|
|
681
651
|
|
|
682
652
|
rmSync(repo, { recursive: true, force: true });
|
|
683
|
-
}
|
|
653
|
+
});
|
|
684
654
|
|
|
685
655
|
// ─── createSnapshot: prefs disabled ────────────────────────────────────
|
|
686
656
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
{
|
|
657
|
+
test('createSnapshot: disabled', () => {
|
|
690
658
|
const repo = initBranchTestRepo();
|
|
691
659
|
const svc = new GitServiceImpl(repo, { snapshots: false });
|
|
692
660
|
|
|
@@ -698,16 +666,14 @@ async function main(): Promise<void> {
|
|
|
698
666
|
svc.createSnapshot("gsd/M001/S01");
|
|
699
667
|
|
|
700
668
|
const refs = run("git for-each-ref refs/gsd/snapshots/", repo);
|
|
701
|
-
|
|
669
|
+
assert.deepStrictEqual(refs, "", "no snapshot ref created when prefs.snapshots is false");
|
|
702
670
|
|
|
703
671
|
rmSync(repo, { recursive: true, force: true });
|
|
704
|
-
}
|
|
672
|
+
});
|
|
705
673
|
|
|
706
674
|
// ─── runPreMergeCheck: pass ────────────────────────────────────────────
|
|
707
675
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
{
|
|
676
|
+
test('runPreMergeCheck: pass', () => {
|
|
711
677
|
const repo = initBranchTestRepo();
|
|
712
678
|
// Create package.json with passing test script
|
|
713
679
|
createFile(repo, "package.json", JSON.stringify({
|
|
@@ -720,17 +686,15 @@ async function main(): Promise<void> {
|
|
|
720
686
|
const svc = new GitServiceImpl(repo, { pre_merge_check: true });
|
|
721
687
|
const result: PreMergeCheckResult = svc.runPreMergeCheck();
|
|
722
688
|
|
|
723
|
-
|
|
724
|
-
|
|
689
|
+
assert.deepStrictEqual(result.passed, true, "runPreMergeCheck returns passed:true when tests pass");
|
|
690
|
+
assert.ok(!result.skipped, "runPreMergeCheck is not skipped when enabled");
|
|
725
691
|
|
|
726
692
|
rmSync(repo, { recursive: true, force: true });
|
|
727
|
-
}
|
|
693
|
+
});
|
|
728
694
|
|
|
729
695
|
// ─── runPreMergeCheck: fail ────────────────────────────────────────────
|
|
730
696
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
{
|
|
697
|
+
test('runPreMergeCheck: fail', () => {
|
|
734
698
|
const repo = initBranchTestRepo();
|
|
735
699
|
// Create package.json with failing test script
|
|
736
700
|
createFile(repo, "package.json", JSON.stringify({
|
|
@@ -743,17 +707,15 @@ async function main(): Promise<void> {
|
|
|
743
707
|
const svc = new GitServiceImpl(repo, { pre_merge_check: true });
|
|
744
708
|
const result: PreMergeCheckResult = svc.runPreMergeCheck();
|
|
745
709
|
|
|
746
|
-
|
|
747
|
-
|
|
710
|
+
assert.deepStrictEqual(result.passed, false, "runPreMergeCheck returns passed:false when tests fail");
|
|
711
|
+
assert.ok(!result.skipped, "runPreMergeCheck is not skipped when enabled");
|
|
748
712
|
|
|
749
713
|
rmSync(repo, { recursive: true, force: true });
|
|
750
|
-
}
|
|
714
|
+
});
|
|
751
715
|
|
|
752
716
|
// ─── runPreMergeCheck: disabled ────────────────────────────────────────
|
|
753
717
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
{
|
|
718
|
+
test('runPreMergeCheck: disabled', () => {
|
|
757
719
|
const repo = initBranchTestRepo();
|
|
758
720
|
createFile(repo, "package.json", JSON.stringify({
|
|
759
721
|
name: "test-disabled",
|
|
@@ -765,98 +727,86 @@ async function main(): Promise<void> {
|
|
|
765
727
|
const svc = new GitServiceImpl(repo, { pre_merge_check: false });
|
|
766
728
|
const result: PreMergeCheckResult = svc.runPreMergeCheck();
|
|
767
729
|
|
|
768
|
-
|
|
769
|
-
|
|
730
|
+
assert.deepStrictEqual(result.skipped, true, "runPreMergeCheck skipped when pre_merge_check is false");
|
|
731
|
+
assert.deepStrictEqual(result.passed, true, "runPreMergeCheck returns passed:true when skipped (no block)");
|
|
770
732
|
|
|
771
733
|
rmSync(repo, { recursive: true, force: true });
|
|
772
|
-
}
|
|
734
|
+
});
|
|
773
735
|
|
|
774
736
|
// ─── runPreMergeCheck: custom command ──────────────────────────────────
|
|
775
737
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
{
|
|
738
|
+
test('runPreMergeCheck: custom command', () => {
|
|
779
739
|
const repo = initBranchTestRepo();
|
|
780
740
|
// Custom command string overrides auto-detection
|
|
781
741
|
const svc = new GitServiceImpl(repo, { pre_merge_check: 'node -e "process.exit(0)"' });
|
|
782
742
|
const result: PreMergeCheckResult = svc.runPreMergeCheck();
|
|
783
743
|
|
|
784
|
-
|
|
785
|
-
|
|
744
|
+
assert.deepStrictEqual(result.passed, true, "runPreMergeCheck passes with custom command that exits 0");
|
|
745
|
+
assert.ok(!result.skipped, "custom command is not skipped");
|
|
786
746
|
|
|
787
747
|
rmSync(repo, { recursive: true, force: true });
|
|
788
|
-
}
|
|
748
|
+
});
|
|
789
749
|
|
|
790
750
|
// ─── VALID_BRANCH_NAME regex ──────────────────────────────────────────
|
|
791
751
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
{
|
|
752
|
+
test('VALID_BRANCH_NAME regex', () => {
|
|
795
753
|
// Valid branch names
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
754
|
+
assert.ok(VALID_BRANCH_NAME.test("main"), "VALID_BRANCH_NAME accepts 'main'");
|
|
755
|
+
assert.ok(VALID_BRANCH_NAME.test("master"), "VALID_BRANCH_NAME accepts 'master'");
|
|
756
|
+
assert.ok(VALID_BRANCH_NAME.test("develop"), "VALID_BRANCH_NAME accepts 'develop'");
|
|
757
|
+
assert.ok(VALID_BRANCH_NAME.test("feature/foo"), "VALID_BRANCH_NAME accepts 'feature/foo'");
|
|
758
|
+
assert.ok(VALID_BRANCH_NAME.test("release-1.0"), "VALID_BRANCH_NAME accepts 'release-1.0'");
|
|
759
|
+
assert.ok(VALID_BRANCH_NAME.test("my_branch"), "VALID_BRANCH_NAME accepts 'my_branch'");
|
|
760
|
+
assert.ok(VALID_BRANCH_NAME.test("v2.0.1"), "VALID_BRANCH_NAME accepts 'v2.0.1'");
|
|
803
761
|
|
|
804
762
|
// Invalid / injection attempts
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
763
|
+
assert.ok(!VALID_BRANCH_NAME.test("main; rm -rf /"), "VALID_BRANCH_NAME rejects shell injection");
|
|
764
|
+
assert.ok(!VALID_BRANCH_NAME.test("main && echo pwned"), "VALID_BRANCH_NAME rejects && injection");
|
|
765
|
+
assert.ok(!VALID_BRANCH_NAME.test(""), "VALID_BRANCH_NAME rejects empty string");
|
|
766
|
+
assert.ok(!VALID_BRANCH_NAME.test("branch name"), "VALID_BRANCH_NAME rejects spaces");
|
|
767
|
+
assert.ok(!VALID_BRANCH_NAME.test("branch`cmd`"), "VALID_BRANCH_NAME rejects backticks");
|
|
768
|
+
assert.ok(!VALID_BRANCH_NAME.test("branch$(cmd)"), "VALID_BRANCH_NAME rejects $() subshell");
|
|
769
|
+
});
|
|
812
770
|
|
|
813
771
|
// ─── getMainBranch: configured main_branch preference ──────────────────
|
|
814
772
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
{
|
|
773
|
+
test('getMainBranch: configured main_branch', () => {
|
|
818
774
|
const repo = initBranchTestRepo();
|
|
819
775
|
const svc = new GitServiceImpl(repo, { main_branch: "trunk" });
|
|
820
776
|
|
|
821
|
-
|
|
777
|
+
assert.deepStrictEqual(svc.getMainBranch(), "trunk", "getMainBranch returns configured main_branch preference");
|
|
822
778
|
|
|
823
779
|
rmSync(repo, { recursive: true, force: true });
|
|
824
|
-
}
|
|
780
|
+
});
|
|
825
781
|
|
|
826
782
|
// ─── getMainBranch: falls back to auto-detection when not set ──────────
|
|
827
783
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
{
|
|
784
|
+
test('getMainBranch: fallback to auto-detection', () => {
|
|
831
785
|
const repo = initBranchTestRepo();
|
|
832
786
|
const svc = new GitServiceImpl(repo, {});
|
|
833
787
|
|
|
834
|
-
|
|
788
|
+
assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch falls back to auto-detection when main_branch not set");
|
|
835
789
|
|
|
836
790
|
rmSync(repo, { recursive: true, force: true });
|
|
837
|
-
}
|
|
791
|
+
});
|
|
838
792
|
|
|
839
793
|
// ─── getMainBranch: ignores invalid branch names ───────────────────────
|
|
840
794
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
{
|
|
795
|
+
test('getMainBranch: ignores invalid branch name', () => {
|
|
844
796
|
const repo = initBranchTestRepo();
|
|
845
797
|
const svc = new GitServiceImpl(repo, { main_branch: "main; rm -rf /" });
|
|
846
798
|
|
|
847
|
-
|
|
799
|
+
assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch ignores invalid branch name and falls back to auto-detection");
|
|
848
800
|
|
|
849
801
|
rmSync(repo, { recursive: true, force: true });
|
|
850
|
-
}
|
|
802
|
+
});
|
|
851
803
|
|
|
852
804
|
// ─── PreMergeCheckResult type export compile check ─────────────────────
|
|
853
805
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
{
|
|
806
|
+
test('PreMergeCheckResult type export', () => {
|
|
857
807
|
const _checkResult: PreMergeCheckResult = { passed: true, skipped: false };
|
|
858
|
-
|
|
859
|
-
}
|
|
808
|
+
assert.ok(true, "PreMergeCheckResult type exported and usable");
|
|
809
|
+
});
|
|
860
810
|
|
|
861
811
|
// ═══════════════════════════════════════════════════════════════════════
|
|
862
812
|
// Integration branch — feature-branch workflow support
|
|
@@ -864,82 +814,70 @@ async function main(): Promise<void> {
|
|
|
864
814
|
|
|
865
815
|
// ─── writeIntegrationBranch / readIntegrationBranch: round-trip ────────
|
|
866
816
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
{
|
|
817
|
+
test('Integration branch: write and read', () => {
|
|
870
818
|
const repo = initBranchTestRepo();
|
|
871
819
|
|
|
872
820
|
// Initially no integration branch
|
|
873
|
-
|
|
821
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "readIntegrationBranch returns null when no metadata");
|
|
874
822
|
|
|
875
823
|
// Write integration branch
|
|
876
824
|
writeIntegrationBranch(repo, "M001", "f-123-new-thing");
|
|
877
|
-
|
|
825
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-new-thing", "readIntegrationBranch returns written branch");
|
|
878
826
|
|
|
879
827
|
rmSync(repo, { recursive: true, force: true });
|
|
880
|
-
}
|
|
828
|
+
});
|
|
881
829
|
|
|
882
830
|
// ─── writeIntegrationBranch: updates when branch changes (#300) ──────
|
|
883
831
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
{
|
|
832
|
+
test('Integration branch: updates on branch change', () => {
|
|
887
833
|
const repo = initBranchTestRepo();
|
|
888
834
|
|
|
889
835
|
writeIntegrationBranch(repo, "M001", "f-123-first");
|
|
890
836
|
writeIntegrationBranch(repo, "M001", "f-456-second"); // updates to new branch (#300)
|
|
891
837
|
|
|
892
|
-
|
|
838
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-456-second", "second write updates integration branch to new value");
|
|
893
839
|
|
|
894
840
|
rmSync(repo, { recursive: true, force: true });
|
|
895
|
-
}
|
|
841
|
+
});
|
|
896
842
|
|
|
897
843
|
// ─── writeIntegrationBranch: same branch is idempotent ─────────────────
|
|
898
844
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
{
|
|
845
|
+
test('Integration branch: same branch is idempotent', () => {
|
|
902
846
|
const repo = initBranchTestRepo();
|
|
903
847
|
|
|
904
848
|
writeIntegrationBranch(repo, "M001", "f-123-first");
|
|
905
849
|
writeIntegrationBranch(repo, "M001", "f-123-first"); // same branch — no-op
|
|
906
850
|
|
|
907
|
-
|
|
851
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-first", "same branch write is idempotent");
|
|
908
852
|
|
|
909
853
|
rmSync(repo, { recursive: true, force: true });
|
|
910
|
-
}
|
|
854
|
+
});
|
|
911
855
|
|
|
912
856
|
// ─── writeIntegrationBranch: rejects slice branches ───────────────────
|
|
913
857
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
{
|
|
858
|
+
test('Integration branch: rejects slice branches', () => {
|
|
917
859
|
const repo = initBranchTestRepo();
|
|
918
860
|
|
|
919
861
|
writeIntegrationBranch(repo, "M001", "gsd/M001/S01");
|
|
920
|
-
|
|
862
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "slice branches are not recorded as integration branch");
|
|
921
863
|
|
|
922
864
|
rmSync(repo, { recursive: true, force: true });
|
|
923
|
-
}
|
|
865
|
+
});
|
|
924
866
|
|
|
925
867
|
// ─── writeIntegrationBranch: rejects invalid branch names ─────────────
|
|
926
868
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
{
|
|
869
|
+
test('Integration branch: rejects invalid names', () => {
|
|
930
870
|
const repo = initBranchTestRepo();
|
|
931
871
|
|
|
932
872
|
writeIntegrationBranch(repo, "M001", "bad; rm -rf /");
|
|
933
|
-
|
|
873
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "invalid branch name is not recorded");
|
|
934
874
|
|
|
935
875
|
rmSync(repo, { recursive: true, force: true });
|
|
936
|
-
}
|
|
876
|
+
});
|
|
937
877
|
|
|
938
878
|
// ─── getMainBranch: uses integration branch when milestone set ────────
|
|
939
879
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
{
|
|
880
|
+
test('getMainBranch: integration branch from milestone metadata', () => {
|
|
943
881
|
const repo = initBranchTestRepo();
|
|
944
882
|
|
|
945
883
|
// Create a feature branch
|
|
@@ -951,20 +889,18 @@ async function main(): Promise<void> {
|
|
|
951
889
|
|
|
952
890
|
// Without milestone set, getMainBranch returns "main"
|
|
953
891
|
const svc = new GitServiceImpl(repo);
|
|
954
|
-
|
|
892
|
+
assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch returns main when no milestone set");
|
|
955
893
|
|
|
956
894
|
// With milestone set, getMainBranch returns the integration branch
|
|
957
895
|
svc.setMilestoneId("M001");
|
|
958
|
-
|
|
896
|
+
assert.deepStrictEqual(svc.getMainBranch(), "f-123-feature", "getMainBranch returns integration branch when milestone set");
|
|
959
897
|
|
|
960
898
|
rmSync(repo, { recursive: true, force: true });
|
|
961
|
-
}
|
|
899
|
+
});
|
|
962
900
|
|
|
963
901
|
// ─── getMainBranch: main_branch pref still takes priority ─────────────
|
|
964
902
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
{
|
|
903
|
+
test('getMainBranch: main_branch pref overrides integration branch', () => {
|
|
968
904
|
const repo = initBranchTestRepo();
|
|
969
905
|
|
|
970
906
|
run("git checkout -b f-123-feature", repo);
|
|
@@ -976,16 +912,14 @@ async function main(): Promise<void> {
|
|
|
976
912
|
// Explicit preference still wins
|
|
977
913
|
const svc = new GitServiceImpl(repo, { main_branch: "trunk" });
|
|
978
914
|
svc.setMilestoneId("M001");
|
|
979
|
-
|
|
915
|
+
assert.deepStrictEqual(svc.getMainBranch(), "trunk", "main_branch preference overrides integration branch");
|
|
980
916
|
|
|
981
917
|
rmSync(repo, { recursive: true, force: true });
|
|
982
|
-
}
|
|
918
|
+
});
|
|
983
919
|
|
|
984
920
|
// ─── getMainBranch: falls back when integration branch deleted ────────
|
|
985
921
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
{
|
|
922
|
+
test('getMainBranch: fallback when integration branch deleted', () => {
|
|
989
923
|
const repo = initBranchTestRepo();
|
|
990
924
|
|
|
991
925
|
// Write metadata pointing to a branch that doesn't exist
|
|
@@ -993,75 +927,67 @@ async function main(): Promise<void> {
|
|
|
993
927
|
|
|
994
928
|
const svc = new GitServiceImpl(repo);
|
|
995
929
|
svc.setMilestoneId("M001");
|
|
996
|
-
|
|
930
|
+
assert.deepStrictEqual(svc.getMainBranch(), "main", "getMainBranch falls back to main when integration branch no longer exists");
|
|
997
931
|
|
|
998
932
|
rmSync(repo, { recursive: true, force: true });
|
|
999
|
-
}
|
|
933
|
+
});
|
|
1000
934
|
|
|
1001
935
|
// ─── resolveMilestoneIntegrationBranch: recorded branch wins when it exists ───
|
|
1002
936
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
{
|
|
937
|
+
test('Integration branch: resolver prefers recorded branch', () => {
|
|
1006
938
|
const repo = initBranchTestRepo();
|
|
1007
939
|
run("git checkout -b feature/live", repo);
|
|
1008
940
|
run("git checkout main", repo);
|
|
1009
941
|
writeIntegrationBranch(repo, "M001", "feature/live");
|
|
1010
942
|
|
|
1011
943
|
const resolved = resolveMilestoneIntegrationBranch(repo, "M001");
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
944
|
+
assert.deepStrictEqual(resolved.status, "recorded", "resolver reports recorded branch when metadata branch exists");
|
|
945
|
+
assert.deepStrictEqual(resolved.recordedBranch, "feature/live", "resolver includes recorded branch");
|
|
946
|
+
assert.deepStrictEqual(resolved.effectiveBranch, "feature/live", "resolver uses recorded branch as effective branch");
|
|
1015
947
|
|
|
1016
948
|
rmSync(repo, { recursive: true, force: true });
|
|
1017
|
-
}
|
|
949
|
+
});
|
|
1018
950
|
|
|
1019
951
|
// ─── resolveMilestoneIntegrationBranch: falls back to detected default ────────
|
|
1020
952
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
{
|
|
953
|
+
test('Integration branch: resolver falls back to detected default', () => {
|
|
1024
954
|
const repo = initBranchTestRepo();
|
|
1025
955
|
writeIntegrationBranch(repo, "M001", "deleted-branch");
|
|
1026
956
|
|
|
1027
957
|
const resolved = resolveMilestoneIntegrationBranch(repo, "M001");
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
958
|
+
assert.deepStrictEqual(resolved.status, "fallback", "resolver reports fallback when recorded branch is stale");
|
|
959
|
+
assert.deepStrictEqual(resolved.recordedBranch, "deleted-branch", "resolver preserves stale recorded branch for diagnostics");
|
|
960
|
+
assert.deepStrictEqual(resolved.effectiveBranch, "main", "resolver falls back to detected default branch");
|
|
961
|
+
assert.ok(
|
|
1032
962
|
resolved.reason.includes("deleted-branch") && resolved.reason.includes("main"),
|
|
1033
963
|
"resolver reason mentions stale recorded branch and fallback branch",
|
|
1034
964
|
);
|
|
1035
965
|
|
|
1036
966
|
rmSync(repo, { recursive: true, force: true });
|
|
1037
|
-
}
|
|
967
|
+
});
|
|
1038
968
|
|
|
1039
969
|
// ─── resolveMilestoneIntegrationBranch: configured main_branch is fallback ─────
|
|
1040
970
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
{
|
|
971
|
+
test('Integration branch: resolver uses configured fallback branch', () => {
|
|
1044
972
|
const repo = initBranchTestRepo();
|
|
1045
973
|
run("git checkout -b trunk", repo);
|
|
1046
974
|
run("git checkout main", repo);
|
|
1047
975
|
writeIntegrationBranch(repo, "M001", "deleted-branch");
|
|
1048
976
|
|
|
1049
977
|
const resolved = resolveMilestoneIntegrationBranch(repo, "M001", { main_branch: "trunk" });
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
978
|
+
assert.deepStrictEqual(resolved.status, "fallback", "resolver reports fallback when using configured main_branch");
|
|
979
|
+
assert.deepStrictEqual(resolved.effectiveBranch, "trunk", "resolver prefers configured main_branch as fallback");
|
|
980
|
+
assert.ok(
|
|
1053
981
|
resolved.reason.includes("deleted-branch") && resolved.reason.includes("trunk"),
|
|
1054
982
|
"configured fallback reason mentions stale branch and configured branch",
|
|
1055
983
|
);
|
|
1056
984
|
|
|
1057
985
|
rmSync(repo, { recursive: true, force: true });
|
|
1058
|
-
}
|
|
986
|
+
});
|
|
1059
987
|
|
|
1060
988
|
// ─── Per-milestone isolation: different milestones, different targets ──
|
|
1061
989
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
{
|
|
990
|
+
test('Integration branch: per-milestone isolation', () => {
|
|
1065
991
|
const repo = initBranchTestRepo();
|
|
1066
992
|
|
|
1067
993
|
run("git checkout -b feature-a", repo);
|
|
@@ -1074,37 +1000,33 @@ async function main(): Promise<void> {
|
|
|
1074
1000
|
const svc = new GitServiceImpl(repo);
|
|
1075
1001
|
|
|
1076
1002
|
svc.setMilestoneId("M001");
|
|
1077
|
-
|
|
1003
|
+
assert.deepStrictEqual(svc.getMainBranch(), "feature-a", "M001 integration branch is feature-a");
|
|
1078
1004
|
|
|
1079
1005
|
svc.setMilestoneId("M002");
|
|
1080
|
-
|
|
1006
|
+
assert.deepStrictEqual(svc.getMainBranch(), "feature-b", "M002 integration branch is feature-b");
|
|
1081
1007
|
|
|
1082
1008
|
svc.setMilestoneId(null);
|
|
1083
|
-
|
|
1009
|
+
assert.deepStrictEqual(svc.getMainBranch(), "main", "no milestone set → falls back to main");
|
|
1084
1010
|
|
|
1085
1011
|
rmSync(repo, { recursive: true, force: true });
|
|
1086
|
-
}
|
|
1012
|
+
});
|
|
1087
1013
|
|
|
1088
1014
|
// ─── Backward compatibility: no metadata → existing behavior ──────────
|
|
1089
1015
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
{
|
|
1016
|
+
test('Integration branch: backward compat', () => {
|
|
1093
1017
|
const repo = initBranchTestRepo();
|
|
1094
1018
|
const svc = new GitServiceImpl(repo);
|
|
1095
1019
|
|
|
1096
1020
|
// Set milestone but no metadata file exists
|
|
1097
1021
|
svc.setMilestoneId("M001");
|
|
1098
|
-
|
|
1022
|
+
assert.deepStrictEqual(svc.getMainBranch(), "main", "backward compat: no metadata file → falls back to main");
|
|
1099
1023
|
|
|
1100
1024
|
rmSync(repo, { recursive: true, force: true });
|
|
1101
|
-
}
|
|
1025
|
+
});
|
|
1102
1026
|
|
|
1103
1027
|
// ─── untrackRuntimeFiles: removes tracked runtime files from index ───
|
|
1104
1028
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
{
|
|
1029
|
+
test('untrackRuntimeFiles', async () => {
|
|
1108
1030
|
const { untrackRuntimeFiles } = await import("../gitignore.ts");
|
|
1109
1031
|
const repo = mkdtempSync(join(tmpdir(), "gsd-untrack-"));
|
|
1110
1032
|
run("git init -b main", repo);
|
|
@@ -1125,38 +1047,36 @@ async function main(): Promise<void> {
|
|
|
1125
1047
|
|
|
1126
1048
|
// Precondition: runtime files are tracked
|
|
1127
1049
|
const trackedBefore = run("git ls-files .gsd/", repo);
|
|
1128
|
-
|
|
1129
|
-
|
|
1050
|
+
assert.ok(trackedBefore.includes("completed-units.json"), "untrack: precondition — completed-units tracked");
|
|
1051
|
+
assert.ok(trackedBefore.includes("metrics.json"), "untrack: precondition — metrics tracked");
|
|
1130
1052
|
|
|
1131
1053
|
// Run untrackRuntimeFiles
|
|
1132
1054
|
untrackRuntimeFiles(repo);
|
|
1133
1055
|
|
|
1134
1056
|
// Runtime files should be removed from the index
|
|
1135
1057
|
const trackedAfter = run("git ls-files .gsd/", repo);
|
|
1136
|
-
|
|
1058
|
+
assert.deepStrictEqual(trackedAfter, "", "untrack: all runtime files removed from index");
|
|
1137
1059
|
|
|
1138
1060
|
// Non-runtime files remain tracked
|
|
1139
1061
|
const srcTracked = run("git ls-files src.ts", repo);
|
|
1140
|
-
|
|
1062
|
+
assert.ok(srcTracked.includes("src.ts"), "untrack: non-runtime files remain tracked");
|
|
1141
1063
|
|
|
1142
1064
|
// Files still exist on disk
|
|
1143
|
-
|
|
1065
|
+
assert.ok(existsSync(join(repo, ".gsd", "completed-units.json")),
|
|
1144
1066
|
"untrack: completed-units.json still on disk");
|
|
1145
|
-
|
|
1067
|
+
assert.ok(existsSync(join(repo, ".gsd", "metrics.json")),
|
|
1146
1068
|
"untrack: metrics.json still on disk");
|
|
1147
1069
|
|
|
1148
1070
|
// Idempotent — running again doesn't error
|
|
1149
1071
|
untrackRuntimeFiles(repo);
|
|
1150
|
-
|
|
1072
|
+
assert.ok(true, "untrack: second call is idempotent (no error)");
|
|
1151
1073
|
|
|
1152
1074
|
rmSync(repo, { recursive: true, force: true });
|
|
1153
|
-
}
|
|
1075
|
+
});
|
|
1154
1076
|
|
|
1155
1077
|
// ─── smartStage excludes runtime files but allows milestone artifacts ──
|
|
1156
1078
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
{
|
|
1079
|
+
test('smartStage excludes runtime files, allows milestone artifacts', () => {
|
|
1160
1080
|
const repo = mkdtempSync(join(tmpdir(), "gsd-smart-stage-excludes-"));
|
|
1161
1081
|
run("git init -b main", repo);
|
|
1162
1082
|
run("git config user.email test@test.com", repo);
|
|
@@ -1178,71 +1098,65 @@ async function main(): Promise<void> {
|
|
|
1178
1098
|
// smartStage excludes only runtime paths, not all of .gsd/ (#1326)
|
|
1179
1099
|
const svc = new GitServiceImpl(repo);
|
|
1180
1100
|
const msg = svc.commit({ message: "test commit" });
|
|
1181
|
-
|
|
1101
|
+
assert.ok(msg !== null, "smartStage: commit succeeds");
|
|
1182
1102
|
|
|
1183
1103
|
const committed = run("git show --name-only HEAD", repo);
|
|
1184
|
-
|
|
1104
|
+
assert.ok(committed.includes("src.ts"), "smartStage: source files ARE in commit");
|
|
1185
1105
|
// Runtime files should NOT be committed
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1106
|
+
assert.ok(!committed.includes(".gsd/STATE.md"), "smartStage: STATE.md excluded (runtime)");
|
|
1107
|
+
assert.ok(!committed.includes(".gsd/runtime/"), "smartStage: runtime/ excluded");
|
|
1108
|
+
assert.ok(!committed.includes(".gsd/activity/"), "smartStage: activity/ excluded");
|
|
1189
1109
|
// Milestone artifacts SHOULD be committed when not gitignored (#1326)
|
|
1190
|
-
|
|
1110
|
+
assert.ok(committed.includes(".gsd/milestones/"), "smartStage: milestone artifacts ARE committed");
|
|
1191
1111
|
|
|
1192
1112
|
rmSync(repo, { recursive: true, force: true });
|
|
1193
|
-
}
|
|
1113
|
+
});
|
|
1194
1114
|
|
|
1195
1115
|
// ─── writeIntegrationBranch: no commit (metadata in external storage) ──
|
|
1196
1116
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
{
|
|
1117
|
+
test('writeIntegrationBranch: no commit', () => {
|
|
1200
1118
|
const repo = initBranchTestRepo();
|
|
1201
1119
|
const commitsBefore = run("git rev-list --count HEAD", repo);
|
|
1202
1120
|
|
|
1203
1121
|
writeIntegrationBranch(repo, "M001", "f-123-new-thing");
|
|
1204
1122
|
|
|
1205
1123
|
// File should still be written to disk
|
|
1206
|
-
|
|
1124
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-new-thing",
|
|
1207
1125
|
"writeIntegrationBranch: metadata file exists on disk");
|
|
1208
1126
|
|
|
1209
1127
|
// No commit — .gsd/ is managed externally
|
|
1210
1128
|
const commitsAfter = run("git rev-list --count HEAD", repo);
|
|
1211
|
-
|
|
1129
|
+
assert.deepStrictEqual(commitsBefore, commitsAfter,
|
|
1212
1130
|
"writeIntegrationBranch: no git commit created for integration branch");
|
|
1213
1131
|
|
|
1214
1132
|
rmSync(repo, { recursive: true, force: true });
|
|
1215
|
-
}
|
|
1133
|
+
});
|
|
1216
1134
|
|
|
1217
1135
|
// ─── ensureGitignore: always adds .gsd to gitignore ──────────────────
|
|
1218
1136
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
{
|
|
1137
|
+
test('ensureGitignore: adds .gsd entry', async () => {
|
|
1222
1138
|
const { ensureGitignore } = await import("../gitignore.ts");
|
|
1223
1139
|
const repo = mkdtempSync(join(tmpdir(), "gsd-gitignore-external-state-"));
|
|
1224
1140
|
|
|
1225
1141
|
// Should add .gsd to gitignore (external state dir is a symlink)
|
|
1226
1142
|
const modified = ensureGitignore(repo);
|
|
1227
|
-
|
|
1143
|
+
assert.ok(modified, "ensureGitignore: gitignore was modified");
|
|
1228
1144
|
|
|
1229
1145
|
const { readFileSync } = await import("node:fs");
|
|
1230
1146
|
const content = readFileSync(join(repo, ".gitignore"), "utf-8");
|
|
1231
1147
|
const lines = content.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#"));
|
|
1232
|
-
|
|
1148
|
+
assert.ok(lines.includes(".gsd"), "ensureGitignore: .gitignore contains .gsd");
|
|
1233
1149
|
|
|
1234
1150
|
// Idempotent — calling again doesn't add duplicates
|
|
1235
1151
|
const modified2 = ensureGitignore(repo);
|
|
1236
|
-
|
|
1152
|
+
assert.ok(!modified2, "ensureGitignore: second call is idempotent");
|
|
1237
1153
|
|
|
1238
1154
|
rmSync(repo, { recursive: true, force: true });
|
|
1239
|
-
}
|
|
1155
|
+
});
|
|
1240
1156
|
|
|
1241
1157
|
// ─── nativeAddAllWithExclusions: symlinked .gsd fallback ───────────────
|
|
1242
1158
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
{
|
|
1159
|
+
test('nativeAddAllWithExclusions: symlinked .gsd fallback', () => {
|
|
1246
1160
|
// When .gsd is a symlink, git rejects `:!.gsd/...` pathspecs with
|
|
1247
1161
|
// "fatal: pathspec '...' is beyond a symbolic link". The fix falls
|
|
1248
1162
|
// back to plain `git add -A`, which respects .gitignore.
|
|
@@ -1271,22 +1185,20 @@ async function main(): Promise<void> {
|
|
|
1271
1185
|
threw = true;
|
|
1272
1186
|
console.error(" unexpected error:", e);
|
|
1273
1187
|
}
|
|
1274
|
-
|
|
1188
|
+
assert.ok(!threw, "nativeAddAllWithExclusions does not throw with symlinked .gsd");
|
|
1275
1189
|
|
|
1276
1190
|
// Verify the real file was staged
|
|
1277
1191
|
const staged = run("git diff --cached --name-only", repo);
|
|
1278
|
-
|
|
1279
|
-
|
|
1192
|
+
assert.ok(staged.includes("src/app.ts"), "real file staged despite symlinked .gsd");
|
|
1193
|
+
assert.ok(!staged.includes(".gsd"), ".gsd content not staged");
|
|
1280
1194
|
|
|
1281
1195
|
rmSync(repo, { recursive: true, force: true });
|
|
1282
1196
|
rmSync(externalGsd, { recursive: true, force: true });
|
|
1283
|
-
}
|
|
1197
|
+
});
|
|
1284
1198
|
|
|
1285
1199
|
// ─── nativeAddAllWithExclusions: non-symlinked .gsd still works ───────
|
|
1286
1200
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
{
|
|
1201
|
+
test('nativeAddAllWithExclusions: non-symlinked .gsd still works', () => {
|
|
1290
1202
|
// Verify the normal (non-symlink) case still works with pathspec exclusions
|
|
1291
1203
|
const repo = initTempRepo();
|
|
1292
1204
|
|
|
@@ -1300,96 +1212,91 @@ async function main(): Promise<void> {
|
|
|
1300
1212
|
} catch {
|
|
1301
1213
|
threw = true;
|
|
1302
1214
|
}
|
|
1303
|
-
|
|
1215
|
+
assert.ok(!threw, "nativeAddAllWithExclusions works with normal .gsd directory");
|
|
1304
1216
|
|
|
1305
1217
|
const staged = run("git diff --cached --name-only", repo);
|
|
1306
|
-
|
|
1218
|
+
assert.ok(staged.includes("src/code.ts"), "real file staged with normal .gsd");
|
|
1307
1219
|
|
|
1308
1220
|
rmSync(repo, { recursive: true, force: true });
|
|
1309
|
-
}
|
|
1221
|
+
});
|
|
1310
1222
|
|
|
1311
1223
|
// ─── MergeConflictError: constructor fields ───────────────────────────────
|
|
1312
1224
|
|
|
1313
|
-
|
|
1314
|
-
{
|
|
1225
|
+
test('MergeConflictError: constructor fields', () => {
|
|
1315
1226
|
const err = new MergeConflictError(
|
|
1316
1227
|
["src/foo.ts", "src/bar.ts"],
|
|
1317
1228
|
"squash",
|
|
1318
1229
|
"gsd/M001/S01",
|
|
1319
1230
|
"main",
|
|
1320
1231
|
);
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1232
|
+
assert.deepStrictEqual(err.conflictedFiles, ["src/foo.ts", "src/bar.ts"], "MergeConflictError.conflictedFiles populated");
|
|
1233
|
+
assert.deepStrictEqual(err.strategy, "squash", "MergeConflictError.strategy set");
|
|
1234
|
+
assert.deepStrictEqual(err.branch, "gsd/M001/S01", "MergeConflictError.branch set");
|
|
1235
|
+
assert.deepStrictEqual(err.mainBranch, "main", "MergeConflictError.mainBranch set");
|
|
1236
|
+
assert.deepStrictEqual(err.name, "MergeConflictError", "MergeConflictError.name is MergeConflictError");
|
|
1237
|
+
assert.ok(err.message.includes("src/foo.ts"), "MergeConflictError message lists conflicted files");
|
|
1238
|
+
assert.ok(err.message.toLowerCase().includes("squash"), "MergeConflictError message mentions strategy");
|
|
1239
|
+
assert.ok(err instanceof MergeConflictError, "MergeConflictError is an instanceof MergeConflictError");
|
|
1240
|
+
assert.ok(err instanceof Error, "MergeConflictError is an Error instance");
|
|
1241
|
+
});
|
|
1331
1242
|
|
|
1332
1243
|
// ─── Integration branch: rejects gsd/quick/* branches ────────────────────
|
|
1333
1244
|
|
|
1334
|
-
|
|
1335
|
-
{
|
|
1245
|
+
test('Integration branch: rejects gsd/quick/* branches', () => {
|
|
1336
1246
|
const repo = initBranchTestRepo();
|
|
1337
1247
|
|
|
1338
1248
|
writeIntegrationBranch(repo, "M001", "gsd/quick/1234-some-task");
|
|
1339
|
-
|
|
1249
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "gsd/quick/* branches are not recorded as integration branch");
|
|
1340
1250
|
|
|
1341
1251
|
rmSync(repo, { recursive: true, force: true });
|
|
1342
|
-
}
|
|
1252
|
+
});
|
|
1343
1253
|
|
|
1344
1254
|
// ─── Integration branch: resolver returns missing when no metadata ────────
|
|
1345
1255
|
|
|
1346
|
-
|
|
1347
|
-
{
|
|
1256
|
+
test('Integration branch: resolver returns missing when no metadata', () => {
|
|
1348
1257
|
const repo = initBranchTestRepo();
|
|
1349
1258
|
|
|
1350
1259
|
// No writeIntegrationBranch call — no metadata file exists
|
|
1351
1260
|
const resolved = resolveMilestoneIntegrationBranch(repo, "M999");
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1261
|
+
assert.deepStrictEqual(resolved.status, "missing", "resolver reports missing when no metadata file");
|
|
1262
|
+
assert.deepStrictEqual(resolved.recordedBranch, null, "resolver recordedBranch is null when no metadata");
|
|
1263
|
+
assert.deepStrictEqual(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no metadata");
|
|
1264
|
+
assert.ok(resolved.reason.includes("M999"), "resolver reason mentions the milestone ID");
|
|
1356
1265
|
|
|
1357
1266
|
rmSync(repo, { recursive: true, force: true });
|
|
1358
|
-
}
|
|
1267
|
+
});
|
|
1359
1268
|
|
|
1360
1269
|
// ─── Integration branch: resolver missing when both recorded and configured branches gone ───
|
|
1361
1270
|
|
|
1362
|
-
|
|
1363
|
-
{
|
|
1271
|
+
test('Integration branch: resolver missing when both recorded and configured branches gone', () => {
|
|
1364
1272
|
const repo = initBranchTestRepo();
|
|
1365
1273
|
|
|
1366
1274
|
// Record a branch that doesn't exist
|
|
1367
1275
|
writeIntegrationBranch(repo, "M001", "deleted-feature");
|
|
1368
1276
|
// configured main_branch also doesn't exist
|
|
1369
1277
|
const resolved = resolveMilestoneIntegrationBranch(repo, "M001", { main_branch: "nonexistent-branch" });
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1278
|
+
assert.deepStrictEqual(resolved.status, "missing", "resolver reports missing when recorded branch and configured main_branch both absent");
|
|
1279
|
+
assert.deepStrictEqual(resolved.recordedBranch, "deleted-feature", "resolver preserves stale recorded branch");
|
|
1280
|
+
assert.deepStrictEqual(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no safe fallback");
|
|
1281
|
+
assert.ok(
|
|
1374
1282
|
resolved.reason.includes("deleted-feature") && resolved.reason.includes("nonexistent-branch"),
|
|
1375
1283
|
"reason mentions both stale branch and unavailable configured branch",
|
|
1376
1284
|
);
|
|
1377
1285
|
|
|
1378
1286
|
rmSync(repo, { recursive: true, force: true });
|
|
1379
|
-
}
|
|
1287
|
+
});
|
|
1380
1288
|
|
|
1381
1289
|
// ─── buildTaskCommitMessage: issueNumber appends Resolves trailer ─────────
|
|
1382
1290
|
|
|
1383
|
-
|
|
1384
|
-
{
|
|
1291
|
+
test('buildTaskCommitMessage: issueNumber appends Resolves trailer', () => {
|
|
1385
1292
|
const msg = buildTaskCommitMessage({
|
|
1386
1293
|
taskId: "S01/T03",
|
|
1387
1294
|
taskTitle: "fix login redirect",
|
|
1388
1295
|
issueNumber: 42,
|
|
1389
1296
|
});
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1297
|
+
assert.ok(msg.includes("Resolves #42"), "buildTaskCommitMessage includes Resolves #N trailer when issueNumber is set");
|
|
1298
|
+
assert.ok(msg.startsWith("fix(S01/T03):"), "buildTaskCommitMessage infers fix type");
|
|
1299
|
+
});
|
|
1393
1300
|
|
|
1394
1301
|
{
|
|
1395
1302
|
// No issueNumber — no Resolves trailer
|
|
@@ -1397,29 +1304,26 @@ async function main(): Promise<void> {
|
|
|
1397
1304
|
taskId: "S01/T04",
|
|
1398
1305
|
taskTitle: "add dashboard widget",
|
|
1399
1306
|
});
|
|
1400
|
-
|
|
1307
|
+
assert.ok(!msg.includes("Resolves"), "buildTaskCommitMessage omits Resolves trailer when issueNumber is absent");
|
|
1401
1308
|
}
|
|
1402
1309
|
|
|
1403
1310
|
// ─── runPreMergeCheck: skips when no package.json ────────────────────────
|
|
1404
1311
|
|
|
1405
|
-
|
|
1406
|
-
{
|
|
1312
|
+
test('runPreMergeCheck: skips when no package.json', () => {
|
|
1407
1313
|
const repo = initBranchTestRepo();
|
|
1408
1314
|
// No package.json created — auto-detect should skip gracefully
|
|
1409
1315
|
const svc = new GitServiceImpl(repo, { pre_merge_check: true });
|
|
1410
1316
|
const result: PreMergeCheckResult = svc.runPreMergeCheck();
|
|
1411
1317
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1318
|
+
assert.deepStrictEqual(result.passed, true, "runPreMergeCheck passes when no package.json (skip)");
|
|
1319
|
+
assert.deepStrictEqual(result.skipped, true, "runPreMergeCheck skips when no package.json found");
|
|
1414
1320
|
|
|
1415
1321
|
rmSync(repo, { recursive: true, force: true });
|
|
1416
|
-
}
|
|
1322
|
+
});
|
|
1417
1323
|
|
|
1418
1324
|
// ─── autoCommit: symlinked .gsd does NOT stage milestone artifacts (#2247) ──
|
|
1419
1325
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
{
|
|
1326
|
+
test('autoCommit: symlinked .gsd does NOT stage milestone artifacts (#2247)', () => {
|
|
1423
1327
|
// When .gsd is a symlink (external state project), .gsd/ files live outside
|
|
1424
1328
|
// the repo by design. smartStage() must NOT force-stage them into git — the
|
|
1425
1329
|
// .gitignore exclusion is correct and intentional.
|
|
@@ -1448,21 +1352,14 @@ async function main(): Promise<void> {
|
|
|
1448
1352
|
|
|
1449
1353
|
const svc = new GitServiceImpl(repo);
|
|
1450
1354
|
const msg = svc.autoCommit("complete-milestone", "M009");
|
|
1451
|
-
|
|
1355
|
+
assert.ok(msg !== null, "symlink autoCommit: commit succeeds");
|
|
1452
1356
|
|
|
1453
1357
|
const committed = run("git show --name-only HEAD", repo);
|
|
1454
|
-
|
|
1455
|
-
|
|
1358
|
+
assert.ok(committed.includes("src/feature.ts"), "symlink autoCommit: source file committed");
|
|
1359
|
+
assert.ok(!committed.includes(".gsd/milestones/"),
|
|
1456
1360
|
"symlink autoCommit: .gsd/milestones/ files are NOT staged (external state stays external)");
|
|
1457
1361
|
|
|
1458
1362
|
try { rmSync(repo, { recursive: true, force: true }); } catch {}
|
|
1459
1363
|
try { rmSync(externalGsd, { recursive: true, force: true }); } catch {}
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
report();
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
main().catch((error) => {
|
|
1466
|
-
console.error(error);
|
|
1467
|
-
process.exit(1);
|
|
1364
|
+
});
|
|
1468
1365
|
});
|