gsd-pi 2.54.0 → 2.55.0-dev.9ec7cdf
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/cli.js +19 -19
- package/dist/headless-ui.d.ts +27 -1
- package/dist/headless-ui.js +203 -13
- package/dist/headless.js +60 -3
- package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +2 -2
- package/dist/resources/extensions/bg-shell/utilities.js +34 -5
- package/dist/resources/extensions/gsd/auto/phases.js +19 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +17 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-start.js +12 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +39 -14
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +5 -1
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +18 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -5
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +20 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +15 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -2
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +413 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -1
- package/dist/resources/extensions/gsd/session-lock.js +46 -12
- package/dist/resources/extensions/gsd/skill-health.js +2 -2
- package/dist/resources/extensions/gsd/visualizer-overlay.js +3 -3
- package/dist/resources/extensions/shared/format-utils.js +1 -1
- package/dist/resources/extensions/subagent/worker-registry.js +2 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
- 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/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/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 +2 -2
- 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 +15 -15
- package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-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/6502.2305d0afd2385711.js +9 -0
- 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-0c485498795110d6.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/.next/static/chunks/{webpack-bca0e732db0dcec3.js → webpack-4332cbd5dd1be584.js} +1 -1
- 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 +6 -4
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -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.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +14 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-registry.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -2
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +45 -41
- package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +2 -2
- package/src/resources/extensions/bg-shell/utilities.ts +39 -4
- package/src/resources/extensions/gsd/auto/phases.ts +25 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +21 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +15 -0
- package/src/resources/extensions/gsd/auto-start.ts +13 -5
- package/src/resources/extensions/gsd/auto-worktree.ts +46 -13
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +5 -4
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +53 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -6
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +24 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +19 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +2 -3
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +497 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -1
- package/src/resources/extensions/gsd/session-lock.ts +46 -12
- package/src/resources/extensions/gsd/skill-health.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/{all-milestones-complete-merge.test.ts → integration/all-milestones-complete-merge.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{atomic-task-closeout.test.ts → integration/atomic-task-closeout.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{auto-preflight.test.ts → integration/auto-preflight.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{auto-recovery.test.ts → integration/auto-recovery.test.ts} +7 -7
- package/src/resources/extensions/gsd/tests/{auto-secrets-gate.test.ts → integration/auto-secrets-gate.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{auto-stash-merge.test.ts → integration/auto-stash-merge.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{auto-worktree-milestone-merge.test.ts → integration/auto-worktree-milestone-merge.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{auto-worktree.test.ts → integration/auto-worktree.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{continue-here.test.ts → integration/continue-here.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{doctor-completion-deferral.test.ts → integration/doctor-completion-deferral.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-delimiter-fix.test.ts → integration/doctor-delimiter-fix.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-enhancements.test.ts → integration/doctor-enhancements.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{doctor-environment-worktree.test.ts → integration/doctor-environment-worktree.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-environment.test.ts → integration/doctor-environment.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-fixlevel.test.ts → integration/doctor-fixlevel.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{doctor-git.test.ts → integration/doctor-git.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-proactive.test.ts → integration/doctor-proactive.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-roadmap-summary-atomicity.test.ts → integration/doctor-roadmap-summary-atomicity.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor-runtime.test.ts → integration/doctor-runtime.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{doctor.test.ts → integration/doctor.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{e2e-workflow-pipeline-integration.test.ts → integration/e2e-workflow-pipeline-integration.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{feature-branch-lifecycle-integration.test.ts → integration/feature-branch-lifecycle-integration.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{git-locale.test.ts → integration/git-locale.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{git-self-heal.test.ts → integration/git-self-heal.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{git-service.test.ts → integration/git-service.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{gitignore-tracked-gsd.test.ts → integration/gitignore-tracked-gsd.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{idle-recovery.test.ts → integration/idle-recovery.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{inherited-repo-home-dir.test.ts → integration/inherited-repo-home-dir.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{integration-lifecycle.test.ts → integration/integration-lifecycle.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{integration-mixed-milestones.test.ts → integration/integration-mixed-milestones.test.ts} +6 -6
- package/src/resources/extensions/gsd/tests/{integration-proof.test.ts → integration/integration-proof.test.ts} +12 -12
- package/src/resources/extensions/gsd/tests/{migrate-command.test.ts → integration/migrate-command.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{milestone-transition-worktree.test.ts → integration/milestone-transition-worktree.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{parallel-merge.test.ts → integration/parallel-merge.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{parallel-workers-multi-milestone-e2e.test.ts → integration/parallel-workers-multi-milestone-e2e.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{paths.test.ts → integration/paths.test.ts} +1 -1
- package/src/resources/extensions/gsd/tests/{plugin-importer-live.test.ts → integration/plugin-importer-live.test.ts} +2 -2
- package/src/resources/extensions/gsd/tests/{queue-completed-milestone-perf.test.ts → integration/queue-completed-milestone-perf.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{queue-reorder-e2e.test.ts → integration/queue-reorder-e2e.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{quick-branch-lifecycle.test.ts → integration/quick-branch-lifecycle.test.ts} +5 -5
- package/src/resources/extensions/gsd/tests/{run-uat.test.ts → integration/run-uat.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/{token-savings.test.ts → integration/token-savings.test.ts} +3 -3
- package/src/resources/extensions/gsd/tests/{worktree-e2e.test.ts → integration/worktree-e2e.test.ts} +4 -4
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-queue-context.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +61 -19
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +49 -24
- package/src/resources/extensions/gsd/visualizer-overlay.ts +3 -3
- package/src/resources/extensions/shared/format-utils.ts +1 -1
- package/src/resources/extensions/subagent/worker-registry.ts +2 -1
- package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.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/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Parallel Monitor Overlay
|
|
3
|
+
*
|
|
4
|
+
* Full-screen TUI overlay showing real-time parallel worker progress.
|
|
5
|
+
* Opened via `/gsd parallel watch` or Ctrl+Alt+P.
|
|
6
|
+
* Reads the same data sources as `scripts/parallel-monitor.mjs` but
|
|
7
|
+
* renders as a native pi-tui overlay with theme integration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, statSync, readFileSync, openSync, readSync, closeSync, readdirSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
|
|
14
|
+
import type { Theme } from "@gsd/pi-coding-agent";
|
|
15
|
+
import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
16
|
+
|
|
17
|
+
import { formatDuration, STATUS_GLYPH, STATUS_COLOR } from "../shared/mod.js";
|
|
18
|
+
|
|
19
|
+
// ─── Types ────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
interface StatusJson {
|
|
22
|
+
milestoneId: string;
|
|
23
|
+
pid: number;
|
|
24
|
+
state: string;
|
|
25
|
+
cost: number;
|
|
26
|
+
lastHeartbeat: number;
|
|
27
|
+
startedAt: number;
|
|
28
|
+
worktreePath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AutoLock {
|
|
32
|
+
pid: number;
|
|
33
|
+
startedAt: string;
|
|
34
|
+
unitType: string;
|
|
35
|
+
unitId: string;
|
|
36
|
+
unitStartedAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SliceProgress {
|
|
40
|
+
id: string;
|
|
41
|
+
status: string;
|
|
42
|
+
total: number;
|
|
43
|
+
done: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface WorkerView {
|
|
47
|
+
mid: string;
|
|
48
|
+
pid: number;
|
|
49
|
+
alive: boolean;
|
|
50
|
+
state: string;
|
|
51
|
+
cost: number;
|
|
52
|
+
heartbeatAge: number;
|
|
53
|
+
currentUnit: string | null;
|
|
54
|
+
unitType: string | null;
|
|
55
|
+
unitElapsed: number;
|
|
56
|
+
elapsed: number;
|
|
57
|
+
totalTasks: number;
|
|
58
|
+
doneTasks: number;
|
|
59
|
+
totalSlices: number;
|
|
60
|
+
doneSlices: number;
|
|
61
|
+
slices: SliceProgress[];
|
|
62
|
+
errors: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Data Helpers ─────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
function readJsonSafe<T>(filePath: string): T | null {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(readFileSync(filePath, "utf-8")) as T;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isPidAlive(pid: number): boolean {
|
|
76
|
+
try {
|
|
77
|
+
process.kill(pid, 0);
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function tailRead(filePath: string, maxBytes: number): string {
|
|
85
|
+
try {
|
|
86
|
+
const stat = statSync(filePath);
|
|
87
|
+
const readSize = Math.min(stat.size, maxBytes);
|
|
88
|
+
const fd = openSync(filePath, "r");
|
|
89
|
+
const buf = Buffer.alloc(readSize);
|
|
90
|
+
readSync(fd, buf, 0, readSize, Math.max(0, stat.size - readSize));
|
|
91
|
+
closeSync(fd);
|
|
92
|
+
return buf.toString("utf-8");
|
|
93
|
+
} catch {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function discoverWorkers(basePath: string): string[] {
|
|
99
|
+
const parallelDir = join(basePath, ".gsd", "parallel");
|
|
100
|
+
const worktreeDir = join(basePath, ".gsd", "worktrees");
|
|
101
|
+
const mids = new Set<string>();
|
|
102
|
+
|
|
103
|
+
if (existsSync(parallelDir)) {
|
|
104
|
+
try {
|
|
105
|
+
for (const f of readdirSync(parallelDir)) {
|
|
106
|
+
if (f.endsWith(".status.json")) mids.add(f.replace(".status.json", ""));
|
|
107
|
+
const m = f.match(/^(M\d+)\.(stderr|stdout)\.log$/);
|
|
108
|
+
if (m) mids.add(m[1]);
|
|
109
|
+
}
|
|
110
|
+
} catch { /* skip */ }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (existsSync(worktreeDir)) {
|
|
114
|
+
try {
|
|
115
|
+
for (const d of readdirSync(worktreeDir)) {
|
|
116
|
+
if (d.startsWith("M") && existsSync(join(worktreeDir, d, ".gsd", "auto.lock"))) {
|
|
117
|
+
mids.add(d);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch { /* skip */ }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return [...mids].sort();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function querySliceProgress(basePath: string, mid: string): SliceProgress[] {
|
|
127
|
+
const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
|
|
128
|
+
if (!existsSync(dbPath)) return [];
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const sql = `SELECT s.id, s.status, COUNT(t.id), SUM(CASE WHEN t.status='complete' THEN 1 ELSE 0 END) FROM slices s LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id WHERE s.milestone_id='${mid}' GROUP BY s.id ORDER BY s.id`;
|
|
132
|
+
const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
|
|
133
|
+
const out = (result.stdout || "").trim();
|
|
134
|
+
if (!out || result.status !== 0) return [];
|
|
135
|
+
return out.split("\n").map((line) => {
|
|
136
|
+
const [id, status, total, done] = line.split("|");
|
|
137
|
+
return { id, status, total: parseInt(total, 10), done: parseInt(done || "0", 10) };
|
|
138
|
+
});
|
|
139
|
+
} catch {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function extractCostFromNdjson(basePath: string, mid: string): number {
|
|
145
|
+
const stdoutPath = join(basePath, ".gsd", "parallel", `${mid}.stdout.log`);
|
|
146
|
+
if (!existsSync(stdoutPath)) return 0;
|
|
147
|
+
try {
|
|
148
|
+
const content = readFileSync(stdoutPath, "utf-8");
|
|
149
|
+
let total = 0;
|
|
150
|
+
for (const line of content.split("\n")) {
|
|
151
|
+
if (!line.includes("message_end")) continue;
|
|
152
|
+
try {
|
|
153
|
+
const obj = JSON.parse(line);
|
|
154
|
+
if (obj.type === "message_end") {
|
|
155
|
+
const cost = obj.message?.usage?.cost?.total;
|
|
156
|
+
if (typeof cost === "number") total += cost;
|
|
157
|
+
}
|
|
158
|
+
} catch { /* skip */ }
|
|
159
|
+
}
|
|
160
|
+
return total;
|
|
161
|
+
} catch {
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function queryRecentCompletions(basePath: string, mid: string): string[] {
|
|
167
|
+
const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
|
|
168
|
+
if (!existsSync(dbPath)) return [];
|
|
169
|
+
try {
|
|
170
|
+
const sql = `SELECT id, slice_id, one_liner FROM tasks WHERE milestone_id='${mid}' AND status='complete' AND completed_at IS NOT NULL ORDER BY completed_at DESC LIMIT 5`;
|
|
171
|
+
const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
|
|
172
|
+
const out = (result.stdout || "").trim();
|
|
173
|
+
if (!out || result.status !== 0) return [];
|
|
174
|
+
return out.split("\n").map((line) => {
|
|
175
|
+
const [taskId, sliceId, oneLiner] = line.split("|");
|
|
176
|
+
return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
|
|
177
|
+
});
|
|
178
|
+
} catch {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function collectWorkerData(basePath: string): WorkerView[] {
|
|
184
|
+
const mids = discoverWorkers(basePath);
|
|
185
|
+
const parallelDir = join(basePath, ".gsd", "parallel");
|
|
186
|
+
const workers: WorkerView[] = [];
|
|
187
|
+
|
|
188
|
+
for (const mid of mids) {
|
|
189
|
+
const status = readJsonSafe<StatusJson>(join(parallelDir, `${mid}.status.json`));
|
|
190
|
+
const lock = readJsonSafe<AutoLock>(join(basePath, ".gsd", "worktrees", mid, ".gsd", "auto.lock"));
|
|
191
|
+
const slices = querySliceProgress(basePath, mid);
|
|
192
|
+
|
|
193
|
+
const pid = lock?.pid || status?.pid || 0;
|
|
194
|
+
const alive = pid ? isPidAlive(pid) : false;
|
|
195
|
+
|
|
196
|
+
// Heartbeat: prefer status.json if PID matches, else use file mtime
|
|
197
|
+
let heartbeatAge = Infinity;
|
|
198
|
+
const statusPidMatches = status?.pid === pid && status?.lastHeartbeat;
|
|
199
|
+
if (statusPidMatches) {
|
|
200
|
+
heartbeatAge = Date.now() - status!.lastHeartbeat;
|
|
201
|
+
} else {
|
|
202
|
+
const mtimes: number[] = [];
|
|
203
|
+
const stdoutLog = join(parallelDir, `${mid}.stdout.log`);
|
|
204
|
+
const stderrLog = join(parallelDir, `${mid}.stderr.log`);
|
|
205
|
+
if (existsSync(stdoutLog)) mtimes.push(statSync(stdoutLog).mtimeMs);
|
|
206
|
+
if (existsSync(stderrLog)) mtimes.push(statSync(stderrLog).mtimeMs);
|
|
207
|
+
if (lock?.unitStartedAt) mtimes.push(new Date(lock.unitStartedAt).getTime());
|
|
208
|
+
if (mtimes.length > 0) heartbeatAge = Date.now() - Math.max(...mtimes);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let cost = status?.cost || 0;
|
|
212
|
+
if (cost === 0) cost = extractCostFromNdjson(basePath, mid);
|
|
213
|
+
|
|
214
|
+
const totalTasks = slices.reduce((sum, s) => sum + s.total, 0);
|
|
215
|
+
const doneTasks = slices.reduce((sum, s) => sum + s.done, 0);
|
|
216
|
+
const doneSlices = slices.filter((s) => s.status === "complete").length;
|
|
217
|
+
|
|
218
|
+
const elapsed = status?.startedAt
|
|
219
|
+
? Date.now() - status.startedAt
|
|
220
|
+
: lock?.startedAt
|
|
221
|
+
? Date.now() - new Date(lock.startedAt).getTime()
|
|
222
|
+
: 0;
|
|
223
|
+
|
|
224
|
+
// Errors from stderr (last 4KB, only new content)
|
|
225
|
+
const errors: string[] = [];
|
|
226
|
+
const stderrLog = join(parallelDir, `${mid}.stderr.log`);
|
|
227
|
+
if (existsSync(stderrLog)) {
|
|
228
|
+
const content = tailRead(stderrLog, 4096);
|
|
229
|
+
for (const line of content.trim().split("\n").slice(-5)) {
|
|
230
|
+
if (line.includes("error") || line.includes("Error") || line.includes("exited")) {
|
|
231
|
+
errors.push(line.trim());
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
workers.push({
|
|
237
|
+
mid,
|
|
238
|
+
pid,
|
|
239
|
+
alive,
|
|
240
|
+
state: alive ? "running" : (status?.state || "dead"),
|
|
241
|
+
cost,
|
|
242
|
+
heartbeatAge,
|
|
243
|
+
currentUnit: lock?.unitId || null,
|
|
244
|
+
unitType: lock?.unitType || null,
|
|
245
|
+
unitElapsed: lock?.unitStartedAt ? Date.now() - new Date(lock.unitStartedAt).getTime() : 0,
|
|
246
|
+
elapsed,
|
|
247
|
+
totalTasks,
|
|
248
|
+
doneTasks,
|
|
249
|
+
totalSlices: slices.length,
|
|
250
|
+
doneSlices,
|
|
251
|
+
slices,
|
|
252
|
+
errors,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return workers;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── Rendering Helpers ────────────────────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
function unitTypeLabel(unitType: string | null): string {
|
|
262
|
+
const labels: Record<string, string> = {
|
|
263
|
+
"execute-task": "EXEC",
|
|
264
|
+
"research-slice": "RSRCH",
|
|
265
|
+
"plan-slice": "PLAN",
|
|
266
|
+
"complete-slice": "DONE",
|
|
267
|
+
"complete-task": "DONE",
|
|
268
|
+
"reassess": "ASSESS",
|
|
269
|
+
"validate": "VALID",
|
|
270
|
+
"reassess-roadmap": "ASSESS",
|
|
271
|
+
};
|
|
272
|
+
return labels[unitType || ""] || (unitType || "---").toUpperCase().slice(0, 5);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function progressBar(done: number, total: number, width: number): string {
|
|
276
|
+
if (total === 0) return "░".repeat(width);
|
|
277
|
+
const filled = Math.round((done / total) * width);
|
|
278
|
+
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function healthGlyph(alive: boolean, heartbeatAge: number): string {
|
|
282
|
+
if (!alive) return "○";
|
|
283
|
+
return "●";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ─── Overlay Class ────────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
export class ParallelMonitorOverlay {
|
|
289
|
+
private tui: { requestRender: () => void };
|
|
290
|
+
private theme: Theme;
|
|
291
|
+
private onClose: () => void;
|
|
292
|
+
private basePath: string;
|
|
293
|
+
private refreshTimer: ReturnType<typeof setInterval>;
|
|
294
|
+
private workers: WorkerView[] = [];
|
|
295
|
+
private events: string[] = [];
|
|
296
|
+
private cachedLines?: string[];
|
|
297
|
+
private scrollOffset = 0;
|
|
298
|
+
private disposed = false;
|
|
299
|
+
private resizeHandler: (() => void) | null = null;
|
|
300
|
+
|
|
301
|
+
constructor(
|
|
302
|
+
tui: { requestRender: () => void },
|
|
303
|
+
theme: Theme,
|
|
304
|
+
onClose: () => void,
|
|
305
|
+
basePath?: string,
|
|
306
|
+
) {
|
|
307
|
+
this.tui = tui;
|
|
308
|
+
this.theme = theme;
|
|
309
|
+
this.onClose = onClose;
|
|
310
|
+
this.basePath = basePath || process.cwd();
|
|
311
|
+
|
|
312
|
+
this.resizeHandler = () => {
|
|
313
|
+
if (this.disposed) return;
|
|
314
|
+
this.invalidate();
|
|
315
|
+
this.tui.requestRender();
|
|
316
|
+
};
|
|
317
|
+
process.stdout.on("resize", this.resizeHandler);
|
|
318
|
+
|
|
319
|
+
this.refresh();
|
|
320
|
+
this.refreshTimer = setInterval(() => this.refresh(), 5000);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private refresh(): void {
|
|
324
|
+
if (this.disposed) return;
|
|
325
|
+
this.workers = collectWorkerData(this.basePath);
|
|
326
|
+
|
|
327
|
+
// Collect completion events
|
|
328
|
+
for (const wk of this.workers) {
|
|
329
|
+
const completions = queryRecentCompletions(this.basePath, wk.mid);
|
|
330
|
+
for (const evt of completions) {
|
|
331
|
+
if (!this.events.includes(evt)) this.events.push(evt);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
this.events = this.events.slice(-10);
|
|
335
|
+
|
|
336
|
+
this.cachedLines = undefined;
|
|
337
|
+
this.tui.requestRender();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
dispose(): void {
|
|
341
|
+
this.disposed = true;
|
|
342
|
+
clearInterval(this.refreshTimer);
|
|
343
|
+
if (this.resizeHandler) {
|
|
344
|
+
process.stdout.removeListener("resize", this.resizeHandler);
|
|
345
|
+
this.resizeHandler = null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
handleInput(data: string): void {
|
|
350
|
+
if (matchesKey(data, Key.escape) || data === "q") {
|
|
351
|
+
this.dispose();
|
|
352
|
+
this.onClose();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (matchesKey(data, Key.down) || data === "j") {
|
|
356
|
+
this.scrollOffset++;
|
|
357
|
+
this.invalidate();
|
|
358
|
+
this.tui.requestRender();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (matchesKey(data, Key.up) || data === "k") {
|
|
362
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
363
|
+
this.invalidate();
|
|
364
|
+
this.tui.requestRender();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
invalidate(): void {
|
|
370
|
+
this.cachedLines = undefined;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
render(width: number): string[] {
|
|
374
|
+
if (this.cachedLines) return this.cachedLines;
|
|
375
|
+
|
|
376
|
+
const t = this.theme;
|
|
377
|
+
const lines: string[] = [];
|
|
378
|
+
const w = Math.max(width, 60);
|
|
379
|
+
|
|
380
|
+
// Header
|
|
381
|
+
const totalCost = this.workers.reduce((s, wk) => s + wk.cost, 0);
|
|
382
|
+
const aliveCount = this.workers.filter((wk) => wk.alive).length;
|
|
383
|
+
const now = new Date().toLocaleTimeString();
|
|
384
|
+
|
|
385
|
+
lines.push(t.bold(t.fg("accent", " GSD Parallel Monitor ")));
|
|
386
|
+
lines.push(
|
|
387
|
+
t.fg("muted", ` ${now} │ ${aliveCount}/${this.workers.length} alive │ Total: `) +
|
|
388
|
+
t.bold(`$${totalCost.toFixed(2)}`) +
|
|
389
|
+
t.fg("muted", " │ 5s refresh"),
|
|
390
|
+
);
|
|
391
|
+
lines.push(t.fg("muted", "─".repeat(w)));
|
|
392
|
+
|
|
393
|
+
if (this.workers.length === 0) {
|
|
394
|
+
lines.push("");
|
|
395
|
+
lines.push(t.fg("warning", " No parallel workers found."));
|
|
396
|
+
lines.push(t.fg("muted", " Run /gsd parallel start to begin."));
|
|
397
|
+
} else {
|
|
398
|
+
for (const wk of this.workers) {
|
|
399
|
+
lines.push("");
|
|
400
|
+
|
|
401
|
+
// Health + ID + state
|
|
402
|
+
const healthColor = wk.alive ? "success" : "error";
|
|
403
|
+
const glyph = healthGlyph(wk.alive, wk.heartbeatAge);
|
|
404
|
+
const stateText = wk.alive
|
|
405
|
+
? t.fg("success", "RUNNING")
|
|
406
|
+
: t.fg("error", t.bold("DEAD"));
|
|
407
|
+
const heartbeatText = wk.heartbeatAge === Infinity
|
|
408
|
+
? "never"
|
|
409
|
+
: formatDuration(wk.heartbeatAge) + " ago";
|
|
410
|
+
|
|
411
|
+
lines.push(
|
|
412
|
+
` ${t.fg(healthColor, glyph)} ${t.bold(wk.mid)} ${stateText} ` +
|
|
413
|
+
t.fg("muted", `PID ${wk.pid} │ elapsed ${formatDuration(wk.elapsed)} │ `) +
|
|
414
|
+
`cost ${t.bold("$" + wk.cost.toFixed(2))} ` +
|
|
415
|
+
t.fg("muted", "│ heartbeat ") + t.fg(healthColor, heartbeatText),
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
// Current unit
|
|
419
|
+
if (wk.currentUnit) {
|
|
420
|
+
const phaseColor =
|
|
421
|
+
wk.unitType === "execute-task" ? "accent"
|
|
422
|
+
: wk.unitType === "research-slice" ? "warning"
|
|
423
|
+
: wk.unitType?.includes("complete") ? "success"
|
|
424
|
+
: "text";
|
|
425
|
+
lines.push(
|
|
426
|
+
` ${t.fg("muted", "▸")} ${t.fg(phaseColor, unitTypeLabel(wk.unitType))} ${wk.currentUnit} ` +
|
|
427
|
+
t.fg("muted", `(${formatDuration(wk.unitElapsed)})`),
|
|
428
|
+
);
|
|
429
|
+
} else if (!wk.alive) {
|
|
430
|
+
lines.push(` ${t.fg("muted", "▸")} ${t.fg("error", "stopped")}`);
|
|
431
|
+
} else {
|
|
432
|
+
lines.push(` ${t.fg("muted", "▸ idle / between units")}`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Slice progress chips
|
|
436
|
+
if (wk.slices.length > 0) {
|
|
437
|
+
const chips = wk.slices.map((s) => {
|
|
438
|
+
const pct = s.total > 0 ? s.done / s.total : 0;
|
|
439
|
+
const color = s.status === "complete" ? "success" : pct > 0 ? "warning" : "muted";
|
|
440
|
+
return t.fg(color, `${s.id}:${s.done}/${s.total}`);
|
|
441
|
+
});
|
|
442
|
+
lines.push(` ${t.fg("muted", "slices")} ${chips.join(" ")}`);
|
|
443
|
+
|
|
444
|
+
// Task progress bar
|
|
445
|
+
const bar = progressBar(wk.doneTasks, wk.totalTasks, 25);
|
|
446
|
+
const pct = wk.totalTasks > 0 ? Math.round((wk.doneTasks / wk.totalTasks) * 100) : 0;
|
|
447
|
+
lines.push(
|
|
448
|
+
` ${t.fg("muted", "tasks")} ${t.fg("success", bar)} ${wk.doneTasks}/${wk.totalTasks} ` +
|
|
449
|
+
t.fg("muted", `(${pct}%) │ slices done ${wk.doneSlices}/${wk.totalSlices}`),
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Errors
|
|
454
|
+
for (const err of wk.errors.slice(-2)) {
|
|
455
|
+
const truncated = err.length > w - 10 ? err.slice(0, w - 11) + "…" : err;
|
|
456
|
+
lines.push(` ${t.fg("error", "⚠ " + truncated)}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Event feed
|
|
462
|
+
lines.push("");
|
|
463
|
+
lines.push(t.fg("muted", "─".repeat(w)));
|
|
464
|
+
lines.push(` ${t.bold("Recent Events")}`);
|
|
465
|
+
|
|
466
|
+
if (this.events.length === 0) {
|
|
467
|
+
lines.push(t.fg("muted", " No events yet..."));
|
|
468
|
+
} else {
|
|
469
|
+
for (const evt of this.events.slice(-8)) {
|
|
470
|
+
const mid = evt.match(/^✓ (M\d+)\//)?.[1] || "";
|
|
471
|
+
const truncated = evt.length > w - 10 ? evt.slice(0, w - 11) + "…" : evt;
|
|
472
|
+
lines.push(` ${t.fg("muted", "│")} ${t.fg("accent", mid)} ${truncated.replace(/^✓ M\d+\//, "")}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Footer
|
|
477
|
+
lines.push("");
|
|
478
|
+
const allDone = this.workers.length > 0 && this.workers.every((wk) => !wk.alive);
|
|
479
|
+
if (allDone) {
|
|
480
|
+
lines.push(t.bold(t.fg("success", " ALL WORKERS COMPLETE")));
|
|
481
|
+
for (const wk of this.workers) {
|
|
482
|
+
lines.push(
|
|
483
|
+
` ${wk.mid} $${wk.cost.toFixed(2)} │ ${wk.doneSlices}/${wk.totalSlices} slices ` +
|
|
484
|
+
`${wk.doneTasks}/${wk.totalTasks} tasks │ ${formatDuration(wk.elapsed)}`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
|
|
488
|
+
}
|
|
489
|
+
lines.push(t.fg("muted", " ESC/q to close │ ↑↓ scroll"));
|
|
490
|
+
|
|
491
|
+
// Apply scroll — use terminal rows as height estimate
|
|
492
|
+
const termHeight = process.stdout.rows || 40;
|
|
493
|
+
const visible = lines.slice(this.scrollOffset, this.scrollOffset + termHeight);
|
|
494
|
+
this.cachedLines = visible;
|
|
495
|
+
return visible;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
@@ -21,7 +21,7 @@ import { join, dirname } from "node:path";
|
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
22
|
import { gsdRoot } from "./paths.js";
|
|
23
23
|
import { createWorktree, worktreePath } from "./worktree-manager.js";
|
|
24
|
-
import { autoWorktreeBranch, runWorktreePostCreateHook } from "./auto-worktree.js";
|
|
24
|
+
import { autoWorktreeBranch, runWorktreePostCreateHook, syncGsdStateToWorktree } from "./auto-worktree.js";
|
|
25
25
|
import { nativeBranchExists } from "./native-git-bridge.js";
|
|
26
26
|
import { readIntegrationBranch } from "./git-service.js";
|
|
27
27
|
import { resolveParallelConfig } from "./preferences.js";
|
|
@@ -507,6 +507,11 @@ function createMilestoneWorktree(basePath: string, milestoneId: string): string
|
|
|
507
507
|
// Run post-create hook if configured
|
|
508
508
|
runWorktreePostCreateHook(basePath, info.path);
|
|
509
509
|
|
|
510
|
+
// Copy .gsd/ planning artifacts (milestones, CONTEXT, ROADMAP, etc.) from the
|
|
511
|
+
// project root into the worktree. Without this, workers for newly-planned
|
|
512
|
+
// milestones can't find their roadmap and exit immediately (#2184 Bug 4).
|
|
513
|
+
syncGsdStateToWorktree(basePath, info.path);
|
|
514
|
+
|
|
510
515
|
return info.path;
|
|
511
516
|
}
|
|
512
517
|
|
|
@@ -83,10 +83,31 @@ let _lockAcquiredAt: number = 0;
|
|
|
83
83
|
|
|
84
84
|
const LOCK_FILE = "auto.lock";
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Derive the effective lock file name for the current process.
|
|
88
|
+
* In parallel worker mode (GSD_PARALLEL_WORKER + GSD_MILESTONE_LOCK),
|
|
89
|
+
* each worker uses a per-milestone lock file (`auto-<milestoneId>.lock`)
|
|
90
|
+
* to avoid contending on the shared `.gsd/auto.lock` (#2184).
|
|
91
|
+
*/
|
|
92
|
+
export function effectiveLockFile(): string {
|
|
93
|
+
const mid = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : null;
|
|
94
|
+
return mid ? `auto-${mid}.lock` : LOCK_FILE;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Derive the OS-level lock target directory for the current process.
|
|
99
|
+
* In parallel worker mode, uses `.gsd/parallel/<milestoneId>/` instead of
|
|
100
|
+
* `.gsd/` so workers don't contend on the same proper-lockfile directory (#2184).
|
|
101
|
+
*/
|
|
102
|
+
export function effectiveLockTarget(gsdDir: string): string {
|
|
103
|
+
const mid = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : null;
|
|
104
|
+
return mid ? join(gsdDir, "parallel", mid) : gsdDir;
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
function lockPath(basePath: string): string {
|
|
87
108
|
// If we have a snapshotted path from acquisition, use it for consistency
|
|
88
109
|
if (_snapshotLockPath) return _snapshotLockPath;
|
|
89
|
-
return join(gsdRoot(basePath),
|
|
110
|
+
return join(gsdRoot(basePath), effectiveLockFile());
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
// ─── Stray Lock Cleanup ─────────────────────────────────────────────────────
|
|
@@ -265,14 +286,16 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
265
286
|
}
|
|
266
287
|
|
|
267
288
|
const gsdDir = gsdRoot(basePath);
|
|
289
|
+
const lockTarget = effectiveLockTarget(gsdDir);
|
|
268
290
|
|
|
269
291
|
try {
|
|
270
|
-
// Try to acquire an exclusive OS-level lock on the lock
|
|
271
|
-
// We lock
|
|
272
|
-
//
|
|
273
|
-
|
|
292
|
+
// Try to acquire an exclusive OS-level lock on the lock target.
|
|
293
|
+
// We lock a directory since proper-lockfile works best on directories,
|
|
294
|
+
// and the lock file itself may not exist yet.
|
|
295
|
+
// In parallel worker mode, lockTarget is .gsd/parallel/<MID>/ (#2184).
|
|
296
|
+
mkdirSync(lockTarget, { recursive: true });
|
|
274
297
|
|
|
275
|
-
const release = lockfile.lockSync(
|
|
298
|
+
const release = lockfile.lockSync(lockTarget, {
|
|
276
299
|
realpath: false,
|
|
277
300
|
stale: 1_800_000, // 30 minutes — safe for laptop sleep / long event loop stalls
|
|
278
301
|
update: 10_000, // Update lock mtime every 10s to prove liveness
|
|
@@ -283,7 +306,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
283
306
|
|
|
284
307
|
// Safety net: clean up lock dir on process exit if _releaseFunction
|
|
285
308
|
// wasn't called (e.g., normal exit after clean completion) (#1245).
|
|
286
|
-
ensureExitHandler(
|
|
309
|
+
ensureExitHandler(lockTarget);
|
|
287
310
|
|
|
288
311
|
// Write the informational lock data
|
|
289
312
|
atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
|
|
@@ -298,12 +321,12 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
298
321
|
// If no lock file or no alive process, try to clean up and re-acquire (#1245)
|
|
299
322
|
if (!existingData || (existingPid && !isPidAlive(existingPid))) {
|
|
300
323
|
try {
|
|
301
|
-
const lockDir = join(
|
|
324
|
+
const lockDir = join(lockTarget + ".lock");
|
|
302
325
|
if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
|
|
303
326
|
if (existsSync(lp)) unlinkSync(lp);
|
|
304
327
|
|
|
305
328
|
// Retry acquisition after cleanup
|
|
306
|
-
const release = lockfile.lockSync(
|
|
329
|
+
const release = lockfile.lockSync(lockTarget, {
|
|
307
330
|
realpath: false,
|
|
308
331
|
stale: 1_800_000, // 30 minutes — match primary lock settings
|
|
309
332
|
update: 10_000,
|
|
@@ -312,7 +335,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
312
335
|
assignLockState(basePath, release, lp);
|
|
313
336
|
|
|
314
337
|
// Safety net — uses centralized handler to avoid double-registration
|
|
315
|
-
ensureExitHandler(
|
|
338
|
+
ensureExitHandler(lockTarget);
|
|
316
339
|
|
|
317
340
|
atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
|
|
318
341
|
return { acquired: true };
|
|
@@ -483,13 +506,24 @@ export function releaseSessionLock(basePath: string): void {
|
|
|
483
506
|
// Non-fatal
|
|
484
507
|
}
|
|
485
508
|
|
|
486
|
-
// Remove the proper-lockfile directory
|
|
509
|
+
// Remove the proper-lockfile directory for the current lock target.
|
|
510
|
+
// In parallel worker mode, this is .gsd/parallel/<MID>.lock/ (#2184).
|
|
511
|
+
const gsdDir = gsdRoot(basePath);
|
|
512
|
+
const lockTarget = effectiveLockTarget(gsdDir);
|
|
487
513
|
try {
|
|
488
|
-
const lockDir = join(
|
|
514
|
+
const lockDir = join(lockTarget + ".lock");
|
|
489
515
|
if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
|
|
490
516
|
} catch {
|
|
491
517
|
// Non-fatal
|
|
492
518
|
}
|
|
519
|
+
// Also clean the per-milestone parallel directory itself if it exists
|
|
520
|
+
if (lockTarget !== gsdDir) {
|
|
521
|
+
try {
|
|
522
|
+
if (existsSync(lockTarget)) rmSync(lockTarget, { recursive: true, force: true });
|
|
523
|
+
} catch {
|
|
524
|
+
// Non-fatal
|
|
525
|
+
}
|
|
526
|
+
}
|
|
493
527
|
|
|
494
528
|
// Clean ALL registered lock paths (#1578) — lock files accumulate across
|
|
495
529
|
// main project .gsd/, worktree .gsd/, and projects registry paths.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* research identified as critical for skill quality.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
16
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
19
|
import type { UnitMetrics, MetricsLedger } from "./metrics.js";
|
|
@@ -210,7 +210,7 @@ export function formatSkillDetail(basePath: string, skillName: string): string {
|
|
|
210
210
|
// Check for SKILL.md existence
|
|
211
211
|
const skillPath = join(homedir(), ".agents", "skills", skillName, "SKILL.md");
|
|
212
212
|
if (existsSync(skillPath)) {
|
|
213
|
-
const stat =
|
|
213
|
+
const stat = statSync(skillPath);
|
|
214
214
|
lines.push("");
|
|
215
215
|
lines.push(`SKILL.md: ${skillPath}`);
|
|
216
216
|
lines.push(`Last modified: ${stat.mtime.toISOString().slice(0, 10)}`);
|