gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.d8826a445
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/headless-recover.d.ts +23 -0
- package/dist/headless-recover.js +93 -0
- package/dist/headless.js +9 -0
- package/dist/help-text.js +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
- package/dist/resources/extensions/gsd/auto/phases.js +7 -2
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +7 -58
- package/dist/resources/extensions/gsd/auto-post-unit.js +14 -28
- package/dist/resources/extensions/gsd/auto-start.js +1 -8
- package/dist/resources/extensions/gsd/auto-worktree.js +244 -216
- package/dist/resources/extensions/gsd/auto.js +86 -7
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
- package/dist/resources/extensions/gsd/commands-logs.js +2 -2
- package/dist/resources/extensions/gsd/commands-scan.js +2 -2
- package/dist/resources/extensions/gsd/commands-ship.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
- package/dist/resources/extensions/gsd/db-writer.js +106 -95
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
- package/dist/resources/extensions/gsd/gsd-db.js +268 -8
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +141 -32
- package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
- package/dist/resources/extensions/gsd/paths.js +114 -9
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/dist/resources/extensions/gsd/queue-order.js +6 -1
- package/dist/resources/extensions/gsd/rethink.js +2 -2
- package/dist/resources/extensions/gsd/state.js +91 -372
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
- package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-command.js +4 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +56 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +62 -3
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
- package/src/resources/extensions/gsd/auto/phases.ts +8 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +14 -62
- package/src/resources/extensions/gsd/auto-post-unit.ts +15 -27
- package/src/resources/extensions/gsd/auto-start.ts +1 -8
- package/src/resources/extensions/gsd/auto-worktree.ts +286 -251
- package/src/resources/extensions/gsd/auto.ts +102 -7
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
- package/src/resources/extensions/gsd/commands-logs.ts +2 -2
- package/src/resources/extensions/gsd/commands-scan.ts +2 -2
- package/src/resources/extensions/gsd/commands-ship.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
- package/src/resources/extensions/gsd/db-writer.ts +123 -94
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
- package/src/resources/extensions/gsd/gsd-db.ts +269 -8
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +181 -32
- package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
- package/src/resources/extensions/gsd/paths.ts +122 -9
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/src/resources/extensions/gsd/queue-order.ts +6 -1
- package/src/resources/extensions/gsd/rethink.ts +2 -2
- package/src/resources/extensions/gsd/state.ts +91 -389
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
- package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +26 -3
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
- package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
- package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-command.ts +4 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// GSD-2 + metrics.ts: token & cost tracking for auto-mode units
|
|
1
2
|
/**
|
|
2
3
|
* GSD Metrics — Token & Cost Tracking
|
|
3
4
|
*
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import { join } from "node:path";
|
|
18
|
+
import { openSync, closeSync, unlinkSync, statSync, writeFileSync } from "node:fs";
|
|
17
19
|
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
18
20
|
import { gsdRoot } from "./paths.js";
|
|
19
21
|
import { getAndClearSkills } from "./skill-telemetry.js";
|
|
@@ -21,6 +23,8 @@ import { loadJsonFile, loadJsonFileOrNull, saveJsonFile } from "./json-persisten
|
|
|
21
23
|
import { parseUnitId } from "./unit-id.js";
|
|
22
24
|
import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js";
|
|
23
25
|
import { isUnifiedAuditEnabled } from "./uok/audit-toggle.js";
|
|
26
|
+
import type { MilestoneScope } from "./workspace.js";
|
|
27
|
+
import { logWarning } from "./workflow-logger.js";
|
|
24
28
|
|
|
25
29
|
// Re-export from shared — import directly from format-utils to avoid pulling
|
|
26
30
|
// in the full barrel (mod.js → ui.js → @gsd/pi-tui) which breaks when loaded
|
|
@@ -108,11 +112,17 @@ export function classifyUnitPhase(unitType: string): MetricsPhase {
|
|
|
108
112
|
let ledger: MetricsLedger | null = null;
|
|
109
113
|
let basePath: string = "";
|
|
110
114
|
|
|
115
|
+
// Per-workspace ledger map, keyed by workspace.identityKey.
|
|
116
|
+
// Populated by initMetricsByScope; independent of the module singleton.
|
|
117
|
+
const scopedLedgers = new Map<string, MetricsLedger>();
|
|
118
|
+
|
|
111
119
|
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
112
120
|
|
|
113
121
|
/**
|
|
114
122
|
* Initialize the metrics system for a given project.
|
|
115
123
|
* Loads existing ledger from disk if present.
|
|
124
|
+
*
|
|
125
|
+
* @deprecated TODO(C-future): remove module singleton. Use initMetricsByScope instead.
|
|
116
126
|
*/
|
|
117
127
|
export function initMetrics(base: string): void {
|
|
118
128
|
basePath = base;
|
|
@@ -121,6 +131,8 @@ export function initMetrics(base: string): void {
|
|
|
121
131
|
|
|
122
132
|
/**
|
|
123
133
|
* Reset in-memory state. Called when auto-mode stops.
|
|
134
|
+
*
|
|
135
|
+
* @deprecated TODO(C-future): remove module singleton. Use resetMetricsByScope instead.
|
|
124
136
|
*/
|
|
125
137
|
export function resetMetrics(): void {
|
|
126
138
|
ledger = null;
|
|
@@ -130,6 +142,8 @@ export function resetMetrics(): void {
|
|
|
130
142
|
/**
|
|
131
143
|
* Snapshot usage metrics from the current session before it's wiped.
|
|
132
144
|
* Scans session entries for AssistantMessage usage data.
|
|
145
|
+
*
|
|
146
|
+
* @deprecated TODO(C-future): remove module singleton. Use snapshotUnitMetricsByScope instead.
|
|
133
147
|
*/
|
|
134
148
|
export function snapshotUnitMetrics(
|
|
135
149
|
ctx: ExtensionContext,
|
|
@@ -272,6 +286,182 @@ export function getLedger(): MetricsLedger | null {
|
|
|
272
286
|
return ledger;
|
|
273
287
|
}
|
|
274
288
|
|
|
289
|
+
// ─── Scope-aware API (canonical) ─────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Initialize the metrics system for a given workspace scope.
|
|
293
|
+
* Loads existing ledger from disk into the per-scope ledger map.
|
|
294
|
+
* Does NOT touch the module-level singleton.
|
|
295
|
+
*/
|
|
296
|
+
export function initMetricsByScope(scope: MilestoneScope): void {
|
|
297
|
+
const base = scope.workspace.projectRoot;
|
|
298
|
+
const loaded = loadLedger(base);
|
|
299
|
+
scopedLedgers.set(scope.workspace.identityKey, loaded);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get the in-memory ledger for the given scope, or null if not initialized.
|
|
304
|
+
*/
|
|
305
|
+
export function getLedgerByScope(scope: MilestoneScope): MetricsLedger | null {
|
|
306
|
+
return scopedLedgers.get(scope.workspace.identityKey) ?? null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Reset scoped in-memory state for a workspace. Called when auto-mode stops.
|
|
311
|
+
*/
|
|
312
|
+
export function resetMetricsByScope(scope: MilestoneScope): void {
|
|
313
|
+
scopedLedgers.delete(scope.workspace.identityKey);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Snapshot usage metrics using an explicit workspace scope.
|
|
318
|
+
*
|
|
319
|
+
* This is the canonical variant. It derives the metrics path from
|
|
320
|
+
* scope.workspace.projectRoot rather than the module singleton, so it
|
|
321
|
+
* remains correct across session resume and in multi-workspace processes.
|
|
322
|
+
*
|
|
323
|
+
* Preserves the atomic write-merge logic from saveLedger so concurrent
|
|
324
|
+
* workers cannot silently discard each other's entries.
|
|
325
|
+
*
|
|
326
|
+
* If initMetricsByScope has not been called, the ledger is loaded from
|
|
327
|
+
* disk on first call (lazy init).
|
|
328
|
+
*/
|
|
329
|
+
export function snapshotUnitMetricsByScope(
|
|
330
|
+
scope: MilestoneScope,
|
|
331
|
+
ctx: ExtensionContext,
|
|
332
|
+
unitType: string,
|
|
333
|
+
unitId: string,
|
|
334
|
+
startedAt: number,
|
|
335
|
+
model: string,
|
|
336
|
+
opts?: {
|
|
337
|
+
tier?: string;
|
|
338
|
+
modelDowngraded?: boolean;
|
|
339
|
+
contextWindowTokens?: number;
|
|
340
|
+
truncationSections?: number;
|
|
341
|
+
continueHereFired?: boolean;
|
|
342
|
+
promptCharCount?: number;
|
|
343
|
+
baselineCharCount?: number;
|
|
344
|
+
autoSessionKey?: string;
|
|
345
|
+
traceId?: string;
|
|
346
|
+
turnId?: string;
|
|
347
|
+
causedBy?: string;
|
|
348
|
+
},
|
|
349
|
+
): UnitMetrics | null {
|
|
350
|
+
const base = scope.workspace.projectRoot;
|
|
351
|
+
const key = scope.workspace.identityKey;
|
|
352
|
+
|
|
353
|
+
// Lazy init: load from disk if not yet in scoped map.
|
|
354
|
+
if (!scopedLedgers.has(key)) {
|
|
355
|
+
scopedLedgers.set(key, loadLedger(base));
|
|
356
|
+
}
|
|
357
|
+
const scopedLedger = scopedLedgers.get(key)!;
|
|
358
|
+
|
|
359
|
+
const entries = ctx.sessionManager.getEntries();
|
|
360
|
+
if (!entries || entries.length === 0) return null;
|
|
361
|
+
|
|
362
|
+
const tokens: TokenCounts = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
|
|
363
|
+
let cost = 0;
|
|
364
|
+
let toolCalls = 0;
|
|
365
|
+
let assistantMessages = 0;
|
|
366
|
+
let userMessages = 0;
|
|
367
|
+
|
|
368
|
+
for (const entry of entries) {
|
|
369
|
+
if (entry.type !== "message") continue;
|
|
370
|
+
const msg = (entry as any).message;
|
|
371
|
+
if (!msg) continue;
|
|
372
|
+
|
|
373
|
+
if (msg.role === "assistant") {
|
|
374
|
+
assistantMessages++;
|
|
375
|
+
if (msg.usage) {
|
|
376
|
+
tokens.input += msg.usage.input ?? 0;
|
|
377
|
+
tokens.output += msg.usage.output ?? 0;
|
|
378
|
+
tokens.cacheRead += msg.usage.cacheRead ?? 0;
|
|
379
|
+
tokens.cacheWrite += msg.usage.cacheWrite ?? 0;
|
|
380
|
+
tokens.total += msg.usage.totalTokens ?? 0;
|
|
381
|
+
if (msg.usage.cost != null) {
|
|
382
|
+
const c = msg.usage.cost;
|
|
383
|
+
cost += typeof c === "number" ? c : (c.total ?? 0);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
387
|
+
for (const block of msg.content) {
|
|
388
|
+
if (block.type === "toolCall") toolCalls++;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
} else if (msg.role === "user") {
|
|
392
|
+
userMessages++;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const unit: UnitMetrics = {
|
|
397
|
+
type: unitType,
|
|
398
|
+
id: unitId,
|
|
399
|
+
model,
|
|
400
|
+
startedAt,
|
|
401
|
+
finishedAt: Date.now(),
|
|
402
|
+
...(opts?.autoSessionKey ? { autoSessionKey: opts.autoSessionKey } : {}),
|
|
403
|
+
tokens,
|
|
404
|
+
cost,
|
|
405
|
+
toolCalls,
|
|
406
|
+
assistantMessages,
|
|
407
|
+
userMessages,
|
|
408
|
+
apiRequests: assistantMessages,
|
|
409
|
+
...(opts?.tier ? { tier: opts.tier } : {}),
|
|
410
|
+
...(opts?.modelDowngraded !== undefined ? { modelDowngraded: opts.modelDowngraded } : {}),
|
|
411
|
+
...(opts?.contextWindowTokens !== undefined ? { contextWindowTokens: opts.contextWindowTokens } : {}),
|
|
412
|
+
...(opts?.truncationSections !== undefined ? { truncationSections: opts.truncationSections } : {}),
|
|
413
|
+
...(opts?.continueHereFired !== undefined ? { continueHereFired: opts.continueHereFired } : {}),
|
|
414
|
+
...(opts?.promptCharCount != null ? { promptCharCount: opts.promptCharCount } : {}),
|
|
415
|
+
...(opts?.baselineCharCount != null ? { baselineCharCount: opts.baselineCharCount } : {}),
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// Auto-capture skill telemetry (#599)
|
|
419
|
+
const skills = getAndClearSkills();
|
|
420
|
+
if (skills.length > 0) {
|
|
421
|
+
unit.skills = skills;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Compute cache hit rate
|
|
425
|
+
if (tokens.cacheRead > 0 || tokens.input > 0) {
|
|
426
|
+
const totalInput = tokens.cacheRead + tokens.input;
|
|
427
|
+
unit.cacheHitRate = totalInput > 0 ? Math.round((tokens.cacheRead / totalInput) * 100) : 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Idempotency guard: update in-place on duplicate, append otherwise.
|
|
431
|
+
const dupeIdx = scopedLedger.units.findIndex(
|
|
432
|
+
(u) => u.type === unit.type && u.id === unit.id && u.startedAt === unit.startedAt,
|
|
433
|
+
);
|
|
434
|
+
if (dupeIdx >= 0) {
|
|
435
|
+
scopedLedger.units[dupeIdx] = unit;
|
|
436
|
+
} else {
|
|
437
|
+
scopedLedger.units.push(unit);
|
|
438
|
+
}
|
|
439
|
+
saveLedger(base, scopedLedger);
|
|
440
|
+
|
|
441
|
+
if (isUnifiedAuditEnabled()) {
|
|
442
|
+
emitUokAuditEvent(
|
|
443
|
+
base,
|
|
444
|
+
buildAuditEnvelope({
|
|
445
|
+
traceId: opts?.traceId ?? `metrics:${unitType}:${unitId}`,
|
|
446
|
+
turnId: opts?.turnId,
|
|
447
|
+
causedBy: opts?.causedBy,
|
|
448
|
+
category: "metrics",
|
|
449
|
+
type: "unit-metrics-snapshot",
|
|
450
|
+
payload: {
|
|
451
|
+
unitType,
|
|
452
|
+
unitId,
|
|
453
|
+
model,
|
|
454
|
+
tokens: unit.tokens,
|
|
455
|
+
cost: unit.cost,
|
|
456
|
+
toolCalls: unit.toolCalls,
|
|
457
|
+
},
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return unit;
|
|
463
|
+
}
|
|
464
|
+
|
|
275
465
|
// ─── Aggregation helpers ──────────────────────────────────────────────────────
|
|
276
466
|
|
|
277
467
|
export interface PhaseAggregate {
|
|
@@ -593,6 +783,12 @@ export function pruneMetricsLedger(base: string, keepCount: number): number {
|
|
|
593
783
|
if (ledger) {
|
|
594
784
|
ledger.units = ledger.units.slice(-keepCount);
|
|
595
785
|
}
|
|
786
|
+
// Invalidate all scoped ledger cache entries. Prune is rare; clearing the
|
|
787
|
+
// entire map is simpler than tracking which entry belongs to `base`. Without
|
|
788
|
+
// this, scopedLedgers entries for the pruned workspace hold a pre-prune
|
|
789
|
+
// MetricsLedger that snapshotUnitMetricsByScope would merge back in, causing
|
|
790
|
+
// pruned units to reappear in subsequent snapshots.
|
|
791
|
+
scopedLedgers.clear();
|
|
596
792
|
return removed;
|
|
597
793
|
}
|
|
598
794
|
|
|
@@ -635,6 +831,130 @@ function deduplicateUnits(units: UnitMetrics[]): UnitMetrics[] {
|
|
|
635
831
|
return Array.from(map.values());
|
|
636
832
|
}
|
|
637
833
|
|
|
834
|
+
// How long a lock file must be untouched (in ms) before it is considered
|
|
835
|
+
// orphaned from a crashed process. Set to 2× the acquire timeout.
|
|
836
|
+
export const STALE_LOCK_THRESHOLD_MS = 4000;
|
|
837
|
+
|
|
838
|
+
// Retry interval between lock acquire attempts (ms). Caps syscall rate at
|
|
839
|
+
// ~200 attempts over a 2s timeout instead of ~20,000 without any sleep.
|
|
840
|
+
// Exposed for tests.
|
|
841
|
+
export const LOCK_RETRY_INTERVAL_MS = 5;
|
|
842
|
+
|
|
843
|
+
// Sync sleep via Atomics.wait — true OS-level sleep, no CPU spin.
|
|
844
|
+
// Int32Array must reference a SharedArrayBuffer; we wait on index 0 which
|
|
845
|
+
// will never be woken by a Atomics.notify, so the wait always times out.
|
|
846
|
+
const _lockSleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
847
|
+
function syncSleep(ms: number): void {
|
|
848
|
+
Atomics.wait(_lockSleepBuf, 0, 0, ms);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Counts the number of sleepy retries (non-stale-evicting) made by acquireLock
|
|
852
|
+
// across all calls since the last reset. Exported for test instrumentation only.
|
|
853
|
+
let _lockSleepyRetries = 0;
|
|
854
|
+
export function getLockSleepyRetries(): number { return _lockSleepyRetries; }
|
|
855
|
+
export function resetLockSleepyRetries(): void { _lockSleepyRetries = 0; }
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Acquire an exclusive .lock sentinel file via O_EXCL.
|
|
859
|
+
*
|
|
860
|
+
* Improvements over the original:
|
|
861
|
+
* - No busy spin: the inner `while (Date.now() < waitUntil) {}` spin that
|
|
862
|
+
* burned CPU doing nothing useful is removed. Each retry attempt now makes
|
|
863
|
+
* one `openSync` syscall and immediately re-checks the deadline, which is
|
|
864
|
+
* orders of magnitude cheaper than a tight spin loop.
|
|
865
|
+
* - Stale-lock detection: if the existing lock file's mtime is older than
|
|
866
|
+
* STALE_LOCK_THRESHOLD_MS, the lock is considered orphaned (e.g. the
|
|
867
|
+
* writing process crashed) and is forcibly removed before retrying.
|
|
868
|
+
* A warning is logged so operators can detect crash patterns.
|
|
869
|
+
* - PID stamp: on success, writes the acquiring process's PID and a
|
|
870
|
+
* timestamp into the lock file so external monitors can identify orphans.
|
|
871
|
+
* - Retry sleep: after each non-stale-evicting retry, sleeps
|
|
872
|
+
* LOCK_RETRY_INTERVAL_MS (5ms) via Atomics.wait so the process yields to
|
|
873
|
+
* the OS. This caps syscall rate at ~200–400/s under contention instead of
|
|
874
|
+
* the ~20,000/s that would result from a tight openSync loop.
|
|
875
|
+
* After a stale-lock eviction (lock already removed), no sleep is injected
|
|
876
|
+
* — we retry immediately to close the short race window.
|
|
877
|
+
*
|
|
878
|
+
* Returns true on success, false on timeout.
|
|
879
|
+
*/
|
|
880
|
+
function acquireLock(lockPath: string, timeoutMs = 2000): boolean {
|
|
881
|
+
const deadline = Date.now() + timeoutMs;
|
|
882
|
+
while (Date.now() < deadline) {
|
|
883
|
+
try {
|
|
884
|
+
const fd = openSync(lockPath, "wx"); // O_WRONLY | O_CREAT | O_EXCL
|
|
885
|
+
closeSync(fd);
|
|
886
|
+
// Write PID stamp so external monitors can identify the lock owner.
|
|
887
|
+
try {
|
|
888
|
+
writeFileSync(lockPath, `${process.pid}\n${new Date().toISOString()}\n`, "utf-8");
|
|
889
|
+
} catch { /* non-fatal — stamp is diagnostic only */ }
|
|
890
|
+
return true;
|
|
891
|
+
} catch {
|
|
892
|
+
// Lock held by another process — check for staleness before retrying.
|
|
893
|
+
try {
|
|
894
|
+
const st = statSync(lockPath);
|
|
895
|
+
if (Date.now() - st.mtimeMs > STALE_LOCK_THRESHOLD_MS) {
|
|
896
|
+
logWarning(
|
|
897
|
+
"fs",
|
|
898
|
+
`stale metrics lock at ${lockPath} (age ${Date.now() - st.mtimeMs}ms); forcibly removing and retrying`,
|
|
899
|
+
);
|
|
900
|
+
try { unlinkSync(lockPath); } catch { /* already gone */ }
|
|
901
|
+
// Do NOT sleep after stale-lock eviction — retry the open
|
|
902
|
+
// immediately. The lock file was just removed; a short race window
|
|
903
|
+
// exists and sleeping here would unnecessarily delay recovery.
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
} catch { /* lock file disappeared between the failed open and stat — retry */ }
|
|
907
|
+
// Sleep between retries to yield to the OS and cap syscall rate.
|
|
908
|
+
// Uses Atomics.wait for a true blocking sleep (no CPU spin).
|
|
909
|
+
_lockSleepyRetries++;
|
|
910
|
+
syncSleep(LOCK_RETRY_INTERVAL_MS);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function releaseLock(lockPath: string): void {
|
|
917
|
+
try { unlinkSync(lockPath); } catch { /* ignore */ }
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Save the ledger with cross-process merge semantics.
|
|
922
|
+
*
|
|
923
|
+
* Acquires a .lock sentinel file, reads the current on-disk ledger,
|
|
924
|
+
* merges worker units with existing peer units (worker's entry wins on
|
|
925
|
+
* type+id+startedAt conflict since it has the latest finishedAt),
|
|
926
|
+
* then writes atomically. This prevents parallel auto-mode workers from
|
|
927
|
+
* silently discarding each other's metrics entries.
|
|
928
|
+
*
|
|
929
|
+
* Falls back to a direct write (no merge) if the lock cannot be acquired
|
|
930
|
+
* within the timeout — better to potentially overwrite than to lose data
|
|
931
|
+
* entirely.
|
|
932
|
+
*/
|
|
638
933
|
function saveLedger(base: string, data: MetricsLedger): void {
|
|
639
|
-
|
|
934
|
+
const path = metricsPath(base);
|
|
935
|
+
const lockPath = `${path}.lock`;
|
|
936
|
+
const acquired = acquireLock(lockPath);
|
|
937
|
+
if (acquired) {
|
|
938
|
+
try {
|
|
939
|
+
// Read current on-disk state and merge with worker's in-memory units.
|
|
940
|
+
// Worker units take precedence on conflict (by finishedAt in deduplicateUnits).
|
|
941
|
+
const onDisk = loadJsonFileOrNull(path, isMetricsLedger);
|
|
942
|
+
if (onDisk && onDisk.units.length > 0) {
|
|
943
|
+
const merged = deduplicateUnits([...onDisk.units, ...data.units]);
|
|
944
|
+
saveJsonFile(path, { ...data, units: merged });
|
|
945
|
+
} else {
|
|
946
|
+
saveJsonFile(path, data);
|
|
947
|
+
}
|
|
948
|
+
} finally {
|
|
949
|
+
releaseLock(lockPath);
|
|
950
|
+
}
|
|
951
|
+
} else {
|
|
952
|
+
// Lock could not be acquired within the timeout. Fall back to a direct
|
|
953
|
+
// write (no cross-process merge) to avoid losing this worker's data
|
|
954
|
+
// entirely. A concurrent writer may overwrite us, but that is preferable
|
|
955
|
+
// to a torn write caused by two writers simultaneously executing the
|
|
956
|
+
// read-merge-write sequence without mutual exclusion.
|
|
957
|
+
logWarning("fs", "saveLedger: lock not acquired — falling back to direct write (no merge)");
|
|
958
|
+
saveJsonFile(path, data);
|
|
959
|
+
}
|
|
640
960
|
}
|
|
@@ -9,7 +9,7 @@ import { existsSync, readdirSync } from "node:fs";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
11
|
import { loadFile } from "./files.js";
|
|
12
|
-
import { resolveMilestoneFile } from "./paths.js";
|
|
12
|
+
import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
|
|
13
13
|
import { mergeMilestoneToMain } from "./auto-worktree.js";
|
|
14
14
|
import { MergeConflictError } from "./git-service.js";
|
|
15
15
|
import { removeSessionStatus } from "./session-status-io.js";
|
|
@@ -33,12 +33,13 @@ export type MergeOrder = "sequential" | "by-completion";
|
|
|
33
33
|
// ─── Merge Queue ───────────────────────────────────────────────────────────
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Check whether a milestone is complete by querying
|
|
36
|
+
* Check whether a milestone is complete by querying the canonical project DB.
|
|
37
37
|
* Uses a subprocess to avoid disrupting the global DB singleton.
|
|
38
|
-
* Returns true when milestones.status = 'complete' in
|
|
38
|
+
* Returns true when milestones.status = 'complete' in project gsd.db.
|
|
39
39
|
*/
|
|
40
|
-
export function
|
|
41
|
-
const
|
|
40
|
+
export function isMilestoneCompleteInProjectDb(basePath: string, mid: string): boolean {
|
|
41
|
+
const workRoot = join(basePath, ".gsd", "worktrees", mid);
|
|
42
|
+
const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
|
|
42
43
|
if (!existsSync(dbPath)) return false;
|
|
43
44
|
|
|
44
45
|
try {
|
|
@@ -55,15 +56,15 @@ export function isMilestoneCompleteInWorktreeDb(basePath: string, mid: string):
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
* Discover milestone IDs with status='complete' in
|
|
59
|
-
*
|
|
59
|
+
* Discover milestone IDs with status='complete' in the canonical DB,
|
|
60
|
+
* using worktree directories only to enumerate active parallel workers.
|
|
60
61
|
*/
|
|
61
62
|
function discoverDbCompletedMilestones(basePath: string): Set<string> {
|
|
62
63
|
const completed = new Set<string>();
|
|
63
64
|
const worktreeDir = join(basePath, ".gsd", "worktrees");
|
|
64
65
|
try {
|
|
65
66
|
for (const entry of readdirSync(worktreeDir)) {
|
|
66
|
-
if (entry.startsWith("M") &&
|
|
67
|
+
if (entry.startsWith("M") && isMilestoneCompleteInProjectDb(basePath, entry)) {
|
|
67
68
|
completed.add(entry);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -78,9 +79,9 @@ function discoverDbCompletedMilestones(basePath: string): Set<string> {
|
|
|
78
79
|
* Sequential: merge in milestone ID order (M001 before M002).
|
|
79
80
|
* By-completion: merge in the order milestones finished.
|
|
80
81
|
*
|
|
81
|
-
* When basePath is provided, also checks
|
|
82
|
-
* source of truth
|
|
83
|
-
* are included if their
|
|
82
|
+
* When basePath is provided, also checks the canonical project DB as the
|
|
83
|
+
* source of truth. Workers with stale orchestrator state (e.g. "error")
|
|
84
|
+
* are included if their project DB row shows status='complete'.
|
|
84
85
|
* See: https://github.com/gsd-build/gsd-2/issues/2812
|
|
85
86
|
*/
|
|
86
87
|
export function determineMergeOrder(
|
|
@@ -93,7 +94,7 @@ export function determineMergeOrder(
|
|
|
93
94
|
workers.filter(w => w.state === "stopped").map(w => w.milestoneId),
|
|
94
95
|
);
|
|
95
96
|
|
|
96
|
-
// When basePath is available, also check
|
|
97
|
+
// When basePath is available, also check the project DB for milestones
|
|
97
98
|
// whose orchestrator state is stale but are actually complete (#2812)
|
|
98
99
|
const dbCompleted = basePath ? discoverDbCompletedMilestones(basePath) : new Set<string>();
|
|
99
100
|
|
|
@@ -109,7 +110,7 @@ export function determineMergeOrder(
|
|
|
109
110
|
if (w) {
|
|
110
111
|
allMergeable.push(w);
|
|
111
112
|
} else {
|
|
112
|
-
// Milestone discovered from
|
|
113
|
+
// Milestone discovered from project DB but not in workers list
|
|
113
114
|
allMergeable.push({
|
|
114
115
|
milestoneId: mid,
|
|
115
116
|
title: mid,
|
|
@@ -17,6 +17,7 @@ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
|
17
17
|
|
|
18
18
|
import { formatDuration, STATUS_GLYPH, STATUS_COLOR } from "../shared/mod.js";
|
|
19
19
|
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
20
|
+
import { resolveGsdPathContract } from "./paths.js";
|
|
20
21
|
|
|
21
22
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
22
23
|
|
|
@@ -126,7 +127,8 @@ function discoverWorkers(basePath: string): string[] {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function querySliceProgress(basePath: string, mid: string): SliceProgress[] {
|
|
129
|
-
const
|
|
130
|
+
const workRoot = join(basePath, ".gsd", "worktrees", mid);
|
|
131
|
+
const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
|
|
130
132
|
if (!existsSync(dbPath)) return [];
|
|
131
133
|
|
|
132
134
|
try {
|
|
@@ -166,7 +168,8 @@ function extractCostFromNdjson(basePath: string, mid: string): number {
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
function queryRecentCompletions(basePath: string, mid: string): string[] {
|
|
169
|
-
const
|
|
171
|
+
const workRoot = join(basePath, ".gsd", "worktrees", mid);
|
|
172
|
+
const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
|
|
170
173
|
if (!existsSync(dbPath)) return [];
|
|
171
174
|
try {
|
|
172
175
|
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`;
|