gsd-pi 2.45.0 → 2.46.0-dev.cc9d310
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/dist/help-text.js +1 -1
- package/dist/loader.js +34 -0
- package/dist/resources/extensions/gsd/auto/phases.js +27 -42
- package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
- package/dist/resources/extensions/gsd/auto/session.js +0 -11
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
- package/dist/resources/extensions/gsd/auto.js +12 -57
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
- package/dist/resources/extensions/gsd/commands/context.js +0 -4
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
- package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
- package/dist/resources/extensions/gsd/db-writer.js +9 -9
- package/dist/resources/extensions/gsd/doctor-checks.js +167 -2
- package/dist/resources/extensions/gsd/doctor.js +5 -3
- package/dist/resources/extensions/gsd/gsd-db.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +1 -2
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
- package/dist/resources/extensions/gsd/preferences-types.js +2 -2
- package/dist/resources/extensions/gsd/preferences.js +8 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
- package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/session-lock.js +1 -3
- package/dist/resources/extensions/gsd/state.js +7 -0
- package/dist/resources/extensions/gsd/sync-lock.js +89 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +61 -11
- package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
- package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
- package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
- package/dist/resources/extensions/gsd/workflow-events.js +102 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +193 -0
- package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
- package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
- package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
- package/dist/resources/extensions/gsd/write-intercept.js +84 -0
- package/dist/resources/extensions/voice/index.js +11 -16
- package/dist/resources/extensions/voice/linux-ready.js +67 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- 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-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- 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 +2 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- 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/lifecycle-hooks.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
- package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
- package/src/resources/extensions/gsd/auto/phases.ts +24 -44
- package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
- package/src/resources/extensions/gsd/auto/session.ts +0 -18
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
- package/src/resources/extensions/gsd/auto-start.ts +1 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
- package/src/resources/extensions/gsd/auto.ts +7 -83
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/commands/context.ts +0 -5
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
- package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
- package/src/resources/extensions/gsd/db-writer.ts +9 -17
- package/src/resources/extensions/gsd/doctor-checks.ts +180 -2
- package/src/resources/extensions/gsd/doctor-types.ts +7 -1
- package/src/resources/extensions/gsd/doctor.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +16 -3
- package/src/resources/extensions/gsd/guided-flow.ts +1 -2
- package/src/resources/extensions/gsd/journal.ts +6 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
- package/src/resources/extensions/gsd/preferences-types.ts +2 -2
- package/src/resources/extensions/gsd/preferences.ts +7 -3
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/prompts/queue.md +2 -2
- package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/session-lock.ts +0 -4
- package/src/resources/extensions/gsd/state.ts +8 -0
- package/src/resources/extensions/gsd/sync-lock.ts +94 -0
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +74 -11
- package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
- package/src/resources/extensions/gsd/workflow-events.ts +154 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +243 -0
- package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
- package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
- package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
- package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
- package/src/resources/extensions/gsd/write-intercept.ts +90 -0
- package/src/resources/extensions/voice/index.ts +11 -21
- package/src/resources/extensions/voice/linux-ready.ts +87 -0
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
- package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// GSD — reopen-task handler tests
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
openDatabase,
|
|
12
|
+
closeDatabase,
|
|
13
|
+
insertMilestone,
|
|
14
|
+
insertSlice,
|
|
15
|
+
insertTask,
|
|
16
|
+
getTask,
|
|
17
|
+
} from '../gsd-db.ts';
|
|
18
|
+
import { handleReopenTask } from '../tools/reopen-task.ts';
|
|
19
|
+
|
|
20
|
+
function makeTmpBase(): string {
|
|
21
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-reopen-task-'));
|
|
22
|
+
mkdirSync(join(base, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks'), { recursive: true });
|
|
23
|
+
return base;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function cleanup(base: string): void {
|
|
27
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
28
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function seedCompleteTask(): void {
|
|
32
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone', status: 'active' });
|
|
33
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice', status: 'in_progress' });
|
|
34
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Task One', status: 'complete' });
|
|
35
|
+
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Task Two', status: 'pending' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Success path ────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
test('handleReopenTask: resets a complete task to pending', async () => {
|
|
41
|
+
const base = makeTmpBase();
|
|
42
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
43
|
+
try {
|
|
44
|
+
seedCompleteTask();
|
|
45
|
+
|
|
46
|
+
const result = await handleReopenTask({
|
|
47
|
+
milestoneId: 'M001',
|
|
48
|
+
sliceId: 'S01',
|
|
49
|
+
taskId: 'T01',
|
|
50
|
+
reason: 'verification failed after merge',
|
|
51
|
+
}, base);
|
|
52
|
+
|
|
53
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
54
|
+
assert.equal(result.taskId, 'T01');
|
|
55
|
+
|
|
56
|
+
const task = getTask('M001', 'S01', 'T01');
|
|
57
|
+
assert.ok(task, 'task should still exist');
|
|
58
|
+
assert.equal(task!.status, 'pending', 'task status should be reset to pending');
|
|
59
|
+
} finally {
|
|
60
|
+
cleanup(base);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('handleReopenTask: does not affect other tasks in the slice', async () => {
|
|
65
|
+
const base = makeTmpBase();
|
|
66
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
67
|
+
try {
|
|
68
|
+
seedCompleteTask();
|
|
69
|
+
|
|
70
|
+
await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
|
|
71
|
+
|
|
72
|
+
const t02 = getTask('M001', 'S01', 'T02');
|
|
73
|
+
assert.ok(t02, 'T02 should still exist');
|
|
74
|
+
assert.equal(t02!.status, 'pending', 'T02 status should be unchanged');
|
|
75
|
+
} finally {
|
|
76
|
+
cleanup(base);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ─── Failure paths ───────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
test('handleReopenTask: rejects empty taskId', async () => {
|
|
83
|
+
const base = makeTmpBase();
|
|
84
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
85
|
+
try {
|
|
86
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: '' }, base);
|
|
87
|
+
assert.ok('error' in result);
|
|
88
|
+
assert.match(result.error, /taskId/);
|
|
89
|
+
} finally {
|
|
90
|
+
cleanup(base);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('handleReopenTask: rejects non-existent milestone', async () => {
|
|
95
|
+
const base = makeTmpBase();
|
|
96
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
97
|
+
try {
|
|
98
|
+
const result = await handleReopenTask({ milestoneId: 'M999', sliceId: 'S01', taskId: 'T01' }, base);
|
|
99
|
+
assert.ok('error' in result);
|
|
100
|
+
assert.match(result.error, /milestone not found/);
|
|
101
|
+
} finally {
|
|
102
|
+
cleanup(base);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('handleReopenTask: rejects task in a closed milestone', async () => {
|
|
107
|
+
const base = makeTmpBase();
|
|
108
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
109
|
+
try {
|
|
110
|
+
insertMilestone({ id: 'M001', title: 'Done', status: 'complete' });
|
|
111
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
112
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
113
|
+
|
|
114
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
|
|
115
|
+
assert.ok('error' in result);
|
|
116
|
+
assert.match(result.error, /closed milestone/);
|
|
117
|
+
} finally {
|
|
118
|
+
cleanup(base);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('handleReopenTask: rejects task inside a closed slice', async () => {
|
|
123
|
+
const base = makeTmpBase();
|
|
124
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
125
|
+
try {
|
|
126
|
+
insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
|
|
127
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
128
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
129
|
+
|
|
130
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
|
|
131
|
+
assert.ok('error' in result);
|
|
132
|
+
assert.match(result.error, /closed slice/);
|
|
133
|
+
} finally {
|
|
134
|
+
cleanup(base);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('handleReopenTask: rejects reopening a task that is not complete', async () => {
|
|
139
|
+
const base = makeTmpBase();
|
|
140
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
141
|
+
try {
|
|
142
|
+
seedCompleteTask();
|
|
143
|
+
|
|
144
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T02' }, base);
|
|
145
|
+
assert.ok('error' in result);
|
|
146
|
+
assert.match(result.error, /not complete/);
|
|
147
|
+
} finally {
|
|
148
|
+
cleanup(base);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('handleReopenTask: rejects non-existent task', async () => {
|
|
153
|
+
const base = makeTmpBase();
|
|
154
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
155
|
+
try {
|
|
156
|
+
insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
|
|
157
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'in_progress' });
|
|
158
|
+
|
|
159
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T99' }, base);
|
|
160
|
+
assert.ok('error' in result);
|
|
161
|
+
assert.match(result.error, /task not found/);
|
|
162
|
+
} finally {
|
|
163
|
+
cleanup(base);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
@@ -103,7 +103,7 @@ describe('session-lock-regression', async () => {
|
|
|
103
103
|
try {
|
|
104
104
|
acquireSessionLock(base);
|
|
105
105
|
|
|
106
|
-
updateSessionLock(base, 'execute-task', 'M001/S01/T01',
|
|
106
|
+
updateSessionLock(base, 'execute-task', 'M001/S01/T01', '/tmp/session.json');
|
|
107
107
|
|
|
108
108
|
const data = readSessionLockData(base);
|
|
109
109
|
assert.ok(data !== null, 'lock data readable after update');
|
|
@@ -111,7 +111,6 @@ describe('session-lock-regression', async () => {
|
|
|
111
111
|
assert.deepStrictEqual(data.pid, process.pid, 'lock data has correct PID');
|
|
112
112
|
assert.deepStrictEqual(data.unitType, 'execute-task', 'lock data has correct unit type');
|
|
113
113
|
assert.deepStrictEqual(data.unitId, 'M001/S01/T01', 'lock data has correct unit ID');
|
|
114
|
-
assert.deepStrictEqual(data.completedUnits, 5, 'lock data has correct completed count');
|
|
115
114
|
assert.deepStrictEqual(data.sessionFile, '/tmp/session.json', 'lock data has session file');
|
|
116
115
|
}
|
|
117
116
|
|
|
@@ -136,7 +135,6 @@ describe('session-lock-regression', async () => {
|
|
|
136
135
|
unitType: 'execute-task',
|
|
137
136
|
unitId: 'M001/S01/T01',
|
|
138
137
|
unitStartedAt: new Date(Date.now() - 3600000).toISOString(),
|
|
139
|
-
completedUnits: 3,
|
|
140
138
|
};
|
|
141
139
|
writeFileSync(lockFile, JSON.stringify(staleLock, null, 2));
|
|
142
140
|
|
|
@@ -233,7 +231,6 @@ describe('session-lock-regression', async () => {
|
|
|
233
231
|
unitType: 'execute-task',
|
|
234
232
|
unitId: 'M001/S01/T01',
|
|
235
233
|
unitStartedAt: new Date().toISOString(),
|
|
236
|
-
completedUnits: 0,
|
|
237
234
|
}, null, 2));
|
|
238
235
|
|
|
239
236
|
const status = getSessionLockStatus(base);
|
|
@@ -64,7 +64,7 @@ test("stopAutoRemote cleans up stale lock (dead PID) and returns found:false", (
|
|
|
64
64
|
const base = makeTmpBase();
|
|
65
65
|
try {
|
|
66
66
|
// Write a lock with a PID that doesn't exist
|
|
67
|
-
writeLock(base, "execute-task", "M001/S01/T01"
|
|
67
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
68
68
|
// Overwrite PID to a dead one
|
|
69
69
|
const lock = readCrashLock(base)!;
|
|
70
70
|
const staleData = { ...lock, pid: 999999999 };
|
|
@@ -111,7 +111,6 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", {
|
|
|
111
111
|
unitType: "execute-task",
|
|
112
112
|
unitId: "M001/S01/T01",
|
|
113
113
|
unitStartedAt: new Date().toISOString(),
|
|
114
|
-
completedUnits: 0,
|
|
115
114
|
};
|
|
116
115
|
writeFileSync(join(base, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2), "utf-8");
|
|
117
116
|
|
|
@@ -143,7 +142,7 @@ test("lock file should be discoverable at project root, not worktree path", () =
|
|
|
143
142
|
|
|
144
143
|
try {
|
|
145
144
|
// Simulate: auto-mode writes lock to project root (the fix)
|
|
146
|
-
writeLock(projectRoot, "execute-task", "M001/S01/T01"
|
|
145
|
+
writeLock(projectRoot, "execute-task", "M001/S01/T01");
|
|
147
146
|
|
|
148
147
|
// Second terminal checks project root — should find the lock
|
|
149
148
|
const lock = readCrashLock(projectRoot);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// GSD Extension — sync-lock unit tests
|
|
2
|
+
// Tests acquireSyncLock() and releaseSyncLock().
|
|
3
|
+
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
import { acquireSyncLock, releaseSyncLock } from '../sync-lock.ts';
|
|
10
|
+
|
|
11
|
+
function tempDir(): string {
|
|
12
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-sync-lock-'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function cleanupDir(dirPath: string): void {
|
|
16
|
+
try { fs.rmSync(dirPath, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ─── acquireSyncLock ─────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
test('sync-lock: acquireSyncLock returns { acquired: true } when no lock exists', () => {
|
|
22
|
+
const base = tempDir();
|
|
23
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
24
|
+
try {
|
|
25
|
+
const result = acquireSyncLock(base);
|
|
26
|
+
assert.strictEqual(result.acquired, true);
|
|
27
|
+
} finally {
|
|
28
|
+
cleanupDir(base);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('sync-lock: acquireSyncLock creates lock file at .gsd/sync.lock', () => {
|
|
33
|
+
const base = tempDir();
|
|
34
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
35
|
+
try {
|
|
36
|
+
acquireSyncLock(base);
|
|
37
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
38
|
+
assert.ok(fs.existsSync(lockPath), 'sync.lock should exist after acquire');
|
|
39
|
+
} finally {
|
|
40
|
+
cleanupDir(base);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('sync-lock: lock file contains pid and acquired_at fields', () => {
|
|
45
|
+
const base = tempDir();
|
|
46
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
47
|
+
try {
|
|
48
|
+
acquireSyncLock(base);
|
|
49
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
50
|
+
const content = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
|
|
51
|
+
assert.strictEqual(typeof content.pid, 'number');
|
|
52
|
+
assert.strictEqual(typeof content.acquired_at, 'string');
|
|
53
|
+
} finally {
|
|
54
|
+
cleanupDir(base);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ─── releaseSyncLock ─────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
test('sync-lock: releaseSyncLock removes lock file', () => {
|
|
61
|
+
const base = tempDir();
|
|
62
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
63
|
+
try {
|
|
64
|
+
acquireSyncLock(base);
|
|
65
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
66
|
+
assert.ok(fs.existsSync(lockPath), 'lock file should exist before release');
|
|
67
|
+
releaseSyncLock(base);
|
|
68
|
+
assert.ok(!fs.existsSync(lockPath), 'lock file should not exist after release');
|
|
69
|
+
} finally {
|
|
70
|
+
cleanupDir(base);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('sync-lock: releaseSyncLock is a no-op when no lock file exists', () => {
|
|
75
|
+
const base = tempDir();
|
|
76
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
77
|
+
try {
|
|
78
|
+
// Should not throw
|
|
79
|
+
releaseSyncLock(base);
|
|
80
|
+
} finally {
|
|
81
|
+
cleanupDir(base);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ─── acquire → release → re-acquire round-trip ───────────────────────────
|
|
86
|
+
|
|
87
|
+
test('sync-lock: can re-acquire after release', () => {
|
|
88
|
+
const base = tempDir();
|
|
89
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
90
|
+
try {
|
|
91
|
+
const r1 = acquireSyncLock(base);
|
|
92
|
+
assert.strictEqual(r1.acquired, true, 'first acquire should succeed');
|
|
93
|
+
releaseSyncLock(base);
|
|
94
|
+
const r2 = acquireSyncLock(base);
|
|
95
|
+
assert.strictEqual(r2.acquired, true, 're-acquire after release should succeed');
|
|
96
|
+
releaseSyncLock(base);
|
|
97
|
+
} finally {
|
|
98
|
+
cleanupDir(base);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ─── stale lock override ─────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
test('sync-lock: overrides stale lock file (mtime backdated)', (t) => {
|
|
105
|
+
const base = tempDir();
|
|
106
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
107
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
108
|
+
try {
|
|
109
|
+
// Write a lock file with a very old mtime (simulating staleness)
|
|
110
|
+
fs.writeFileSync(lockPath, JSON.stringify({ pid: 99999, acquired_at: new Date(0).toISOString() }));
|
|
111
|
+
// Backdate mtime by 2 minutes
|
|
112
|
+
const staleTime = new Date(Date.now() - 120_000);
|
|
113
|
+
fs.utimesSync(lockPath, staleTime, staleTime);
|
|
114
|
+
|
|
115
|
+
// Should override stale lock and acquire
|
|
116
|
+
const result = acquireSyncLock(base, 500);
|
|
117
|
+
assert.strictEqual(result.acquired, true, 'should acquire over stale lock');
|
|
118
|
+
releaseSyncLock(base);
|
|
119
|
+
} finally {
|
|
120
|
+
cleanupDir(base);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// GSD — unit-ownership tests
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
claimUnit,
|
|
12
|
+
releaseUnit,
|
|
13
|
+
getOwner,
|
|
14
|
+
checkOwnership,
|
|
15
|
+
taskUnitKey,
|
|
16
|
+
sliceUnitKey,
|
|
17
|
+
} from '../unit-ownership.ts';
|
|
18
|
+
|
|
19
|
+
function makeTmpBase(): string {
|
|
20
|
+
return mkdtempSync(join(tmpdir(), 'gsd-ownership-'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cleanup(base: string): void {
|
|
24
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Key builders ────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
test('taskUnitKey: builds correct key', () => {
|
|
30
|
+
assert.equal(taskUnitKey('M001', 'S01', 'T01'), 'M001/S01/T01');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('sliceUnitKey: builds correct key', () => {
|
|
34
|
+
assert.equal(sliceUnitKey('M001', 'S01'), 'M001/S01');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ─── Claim / get / release ───────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
test('claimUnit: creates claim file and records agent', () => {
|
|
40
|
+
const base = makeTmpBase();
|
|
41
|
+
try {
|
|
42
|
+
claimUnit(base, 'M001/S01/T01', 'executor-01');
|
|
43
|
+
|
|
44
|
+
assert.ok(existsSync(join(base, '.gsd', 'unit-claims.json')), 'claim file should exist');
|
|
45
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), 'executor-01');
|
|
46
|
+
} finally {
|
|
47
|
+
cleanup(base);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('claimUnit: overwrites existing claim (last writer wins)', () => {
|
|
52
|
+
const base = makeTmpBase();
|
|
53
|
+
try {
|
|
54
|
+
claimUnit(base, 'M001/S01/T01', 'executor-01');
|
|
55
|
+
claimUnit(base, 'M001/S01/T01', 'executor-02');
|
|
56
|
+
|
|
57
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), 'executor-02');
|
|
58
|
+
} finally {
|
|
59
|
+
cleanup(base);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('claimUnit: multiple units can be claimed independently', () => {
|
|
64
|
+
const base = makeTmpBase();
|
|
65
|
+
try {
|
|
66
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
67
|
+
claimUnit(base, 'M001/S01/T02', 'agent-b');
|
|
68
|
+
|
|
69
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), 'agent-a');
|
|
70
|
+
assert.equal(getOwner(base, 'M001/S01/T02'), 'agent-b');
|
|
71
|
+
} finally {
|
|
72
|
+
cleanup(base);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('getOwner: returns null when no claim file exists', () => {
|
|
77
|
+
const base = makeTmpBase();
|
|
78
|
+
try {
|
|
79
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), null);
|
|
80
|
+
} finally {
|
|
81
|
+
cleanup(base);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('getOwner: returns null for unclaimed unit', () => {
|
|
86
|
+
const base = makeTmpBase();
|
|
87
|
+
try {
|
|
88
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
89
|
+
assert.equal(getOwner(base, 'M001/S01/T99'), null);
|
|
90
|
+
} finally {
|
|
91
|
+
cleanup(base);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('releaseUnit: removes claim', () => {
|
|
96
|
+
const base = makeTmpBase();
|
|
97
|
+
try {
|
|
98
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
99
|
+
releaseUnit(base, 'M001/S01/T01');
|
|
100
|
+
|
|
101
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), null);
|
|
102
|
+
} finally {
|
|
103
|
+
cleanup(base);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('releaseUnit: no-op for non-existent claim', () => {
|
|
108
|
+
const base = makeTmpBase();
|
|
109
|
+
try {
|
|
110
|
+
// Should not throw
|
|
111
|
+
releaseUnit(base, 'M001/S01/T01');
|
|
112
|
+
} finally {
|
|
113
|
+
cleanup(base);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ─── checkOwnership ──────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
test('checkOwnership: returns null when no actorName provided (opt-in)', () => {
|
|
120
|
+
const base = makeTmpBase();
|
|
121
|
+
try {
|
|
122
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
123
|
+
|
|
124
|
+
// No actorName → ownership not enforced
|
|
125
|
+
assert.equal(checkOwnership(base, 'M001/S01/T01', undefined), null);
|
|
126
|
+
} finally {
|
|
127
|
+
cleanup(base);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('checkOwnership: returns null when no claim file exists', () => {
|
|
132
|
+
const base = makeTmpBase();
|
|
133
|
+
try {
|
|
134
|
+
assert.equal(checkOwnership(base, 'M001/S01/T01', 'agent-a'), null);
|
|
135
|
+
} finally {
|
|
136
|
+
cleanup(base);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('checkOwnership: returns null when unit is unclaimed', () => {
|
|
141
|
+
const base = makeTmpBase();
|
|
142
|
+
try {
|
|
143
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
144
|
+
|
|
145
|
+
// Different unit, unclaimed
|
|
146
|
+
assert.equal(checkOwnership(base, 'M001/S01/T99', 'agent-b'), null);
|
|
147
|
+
} finally {
|
|
148
|
+
cleanup(base);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('checkOwnership: returns null when actor matches owner', () => {
|
|
153
|
+
const base = makeTmpBase();
|
|
154
|
+
try {
|
|
155
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
156
|
+
|
|
157
|
+
assert.equal(checkOwnership(base, 'M001/S01/T01', 'agent-a'), null);
|
|
158
|
+
} finally {
|
|
159
|
+
cleanup(base);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('checkOwnership: returns error string when actor does not match owner', () => {
|
|
164
|
+
const base = makeTmpBase();
|
|
165
|
+
try {
|
|
166
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
167
|
+
|
|
168
|
+
const err = checkOwnership(base, 'M001/S01/T01', 'agent-b');
|
|
169
|
+
assert.ok(err !== null, 'should return error');
|
|
170
|
+
assert.match(err!, /owned by agent-a/);
|
|
171
|
+
assert.match(err!, /not agent-b/);
|
|
172
|
+
} finally {
|
|
173
|
+
cleanup(base);
|
|
174
|
+
}
|
|
175
|
+
});
|