@opengsd/gsd-pi 1.2.0-dev.4c756166 → 1.2.0-dev.5457a158
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-style.d.ts +17 -0
- package/dist/cli-style.js +28 -0
- package/dist/cli.js +1 -1
- package/dist/headless-events.d.ts +4 -2
- package/dist/headless-events.js +7 -29
- package/dist/models-resolver.d.ts +3 -13
- package/dist/models-resolver.js +3 -22
- package/dist/resource-loader.js +2 -14
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/bg-shell/utilities.js +5 -2
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +35 -4
- package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
- package/dist/resources/extensions/gsd/auto/phases.js +6 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +12 -14
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +7 -16
- package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +35 -352
- package/dist/resources/extensions/gsd/auto.js +8 -20
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +86 -6
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +30 -4
- package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
- package/dist/resources/extensions/gsd/captures.js +5 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
- package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
- package/dist/resources/extensions/gsd/db/engine.js +755 -0
- package/dist/resources/extensions/gsd/db/queries.js +372 -0
- package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
- package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
- package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
- package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
- package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
- package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
- package/dist/resources/extensions/gsd/doctor-format.js +9 -6
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
- package/dist/resources/extensions/gsd/error-classifier.js +9 -0
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +3 -0
- package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
- package/dist/resources/extensions/gsd/guidance.js +98 -0
- package/dist/resources/extensions/gsd/guided-flow.js +51 -5
- package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
- package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
- package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
- package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
- package/dist/resources/extensions/gsd/model-router.js +3 -0
- package/dist/resources/extensions/gsd/notification-store.js +11 -4
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
- package/dist/resources/extensions/gsd/paths.js +37 -24
- package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
- package/dist/resources/extensions/gsd/preferences-models.js +12 -46
- package/dist/resources/extensions/gsd/preferences.js +14 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
- package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
- package/dist/resources/extensions/gsd/publication.js +87 -0
- package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
- package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
- package/dist/resources/extensions/gsd/state.js +1 -20
- package/dist/resources/extensions/gsd/status-guards.js +56 -8
- package/dist/resources/extensions/gsd/stop-notice.js +57 -0
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
- package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
- package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -8
- package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
- package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
- package/dist/resources/extensions/gsd/undo.js +8 -7
- package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +9 -1
- package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
- package/dist/resources/extensions/gsd/unit-registry.js +350 -0
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
- package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
- package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
- package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
- package/dist/resources/extensions/gsd/worktree-root.js +28 -6
- package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
- package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
- package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
- 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 +9 -9
- 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/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/mcp-connections/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/shutdown/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 +9 -9
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
- package/dist/web/standalone/.next/server/chunks/8357.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/worktree-cli.js +3 -6
- package/dist/worktree-status-banner.js +7 -11
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +4 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/cli.js +6 -3
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +46 -21
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +294 -239
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +260 -256
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/utilities.ts +5 -2
- package/src/resources/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +37 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
- package/src/resources/extensions/gsd/auto/phases.ts +10 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +3 -0
- package/src/resources/extensions/gsd/auto-start.ts +12 -15
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +10 -17
- package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +41 -364
- package/src/resources/extensions/gsd/auto.ts +20 -24
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +87 -6
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +29 -3
- package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
- package/src/resources/extensions/gsd/captures.ts +5 -16
- package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
- package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
- package/src/resources/extensions/gsd/db/engine.ts +809 -0
- package/src/resources/extensions/gsd/db/queries.ts +453 -0
- package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
- package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
- package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
- package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
- package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
- package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
- package/src/resources/extensions/gsd/doctor-format.ts +12 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
- package/src/resources/extensions/gsd/error-classifier.ts +11 -0
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +3 -0
- package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
- package/src/resources/extensions/gsd/guidance.ts +139 -0
- package/src/resources/extensions/gsd/guided-flow.ts +50 -5
- package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
- package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
- package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
- package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
- package/src/resources/extensions/gsd/model-router.ts +3 -0
- package/src/resources/extensions/gsd/notification-store.ts +26 -3
- package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
- package/src/resources/extensions/gsd/paths.ts +42 -22
- package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
- package/src/resources/extensions/gsd/preferences-models.ts +10 -46
- package/src/resources/extensions/gsd/preferences.ts +18 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
- package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
- package/src/resources/extensions/gsd/publication.ts +122 -0
- package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
- package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
- package/src/resources/extensions/gsd/state.ts +4 -21
- package/src/resources/extensions/gsd/status-guards.ts +59 -8
- package/src/resources/extensions/gsd/stop-notice.ts +75 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/guidance.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +51 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
- package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +23 -2
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
- package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -8
- package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
- package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
- package/src/resources/extensions/gsd/undo.ts +9 -8
- package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +12 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
- package/src/resources/extensions/gsd/unit-registry.ts +425 -0
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
- package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
- package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
- package/src/resources/extensions/gsd/worktree-root.ts +29 -6
- package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
- package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
- package/src/resources/skills/gsd-browser/SKILL.md +1 -1
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → 2p9Rv9pQflAxCBbGVI2vb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → 2p9Rv9pQflAxCBbGVI2vb}/_ssgManifest.js +0 -0
|
@@ -161,13 +161,13 @@ test("enterMilestone returns ok:true mode:worktree on successful create", (t) =>
|
|
|
161
161
|
if (result.ok) {
|
|
162
162
|
assert.equal(result.mode, "worktree");
|
|
163
163
|
assert.ok(
|
|
164
|
-
result.path.endsWith("/.gsd
|
|
165
|
-
`expected path to end with /.gsd
|
|
164
|
+
result.path.endsWith("/.gsd-worktrees/M001"),
|
|
165
|
+
`expected path to end with /.gsd-worktrees/M001, got ${result.path}`,
|
|
166
166
|
);
|
|
167
167
|
}
|
|
168
168
|
assert.ok(
|
|
169
|
-
s.basePath.endsWith("/.gsd
|
|
170
|
-
`expected s.basePath to end with /.gsd
|
|
169
|
+
s.basePath.endsWith("/.gsd-worktrees/M001"),
|
|
170
|
+
`expected s.basePath to end with /.gsd-worktrees/M001, got ${s.basePath}`,
|
|
171
171
|
);
|
|
172
172
|
// After C3 (#5626) `invalidateAllCaches` is inlined; assertion against
|
|
173
173
|
// `deps.calls` for cache invalidation is no longer possible.
|
|
@@ -240,6 +240,43 @@ test("adoptStrandedMilestone forces branch recovery even when normal preferences
|
|
|
240
240
|
assert.equal(currentBranch, "milestone/M001");
|
|
241
241
|
});
|
|
242
242
|
|
|
243
|
+
test("enterMilestone honors stranded branch recovery instead of recreating the worktree", (t) => {
|
|
244
|
+
// Regression: after adoptStrandedMilestone checks out milestone/M001 in
|
|
245
|
+
// the project root, a plain enterMilestone under isolation:worktree used
|
|
246
|
+
// to attempt `git worktree add`, which git refuses ("branch is already in
|
|
247
|
+
// use by another worktree" — the root checkout IS the conflicting
|
|
248
|
+
// worktree), tripping a creation-failed warning and degrading isolation.
|
|
249
|
+
// The recovery override must keep re-entries in branch mode.
|
|
250
|
+
const previousCwd = process.cwd();
|
|
251
|
+
const base = makeGitRepoBase({ isolation: "worktree" });
|
|
252
|
+
t.after(() => cleanupRepoBase(base, previousCwd));
|
|
253
|
+
|
|
254
|
+
const s = makeSession({ basePath: base, originalBasePath: base });
|
|
255
|
+
const deps = makeDeps();
|
|
256
|
+
const ctx = makeCtx();
|
|
257
|
+
const lifecycle = new WorktreeLifecycle(s, deps);
|
|
258
|
+
|
|
259
|
+
const adopted = lifecycle.adoptStrandedMilestone("M001", base, ctx, {
|
|
260
|
+
mode: "branch",
|
|
261
|
+
});
|
|
262
|
+
assert.equal(adopted.ok, true, `expected adopt ok:true, got: ${JSON.stringify(adopted)}`);
|
|
263
|
+
|
|
264
|
+
const result = lifecycle.enterMilestone("M001", ctx);
|
|
265
|
+
|
|
266
|
+
assert.equal(result.ok, true, `expected ok:true, got: ${JSON.stringify(result)}`);
|
|
267
|
+
if (result.ok) {
|
|
268
|
+
assert.equal(result.mode, "branch");
|
|
269
|
+
assert.equal(result.path, base);
|
|
270
|
+
}
|
|
271
|
+
assert.equal(s.basePath, base);
|
|
272
|
+
assert.equal(s.isolationDegraded, false, "intentional branch adoption must not degrade isolation");
|
|
273
|
+
assert.equal(
|
|
274
|
+
ctx.messages.some((m) => m.msg.includes("creation for M001 failed")),
|
|
275
|
+
false,
|
|
276
|
+
"re-entry must not attempt (and fail) canonical worktree creation",
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
243
280
|
test("enterMilestone returns ok:false reason:isolation-degraded when session degraded", () => {
|
|
244
281
|
const s = makeSession({ isolationDegraded: true });
|
|
245
282
|
const deps = makeDeps({ getIsolationMode: () => "branch" });
|
|
@@ -142,6 +142,27 @@ describe("createWorktree", () => {
|
|
|
142
142
|
run("git rev-parse --git-dir", info.path);
|
|
143
143
|
assert.ok(!existsSync(join(info.path, "orphan.txt")), "stale file removed by recovery");
|
|
144
144
|
});
|
|
145
|
+
|
|
146
|
+
test("removes stale canonical directory when legacy orphan is cleaned and canonical target is stale", () => {
|
|
147
|
+
// Scenario: a stale .gsd-worktrees/M020 directory (no .git — aborted prior
|
|
148
|
+
// creation) coexists with an orphaned .gsd/worktrees/M020 dir (.git file not
|
|
149
|
+
// registered with git). worktreePathFor returns the legacy path (canonical has
|
|
150
|
+
// no .git marker), so only the legacy path was previously cleaned — the stale
|
|
151
|
+
// canonical blocked git worktree add. The fix ensures createWorktree also
|
|
152
|
+
// removes the stale canonical before calling git worktree add.
|
|
153
|
+
const canonicalDir = join(base, ".gsd-worktrees", "M020");
|
|
154
|
+
const legacyDir = join(base, ".gsd", "worktrees", "M020");
|
|
155
|
+
|
|
156
|
+
mkdirSync(canonicalDir, { recursive: true }); // stale canonical: exists, no .git
|
|
157
|
+
mkdirSync(legacyDir, { recursive: true });
|
|
158
|
+
writeFileSync(join(legacyDir, ".git"), "gitdir: ../../../../.git/worktrees/M020\n", "utf-8");
|
|
159
|
+
|
|
160
|
+
const info = createWorktree(base, "M020");
|
|
161
|
+
assert.strictEqual(info.name, "M020");
|
|
162
|
+
assert.ok(existsSync(info.path), "worktree path should exist after creation");
|
|
163
|
+
assert.ok(existsSync(join(info.path, ".git")), "new worktree has .git marker");
|
|
164
|
+
run("git rev-parse --git-dir", info.path);
|
|
165
|
+
});
|
|
145
166
|
});
|
|
146
167
|
|
|
147
168
|
describe("createWorktree — duplicate rejection", () => {
|
|
@@ -180,7 +201,7 @@ describe("createWorktree — branch cleanup on add failure", () => {
|
|
|
180
201
|
|
|
181
202
|
// Make the worktrees parent directory non-writable so `git worktree add`
|
|
182
203
|
// fails after the branch has already been force-reset.
|
|
183
|
-
const parentDir = join(base, ".gsd
|
|
204
|
+
const parentDir = join(base, ".gsd-worktrees");
|
|
184
205
|
mkdirSync(parentDir, { recursive: true });
|
|
185
206
|
run(`chmod 555 "${parentDir}"`, base);
|
|
186
207
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for worktreePathFor — the forward seam (project + name → path).
|
|
3
|
+
*
|
|
4
|
+
* Key invariant: a stale canonical directory (no .git marker) must NOT
|
|
5
|
+
* shadow a live legacy worktree (.gsd/worktrees/<name> with .git).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
import { worktreePathFor, canonicalWorktreesDir, legacyWorktreesDir } from "../worktree-placement.ts";
|
|
16
|
+
|
|
17
|
+
function makeTmpRoot(): string {
|
|
18
|
+
const root = join(tmpdir(), `gsd-placement-test-${randomUUID()}`);
|
|
19
|
+
mkdirSync(root, { recursive: true });
|
|
20
|
+
return root;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cleanup(root: string): void {
|
|
24
|
+
try { rmSync(root, { recursive: true, force: true }); } catch { /* */ }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeCanonicalDir(root: string, name: string): string {
|
|
28
|
+
const p = join(canonicalWorktreesDir(root), name);
|
|
29
|
+
mkdirSync(p, { recursive: true });
|
|
30
|
+
return p;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeLiveCanonical(root: string, name: string): string {
|
|
34
|
+
const p = makeCanonicalDir(root, name);
|
|
35
|
+
writeFileSync(join(p, ".git"), `gitdir: ${join(root, ".git", "worktrees", name)}\n`);
|
|
36
|
+
return p;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function makeLiveLegacy(root: string, name: string): string {
|
|
40
|
+
const p = join(legacyWorktreesDir(root), name);
|
|
41
|
+
mkdirSync(p, { recursive: true });
|
|
42
|
+
writeFileSync(join(p, ".git"), `gitdir: ${join(root, ".git", "worktrees", name)}\n`);
|
|
43
|
+
return p;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test("returns canonical path when canonical has .git marker", () => {
|
|
47
|
+
const root = makeTmpRoot();
|
|
48
|
+
try {
|
|
49
|
+
const canonical = makeLiveCanonical(root, "M001");
|
|
50
|
+
assert.equal(worktreePathFor(root, "M001"), canonical);
|
|
51
|
+
} finally {
|
|
52
|
+
cleanup(root);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("returns legacy path when only legacy exists with .git marker", () => {
|
|
57
|
+
const root = makeTmpRoot();
|
|
58
|
+
try {
|
|
59
|
+
const legacy = makeLiveLegacy(root, "M001");
|
|
60
|
+
assert.equal(worktreePathFor(root, "M001"), legacy);
|
|
61
|
+
} finally {
|
|
62
|
+
cleanup(root);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("returns legacy path when canonical dir exists but has no .git (stale canonical)", () => {
|
|
67
|
+
const root = makeTmpRoot();
|
|
68
|
+
try {
|
|
69
|
+
makeCanonicalDir(root, "M001"); // stale: dir exists, no .git
|
|
70
|
+
const legacy = makeLiveLegacy(root, "M001");
|
|
71
|
+
assert.equal(
|
|
72
|
+
worktreePathFor(root, "M001"),
|
|
73
|
+
legacy,
|
|
74
|
+
"stale canonical must not shadow live legacy worktree",
|
|
75
|
+
);
|
|
76
|
+
} finally {
|
|
77
|
+
cleanup(root);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("returns canonical path for new-worktree creation when neither path exists", () => {
|
|
82
|
+
const root = makeTmpRoot();
|
|
83
|
+
try {
|
|
84
|
+
const expected = join(canonicalWorktreesDir(root), "M001");
|
|
85
|
+
assert.equal(worktreePathFor(root, "M001"), expected);
|
|
86
|
+
} finally {
|
|
87
|
+
cleanup(root);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("prefers live canonical over live legacy when both exist", () => {
|
|
92
|
+
const root = makeTmpRoot();
|
|
93
|
+
try {
|
|
94
|
+
const canonical = makeLiveCanonical(root, "M001");
|
|
95
|
+
makeLiveLegacy(root, "M001");
|
|
96
|
+
assert.equal(worktreePathFor(root, "M001"), canonical);
|
|
97
|
+
} finally {
|
|
98
|
+
cleanup(root);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("returns legacy when canonical is stale and legacy has no .git (both stale)", () => {
|
|
103
|
+
const root = makeTmpRoot();
|
|
104
|
+
try {
|
|
105
|
+
makeCanonicalDir(root, "M001"); // stale canonical
|
|
106
|
+
const legacy = join(legacyWorktreesDir(root), "M001");
|
|
107
|
+
mkdirSync(legacy, { recursive: true }); // stale legacy (no .git)
|
|
108
|
+
// Falls through to legacy existsSync since canonical has no .git
|
|
109
|
+
assert.equal(worktreePathFor(root, "M001"), legacy);
|
|
110
|
+
} finally {
|
|
111
|
+
cleanup(root);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
@@ -65,7 +65,7 @@ describe("reenterActiveWorktreeIfNeeded", () => {
|
|
|
65
65
|
const entered = await reenterActiveWorktreeIfNeeded(dir);
|
|
66
66
|
assert.ok(entered, "re-entry returned a worktree path");
|
|
67
67
|
assert.strictEqual(realpathSync(process.cwd()), realpathSync(entered!), "cwd moved into the worktree");
|
|
68
|
-
assert.strictEqual(entered, join(dir, ".gsd
|
|
68
|
+
assert.strictEqual(entered, join(dir, ".gsd-worktrees", "M001"));
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
test("no-op when already inside a worktree", async (t) => {
|
|
@@ -126,7 +126,9 @@ describe("Worktree Safety module", () => {
|
|
|
126
126
|
assert.equal(result.ok, false);
|
|
127
127
|
assert.equal(result.kind, "invalid-root");
|
|
128
128
|
assert.equal(result.details?.unitRoot, outsideRoot);
|
|
129
|
-
|
|
129
|
+
// The reported expected root is the canonical container; the legacy
|
|
130
|
+
// .gsd/worktrees/ location is also accepted but not surfaced here.
|
|
131
|
+
assert.equal(result.details?.expectedRoot, join(projectRoot, ".gsd-worktrees", "M001"));
|
|
130
132
|
});
|
|
131
133
|
|
|
132
134
|
test("accepts project root for source-writing Unit when isolation mode is none", () => {
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Fix: removeWorktree should query `git worktree list` to find the actual
|
|
10
10
|
* registered path when the computed path doesn't match.
|
|
11
|
+
*
|
|
12
|
+
* New worktrees are created at the canonical `.gsd-worktrees/` sibling and
|
|
13
|
+
* never cross the symlink, so this scenario only applies to LEGACY worktrees
|
|
14
|
+
* (created under `.gsd/worktrees/` by older versions). The test creates the
|
|
15
|
+
* worktree at the legacy location directly to keep that coverage.
|
|
11
16
|
*/
|
|
12
17
|
import { mkdtempSync, mkdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync, existsSync, realpathSync } from "node:fs";
|
|
13
18
|
import { join } from "node:path";
|
|
@@ -15,7 +20,6 @@ import { tmpdir } from "node:os";
|
|
|
15
20
|
import { execSync } from "node:child_process";
|
|
16
21
|
|
|
17
22
|
import {
|
|
18
|
-
createWorktree,
|
|
19
23
|
removeWorktree,
|
|
20
24
|
listWorktrees,
|
|
21
25
|
worktreePath,
|
|
@@ -64,13 +68,15 @@ test('worktree-symlink-removal removes the git-registered symlink target safely'
|
|
|
64
68
|
run("git add .", base);
|
|
65
69
|
run('git commit -m "init"', base);
|
|
66
70
|
|
|
67
|
-
// Create a worktree
|
|
68
|
-
// the worktree at the external path
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
// Create a LEGACY worktree through the symlinked .gsd path — git resolves
|
|
72
|
+
// the symlink and registers the worktree at the external path. (Current
|
|
73
|
+
// createWorktree() uses the canonical .gsd-worktrees/ sibling instead.)
|
|
74
|
+
const legacyPath = join(base, ".gsd", "worktrees", "M002");
|
|
75
|
+
run(`git worktree add -b milestone/M002 "${legacyPath}"`, base);
|
|
76
|
+
assert.ok(existsSync(legacyPath), "worktree created");
|
|
71
77
|
|
|
72
78
|
// Verify worktree was created at the resolved (external) path
|
|
73
|
-
const realWtPath = realpathSync(
|
|
79
|
+
const realWtPath = realpathSync(legacyPath);
|
|
74
80
|
assert.ok(
|
|
75
81
|
realWtPath.startsWith(externalState),
|
|
76
82
|
`worktree real path (${realWtPath}) is under external state dir`,
|
|
@@ -116,8 +116,8 @@ describe("worktree-teardown-safety", () => {
|
|
|
116
116
|
|
|
117
117
|
const wtPathResult = worktreePath(tempDir, "anything");
|
|
118
118
|
assertTrue(
|
|
119
|
-
wtPathResult.startsWith(join(tempDir, ".gsd
|
|
120
|
-
"worktreePath returns path under .gsd
|
|
119
|
+
wtPathResult.startsWith(join(tempDir, ".gsd-worktrees")),
|
|
120
|
+
"worktreePath returns path under the canonical .gsd-worktrees/ container",
|
|
121
121
|
);
|
|
122
122
|
});
|
|
123
123
|
|
|
@@ -803,3 +803,45 @@ test('write-gate: resetWriteGateState persists through dangling .gsd symlink', (
|
|
|
803
803
|
} catch { /* swallow */ }
|
|
804
804
|
}
|
|
805
805
|
});
|
|
806
|
+
|
|
807
|
+
// ─── Scenario 31: hydrate in-memory gate state from persisted snapshot (MCP subprocess) ──
|
|
808
|
+
|
|
809
|
+
test('write-gate: getPendingGate hydrates from disk when workflow MCP verified gate in child process', () => {
|
|
810
|
+
const base = join(tmpdir(), `gsd-write-gate-mcp-hydrate-${randomUUID()}`);
|
|
811
|
+
const stateFilePath = join(base, '.gsd', 'runtime', 'write-gate-state.json');
|
|
812
|
+
const originalEnv = process.env.GSD_PERSIST_WRITE_GATE_STATE;
|
|
813
|
+
const gateId = 'depth_verification_M005_confirm';
|
|
814
|
+
|
|
815
|
+
try {
|
|
816
|
+
process.env.GSD_PERSIST_WRITE_GATE_STATE = '1';
|
|
817
|
+
mkdirSync(join(base, '.gsd', 'runtime'), { recursive: true });
|
|
818
|
+
clearDiscussionFlowState(base);
|
|
819
|
+
setPendingGate(gateId, base);
|
|
820
|
+
|
|
821
|
+
writeFileSync(stateFilePath, JSON.stringify({
|
|
822
|
+
verifiedDepthMilestones: ['M005'],
|
|
823
|
+
verifiedApprovalGates: [gateId],
|
|
824
|
+
activeQueuePhase: false,
|
|
825
|
+
pendingGateId: null,
|
|
826
|
+
}, null, 2), 'utf-8');
|
|
827
|
+
|
|
828
|
+
assert.strictEqual(getPendingGate(base), null, 'stale in-memory pending must refresh from disk');
|
|
829
|
+
assert.strictEqual(isMilestoneDepthVerified('M005', base), true, 'verified milestone must hydrate from disk');
|
|
830
|
+
assert.deepEqual(loadWriteGateSnapshot(base), {
|
|
831
|
+
verifiedDepthMilestones: ['M005'],
|
|
832
|
+
verifiedApprovalGates: [gateId],
|
|
833
|
+
activeQueuePhase: false,
|
|
834
|
+
pendingGateId: null,
|
|
835
|
+
});
|
|
836
|
+
} finally {
|
|
837
|
+
if (originalEnv === undefined) {
|
|
838
|
+
delete process.env.GSD_PERSIST_WRITE_GATE_STATE;
|
|
839
|
+
} else {
|
|
840
|
+
process.env.GSD_PERSIST_WRITE_GATE_STATE = originalEnv;
|
|
841
|
+
}
|
|
842
|
+
clearDiscussionFlowState(base);
|
|
843
|
+
try {
|
|
844
|
+
rmSync(base, { recursive: true, force: true });
|
|
845
|
+
} catch { /* swallow */ }
|
|
846
|
+
}
|
|
847
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Tool Contract module's runtime face — verify the live SDK tool surface covers a Unit's required workflow tools.
|
|
3
|
+
|
|
4
|
+
import { mcpToolMatchesBaseName } from "./mcp-tool-name.js";
|
|
5
|
+
import { getRequiredWorkflowToolsForUnit } from "./unit-tool-contracts.js";
|
|
6
|
+
import { isWorkflowToolSurfaceName } from "./workflow-tool-surface.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stable phrase recognized as transient by auto-tool-tracking's
|
|
10
|
+
* isToolUnavailableError and error-classifier's transient buckets,
|
|
11
|
+
* which build their matchers from this constant.
|
|
12
|
+
*/
|
|
13
|
+
export const TOOL_SURFACE_NOT_READY = "workflow tool surface not ready";
|
|
14
|
+
|
|
15
|
+
/** MCP server statuses that will not self-heal within the session. */
|
|
16
|
+
const TERMINAL_MCP_SERVER_STATUSES = new Set(["failed", "needs-auth", "disabled"]);
|
|
17
|
+
|
|
18
|
+
export interface LiveToolSurfaceObservation {
|
|
19
|
+
/** Tool names the session reported at init (MCP tools appear as mcp__<server>__<tool>). */
|
|
20
|
+
tools: readonly string[];
|
|
21
|
+
/** MCP server connection statuses the session reported at init. */
|
|
22
|
+
mcpServers: readonly { name: string; status: string }[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Verify the live tool surface observed at SDK session init covers the Unit's
|
|
27
|
+
* required workflow tools. Complements the static pre-dispatch gate
|
|
28
|
+
* (getWorkflowTransportSupportError), which only proves the MCP launch config
|
|
29
|
+
* is discoverable — the workflow server connects asynchronously after session
|
|
30
|
+
* start, so the static gate cannot see whether the tools actually registered.
|
|
31
|
+
*
|
|
32
|
+
* Returns a transient, recovery-classifiable error (kind tool-unavailable →
|
|
33
|
+
* retry) when the workflow server failed or has not yet registered a required
|
|
34
|
+
* tool, so dispatch aborts before the first model turn instead of letting the
|
|
35
|
+
* Unit improvise around "No such tool available". Returns null when no
|
|
36
|
+
* workflow server is part of this session (native tool path), when the Unit
|
|
37
|
+
* requires no workflow tools, or when the surface is ready.
|
|
38
|
+
*/
|
|
39
|
+
export function getToolSurfaceReadinessError(input: {
|
|
40
|
+
unitType: string | undefined;
|
|
41
|
+
workflowServerName: string | undefined;
|
|
42
|
+
observation: LiveToolSurfaceObservation;
|
|
43
|
+
}): string | null {
|
|
44
|
+
const { unitType, workflowServerName, observation } = input;
|
|
45
|
+
if (!unitType || !workflowServerName) return null;
|
|
46
|
+
|
|
47
|
+
const required = getRequiredWorkflowToolsForUnit(unitType).filter(isWorkflowToolSurfaceName);
|
|
48
|
+
if (required.length === 0) return null;
|
|
49
|
+
|
|
50
|
+
const server = observation.mcpServers.find((entry) => entry.name === workflowServerName);
|
|
51
|
+
if (!server) {
|
|
52
|
+
return `${TOOL_SURFACE_NOT_READY} for ${unitType}: MCP server "${workflowServerName}" is absent from the init surface (not yet connected): ${required.join(", ")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The SDK does not wait for MCP servers before init — a still-connecting
|
|
56
|
+
// server reports "pending" there routinely, then registers within seconds,
|
|
57
|
+
// usually well before the Unit's first workflow tool call. Aborting on
|
|
58
|
+
// "pending" would fail the common healthy session, so it passes through;
|
|
59
|
+
// a genuine miss after pass-through still surfaces in-session as
|
|
60
|
+
// "No such tool available" and classifies tool-unavailable → bounded retry.
|
|
61
|
+
// Only statuses that cannot self-heal abort here.
|
|
62
|
+
if (server.status !== "connected" && !TERMINAL_MCP_SERVER_STATUSES.has(server.status)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const missing = required.filter(
|
|
67
|
+
(tool) => !observation.tools.some((name) => name === tool || mcpToolMatchesBaseName(name, tool)),
|
|
68
|
+
);
|
|
69
|
+
if (missing.length === 0) return null;
|
|
70
|
+
|
|
71
|
+
const serverDetail =
|
|
72
|
+
server.status === "connected"
|
|
73
|
+
? `MCP server "${workflowServerName}" is connected but has not registered`
|
|
74
|
+
: `MCP server "${workflowServerName}" status is "${server.status}" and it has not registered`;
|
|
75
|
+
return `${TOOL_SURFACE_NOT_READY} for ${unitType}: ${serverDetail}: ${missing.join(", ")}`;
|
|
76
|
+
}
|
|
@@ -15,20 +15,11 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
|
|
17
17
|
import type { CompleteSliceParams } from "../types.js";
|
|
18
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
19
18
|
import {
|
|
20
|
-
|
|
21
|
-
insertMilestone,
|
|
22
|
-
insertSlice,
|
|
23
|
-
getSlice,
|
|
24
|
-
getSliceTasks,
|
|
25
|
-
getMilestone,
|
|
26
|
-
updateSliceStatus,
|
|
19
|
+
completeSliceCascade,
|
|
27
20
|
setSliceSummaryMd,
|
|
28
21
|
saveGateResult,
|
|
29
22
|
getPendingGatesForTurn,
|
|
30
|
-
getMilestoneSlices,
|
|
31
|
-
updateMilestoneStatus,
|
|
32
23
|
} from "../gsd-db.js";
|
|
33
24
|
import { getGatesForTurn } from "../gate-registry.js";
|
|
34
25
|
import { gsdProjectionRoot, clearPathCache, resolveMilestoneFile } from "../paths.js";
|
|
@@ -371,60 +362,34 @@ export async function handleCompleteSlice(
|
|
|
371
362
|
};
|
|
372
363
|
}
|
|
373
364
|
|
|
374
|
-
// ──
|
|
365
|
+
// ── Atomic completion cascade (guards + writes in one transaction) ───────
|
|
375
366
|
const completedAt = new Date().toISOString();
|
|
376
367
|
let guardError: string | null = null;
|
|
377
368
|
let existingSummaryMd = "";
|
|
378
369
|
let duplicateComplete = false;
|
|
379
370
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// Only block if they exist and are closed.
|
|
384
|
-
const milestone = getMilestone(params.milestoneId);
|
|
385
|
-
if (milestone && isClosedStatus(milestone.status)) {
|
|
386
|
-
guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
391
|
-
existingSummaryMd = slice?.full_summary_md?.trim() ?? "";
|
|
392
|
-
if (slice && isClosedStatus(slice.status)) {
|
|
393
|
-
duplicateComplete = true;
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Verify all tasks are complete
|
|
398
|
-
const tasks = getSliceTasks(params.milestoneId, params.sliceId);
|
|
399
|
-
if (tasks.length === 0) {
|
|
400
|
-
guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
|
|
405
|
-
if (incompleteTasks.length > 0) {
|
|
406
|
-
const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
|
|
407
|
-
guardError = `incomplete tasks: ${incompleteIds}`;
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// All guards passed — perform writes. Preserve existing planning metadata:
|
|
412
|
-
// completion should not overwrite title/risk/depends/demo/sequence.
|
|
413
|
-
insertMilestone({ id: params.milestoneId, title: params.milestoneId });
|
|
414
|
-
if (!slice) {
|
|
415
|
-
insertSlice({ id: params.sliceId, milestoneId: params.milestoneId, title: params.sliceTitle || params.sliceId });
|
|
416
|
-
}
|
|
417
|
-
updateSliceStatus(params.milestoneId, params.sliceId, "complete", completedAt);
|
|
418
|
-
|
|
419
|
-
const updatedSlices = getMilestoneSlices(params.milestoneId);
|
|
420
|
-
if (
|
|
421
|
-
milestone?.status === "planned" &&
|
|
422
|
-
updatedSlices.length > 0 &&
|
|
423
|
-
updatedSlices.every((s) => isClosedStatus(s.status))
|
|
424
|
-
) {
|
|
425
|
-
updateMilestoneStatus(params.milestoneId, "active");
|
|
426
|
-
}
|
|
371
|
+
const outcome = completeSliceCascade(params.milestoneId, params.sliceId, {
|
|
372
|
+
sliceTitle: params.sliceTitle,
|
|
373
|
+
completedAt,
|
|
427
374
|
});
|
|
375
|
+
if (outcome.ok) {
|
|
376
|
+
existingSummaryMd = outcome.existingSummaryMd;
|
|
377
|
+
duplicateComplete = outcome.duplicate;
|
|
378
|
+
} else {
|
|
379
|
+
switch (outcome.reason) {
|
|
380
|
+
case "milestone-closed":
|
|
381
|
+
guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${outcome.status})`;
|
|
382
|
+
break;
|
|
383
|
+
case "no-tasks":
|
|
384
|
+
guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
|
|
385
|
+
break;
|
|
386
|
+
case "incomplete-tasks": {
|
|
387
|
+
const incompleteIds = outcome.incomplete.map((t) => `${t.id} (status: ${t.status})`).join(", ");
|
|
388
|
+
guardError = `incomplete tasks: ${incompleteIds}`;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
428
393
|
|
|
429
394
|
if (duplicateComplete) {
|
|
430
395
|
const staleSummaryPath = sliceSummaryPath(
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { realpathSync } from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { isContextModeEnabled, type ContextModeConfig } from "../preferences-types.js";
|
|
14
|
+
import { projectRootFromWorktreePath } from "../worktree-root.js";
|
|
14
15
|
import { contextModeDisabledResult, type ToolExecutionResult } from "./context-mode-tool-result.js";
|
|
15
16
|
|
|
16
17
|
export interface ExecToolParams {
|
|
@@ -201,13 +202,9 @@ function normalizeScanPath(value: string): string {
|
|
|
201
202
|
|
|
202
203
|
function parseWorktreeBase(baseDir: string): { originalRoot: string; worktreeRoot: string } | null {
|
|
203
204
|
const normalizedBase = normalizeScanPath(baseDir);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
originalRoot: normalizedBase.slice(0, markerIndex),
|
|
209
|
-
worktreeRoot: normalizedBase,
|
|
210
|
-
};
|
|
205
|
+
const originalRoot = projectRootFromWorktreePath(normalizedBase);
|
|
206
|
+
if (!originalRoot) return null;
|
|
207
|
+
return { originalRoot, worktreeRoot: normalizedBase };
|
|
211
208
|
}
|
|
212
209
|
|
|
213
210
|
function pathInside(parent: string, target: string): boolean {
|
|
@@ -297,7 +294,7 @@ function scriptReferencesOriginalRootFromWorktree(script: string, baseDir: strin
|
|
|
297
294
|
const normalizedScript = script.replace(/\\/g, "/");
|
|
298
295
|
return comparablePathVariants(parsed.originalRoot).some((originalRoot) => {
|
|
299
296
|
const originalRootPattern = new RegExp(
|
|
300
|
-
`${escapeRegExp(originalRoot)}(?=$|[\\s'"\\\`;)&|<>]|/(?!\\.gsd
|
|
297
|
+
`${escapeRegExp(originalRoot)}(?=$|[\\s'"\\\`;)&|<>]|/(?!\\.gsd(?:-worktrees|/worktrees)(?:/|$)))`,
|
|
301
298
|
);
|
|
302
299
|
return originalRootPattern.test(normalizedScript);
|
|
303
300
|
});
|
|
@@ -26,8 +26,9 @@ import { writeManifest } from "../workflow-manifest.js";
|
|
|
26
26
|
import { appendEvent } from "../workflow-events.js";
|
|
27
27
|
import { logWarning } from "../workflow-logger.js";
|
|
28
28
|
import { validatePathOnlyPlanningFields, validatePlanningPathScope } from "../planning-path-scope.js";
|
|
29
|
-
import {
|
|
29
|
+
import { runTaskPathChecks } from "../pre-execution-checks.js";
|
|
30
30
|
import type { TaskRow } from "../db-task-slice-rows.js";
|
|
31
|
+
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
31
32
|
import { buildTaskFileName, gsdProjectionRoot } from "../paths.js";
|
|
32
33
|
import { loadEffectiveGSDPreferences } from "../preferences.js";
|
|
33
34
|
import { createRepositoryRegistryFromPreferences, defaultRepositoryTargets, type RepositoryRegistry } from "../repository-registry.js";
|
|
@@ -268,11 +269,16 @@ function validateTaskPathsBeforePersist(
|
|
|
268
269
|
const additionalRoots = allowedRoots
|
|
269
270
|
.map((root) => resolve(root))
|
|
270
271
|
.filter((root) => root !== baseRoot);
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
const resolvedCanonicalRoot = resolve(resolveWorktreeProjectRoot(basePath));
|
|
273
|
+
const canonicalProjectRoot = resolvedCanonicalRoot !== baseRoot ? resolvedCanonicalRoot : undefined;
|
|
274
|
+
const hasContext = additionalRoots.length > 0 || canonicalProjectRoot !== undefined;
|
|
275
|
+
const context = hasContext
|
|
276
|
+
? {
|
|
277
|
+
...(additionalRoots.length > 0 ? { additionalRoots } : {}),
|
|
278
|
+
...(canonicalProjectRoot !== undefined ? { canonicalProjectRoot } : {}),
|
|
279
|
+
}
|
|
280
|
+
: undefined;
|
|
281
|
+
const checks = runTaskPathChecks(taskRows, basePath, context);
|
|
276
282
|
const blocking = checks.filter((check) => !check.passed && check.blocking);
|
|
277
283
|
|
|
278
284
|
if (blocking.length === 0) return null;
|
|
@@ -10,16 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
-
getMilestone,
|
|
14
13
|
getMilestoneSlices,
|
|
15
14
|
getSliceTasks,
|
|
16
|
-
|
|
17
|
-
updateSliceStatus,
|
|
18
|
-
updateTaskStatus,
|
|
19
|
-
transaction,
|
|
15
|
+
reopenMilestoneCascade,
|
|
20
16
|
} from "../gsd-db.js";
|
|
21
17
|
import { invalidateStateCache } from "../state.js";
|
|
22
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
23
18
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
24
19
|
import { writeManifest } from "../workflow-manifest.js";
|
|
25
20
|
import { appendEvent } from "../workflow-events.js";
|
|
@@ -53,40 +48,18 @@ export async function handleReopenMilestone(
|
|
|
53
48
|
return { error: "milestoneId is required and must be a non-empty string" };
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
// ──
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
guardError = `milestone not found: ${params.milestoneId}`;
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (!isClosedStatus(milestone.status)) {
|
|
68
|
-
guardError = `milestone ${params.milestoneId} is not closed (status: ${milestone.status}) — nothing to reopen`;
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
reopenMilestoneStatus(params.milestoneId);
|
|
73
|
-
|
|
74
|
-
const slices = getMilestoneSlices(params.milestoneId);
|
|
75
|
-
slicesResetCount = slices.length;
|
|
76
|
-
|
|
77
|
-
for (const slice of slices) {
|
|
78
|
-
updateSliceStatus(params.milestoneId, slice.id, "in_progress");
|
|
79
|
-
const tasks = getSliceTasks(params.milestoneId, slice.id);
|
|
80
|
-
tasksResetCount += tasks.length;
|
|
81
|
-
for (const task of tasks) {
|
|
82
|
-
updateTaskStatus(params.milestoneId, slice.id, task.id, "pending");
|
|
83
|
-
}
|
|
51
|
+
// ── Atomic reopen cascade (guards + writes in one transaction) ───────────
|
|
52
|
+
const outcome = reopenMilestoneCascade(params.milestoneId);
|
|
53
|
+
if (!outcome.ok) {
|
|
54
|
+
switch (outcome.reason) {
|
|
55
|
+
case "milestone-not-found":
|
|
56
|
+
return { error: `milestone not found: ${params.milestoneId}` };
|
|
57
|
+
case "milestone-not-closed":
|
|
58
|
+
return { error: `milestone ${params.milestoneId} is not closed (status: ${outcome.status}) — nothing to reopen` };
|
|
84
59
|
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
if (guardError) {
|
|
88
|
-
return { error: guardError };
|
|
89
60
|
}
|
|
61
|
+
const slicesResetCount = outcome.slicesReset;
|
|
62
|
+
const tasksResetCount = outcome.tasksReset;
|
|
90
63
|
|
|
91
64
|
// ── Invalidate caches ────────────────────────────────────────────────────
|
|
92
65
|
invalidateStateCache();
|