@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
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: ADR-015 Recovery Classification module for runtime failure taxonomy.
|
|
3
3
|
|
|
4
|
+
import { isToolUnavailableError } from "./auto-tool-tracking.js";
|
|
4
5
|
import { classifyError, isTransient, type ErrorClass } from "./error-classifier.js";
|
|
6
|
+
import { recoveryRemediation } from "./guidance.js";
|
|
5
7
|
import { ReconciliationFailedError } from "./state-reconciliation.js";
|
|
8
|
+
import { IllegalPhaseTransitionError } from "./state-transition-matrix.js";
|
|
6
9
|
|
|
7
10
|
export type RecoveryFailureKind =
|
|
8
11
|
| "tool-schema"
|
|
9
12
|
| "tool-contract"
|
|
13
|
+
| "tool-unavailable"
|
|
10
14
|
| "deterministic-policy"
|
|
11
15
|
| "lifecycle-progression"
|
|
12
16
|
| "stale-worker"
|
|
13
17
|
| "worktree-invalid"
|
|
14
18
|
| "verification-drift"
|
|
15
19
|
| "reconciliation-drift"
|
|
20
|
+
| "illegal-transition"
|
|
16
21
|
| "provider"
|
|
17
22
|
| "runtime-unknown";
|
|
18
23
|
|
|
@@ -43,99 +48,53 @@ export function classifyFailure(input: RecoveryClassificationInput): RecoveryCla
|
|
|
43
48
|
const failureKind =
|
|
44
49
|
input.error instanceof ReconciliationFailedError
|
|
45
50
|
? "reconciliation-drift"
|
|
46
|
-
: input.
|
|
51
|
+
: input.error instanceof IllegalPhaseTransitionError
|
|
52
|
+
? "illegal-transition"
|
|
53
|
+
: input.failureKind ?? inferFailureKind(message);
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
failureKind,
|
|
60
|
-
action: "stop",
|
|
61
|
-
reason: `Tool Contract failure${unitSuffix(input)}: ${message}`,
|
|
62
|
-
exitReason: "tool-contract",
|
|
63
|
-
remediation: "Fix the Unit Tool Contract or prompt so the Unit is only asked to use tools owned by its phase.",
|
|
64
|
-
};
|
|
65
|
-
case "deterministic-policy":
|
|
66
|
-
return {
|
|
67
|
-
failureKind,
|
|
68
|
-
action: "stop",
|
|
69
|
-
reason: `Deterministic policy failure${unitSuffix(input)}: ${message}`,
|
|
70
|
-
exitReason: "deterministic-policy",
|
|
71
|
-
remediation: "Resolve the policy blocker; retrying the same Unit will repeat the failure.",
|
|
72
|
-
};
|
|
73
|
-
case "lifecycle-progression":
|
|
74
|
-
return {
|
|
75
|
-
failureKind,
|
|
76
|
-
action: "stop",
|
|
77
|
-
reason: `Lifecycle progression failure${unitSuffix(input)}: ${message}`,
|
|
78
|
-
exitReason: "lifecycle-progression",
|
|
79
|
-
remediation: "Route to the required owning Unit or restore the missing artifact before advancing lifecycle state.",
|
|
80
|
-
};
|
|
81
|
-
case "stale-worker":
|
|
82
|
-
return {
|
|
83
|
-
failureKind,
|
|
84
|
-
action: "stop",
|
|
85
|
-
reason: `Stale worker failure${unitSuffix(input)}: ${message}`,
|
|
86
|
-
exitReason: "stale-worker",
|
|
87
|
-
remediation: "Clear or reconcile the stale worker before dispatching another Unit.",
|
|
88
|
-
};
|
|
89
|
-
case "worktree-invalid":
|
|
90
|
-
return {
|
|
91
|
-
failureKind,
|
|
92
|
-
action: "stop",
|
|
93
|
-
reason: `Worktree invalid${unitSuffix(input)}: ${message}`,
|
|
94
|
-
exitReason: "worktree-invalid",
|
|
95
|
-
remediation: "Repair or recreate the milestone worktree before launching source-writing Units.",
|
|
96
|
-
};
|
|
97
|
-
case "verification-drift":
|
|
98
|
-
return {
|
|
99
|
-
failureKind,
|
|
100
|
-
action: "escalate",
|
|
101
|
-
reason: `Verification drift${unitSuffix(input)}: ${message}`,
|
|
102
|
-
exitReason: "verification-drift",
|
|
103
|
-
remediation: "Inspect the verification artifact and reconcile the state snapshot before resuming.",
|
|
104
|
-
};
|
|
105
|
-
case "reconciliation-drift":
|
|
106
|
-
return {
|
|
107
|
-
failureKind,
|
|
108
|
-
action: "escalate",
|
|
109
|
-
reason: `Reconciliation drift${unitSuffix(input)}: ${message}`,
|
|
110
|
-
exitReason: "reconciliation-drift",
|
|
111
|
-
remediation:
|
|
112
|
-
"Inspect the persistent or repair-failed drift kinds reported by the State Reconciliation Module before resuming.",
|
|
113
|
-
};
|
|
114
|
-
case "provider": {
|
|
115
|
-
const providerClass = classifyError(message, input.retryAfterMs);
|
|
116
|
-
return {
|
|
117
|
-
failureKind,
|
|
118
|
-
action: isTransient(providerClass) ? "retry" : "escalate",
|
|
119
|
-
reason: message,
|
|
120
|
-
exitReason: `provider-${providerClass.kind}`,
|
|
121
|
-
remediation: isTransient(providerClass)
|
|
122
|
-
? "Retry after the provider/network condition clears."
|
|
123
|
-
: "Inspect provider credentials, model entitlement, or request shape.",
|
|
124
|
-
providerClass: providerClass.kind,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
case "runtime-unknown":
|
|
128
|
-
return {
|
|
129
|
-
failureKind,
|
|
130
|
-
action: "escalate",
|
|
131
|
-
reason: message,
|
|
132
|
-
exitReason: "runtime-unknown",
|
|
133
|
-
remediation: "Inspect the runtime error and add a dedicated classification if it is repeatable.",
|
|
134
|
-
};
|
|
55
|
+
if (failureKind === "provider") {
|
|
56
|
+
const providerClass = classifyError(message, input.retryAfterMs);
|
|
57
|
+
const transient = isTransient(providerClass);
|
|
58
|
+
return {
|
|
59
|
+
failureKind,
|
|
60
|
+
action: transient ? "retry" : "escalate",
|
|
61
|
+
reason: message,
|
|
62
|
+
exitReason: `provider-${providerClass.kind}`,
|
|
63
|
+
remediation: recoveryRemediation(transient ? "provider-transient" : "provider-permanent"),
|
|
64
|
+
providerClass: providerClass.kind,
|
|
65
|
+
};
|
|
135
66
|
}
|
|
67
|
+
|
|
68
|
+
const { action, label } = FAILURE_TAXONOMY[failureKind];
|
|
69
|
+
return {
|
|
70
|
+
failureKind,
|
|
71
|
+
action,
|
|
72
|
+
reason: label ? `${label}${unitSuffix(input)}: ${message}` : message,
|
|
73
|
+
exitReason: failureKind,
|
|
74
|
+
remediation: recoveryRemediation(failureKind),
|
|
75
|
+
};
|
|
136
76
|
}
|
|
137
77
|
|
|
78
|
+
/** Per-kind action and reason label. Remediation lives in the Guidance module. */
|
|
79
|
+
const FAILURE_TAXONOMY: Record<
|
|
80
|
+
Exclude<RecoveryFailureKind, "provider">,
|
|
81
|
+
{ action: RecoveryAction; label: string | null }
|
|
82
|
+
> = {
|
|
83
|
+
"tool-schema": { action: "stop", label: "Tool schema failure" },
|
|
84
|
+
"tool-contract": { action: "stop", label: "Tool Contract failure" },
|
|
85
|
+
"tool-unavailable": { action: "retry", label: "Tool unavailable" },
|
|
86
|
+
"deterministic-policy": { action: "stop", label: "Deterministic policy failure" },
|
|
87
|
+
"lifecycle-progression": { action: "stop", label: "Lifecycle progression failure" },
|
|
88
|
+
"stale-worker": { action: "stop", label: "Stale worker failure" },
|
|
89
|
+
"worktree-invalid": { action: "stop", label: "Worktree invalid" },
|
|
90
|
+
"verification-drift": { action: "escalate", label: "Verification drift" },
|
|
91
|
+
"reconciliation-drift": { action: "escalate", label: "Reconciliation drift" },
|
|
92
|
+
"illegal-transition": { action: "escalate", label: "Illegal phase transition" },
|
|
93
|
+
"runtime-unknown": { action: "escalate", label: null },
|
|
94
|
+
};
|
|
95
|
+
|
|
138
96
|
function inferFailureKind(message: string): RecoveryFailureKind {
|
|
97
|
+
if (isToolUnavailableError(message)) return "tool-unavailable";
|
|
139
98
|
if (/tool contract|auto-unit tool scope|phase-boundary gate|not permitted.*own/i.test(message)) return "tool-contract";
|
|
140
99
|
if (/lifecycle progression|required artifact|missing .*assessment|missing .*closeout|cannot legally (?:advance|progress)/i.test(message)) return "lifecycle-progression";
|
|
141
100
|
if (/schema|invalid.*tool|tool.*invalid|enum/i.test(message)) return "tool-schema";
|
|
@@ -57,9 +57,10 @@ const EXECUTION_TOOL_NAMES = new Set([
|
|
|
57
57
|
"functions.exec_command",
|
|
58
58
|
"gsd_exec",
|
|
59
59
|
"gsd_exec_search",
|
|
60
|
+
"gsd_uat_exec",
|
|
60
61
|
"powershell",
|
|
61
62
|
]);
|
|
62
|
-
const MCP_EXECUTION_TOOL_RE = /^mcp__.+
|
|
63
|
+
const MCP_EXECUTION_TOOL_RE = /^mcp__.+__gsd_(?:uat_)?exec(?:_search)?$/;
|
|
63
64
|
|
|
64
65
|
// ─── Module State ───────────────────────────────────────────────────────────
|
|
65
66
|
|
|
@@ -206,11 +207,17 @@ export function clearEvidenceFromDisk(
|
|
|
206
207
|
* Exit codes and output are filled in by recordToolResult after execution.
|
|
207
208
|
*/
|
|
208
209
|
export function recordToolCall(toolCallId: string, toolName: string, input: Record<string, unknown>): void {
|
|
210
|
+
// Idempotent by toolCallId: native tools reach this via both
|
|
211
|
+
// tool_execution_start and tool_call; external (pre-executed) tools only
|
|
212
|
+
// via tool_execution_start. First recording wins.
|
|
213
|
+
if (unitEvidence.some(e => e.toolCallId === toolCallId)) return;
|
|
209
214
|
if (isExecutionToolName(toolName)) {
|
|
210
215
|
unitEvidence.push({
|
|
211
216
|
kind: "bash",
|
|
212
217
|
toolCallId,
|
|
213
|
-
|
|
218
|
+
// gsd_exec / gsd_uat_exec carry the script body in `script` (or `code`);
|
|
219
|
+
// bash-style tools use `command`/`cmd`; gsd_exec_search uses `query`.
|
|
220
|
+
command: String(input.command ?? input.script ?? input.cmd ?? input.code ?? input.query ?? ""),
|
|
214
221
|
exitCode: -1,
|
|
215
222
|
outputSnippet: "",
|
|
216
223
|
timestamp: Date.now(),
|
|
@@ -249,11 +256,36 @@ export function recordToolResult(
|
|
|
249
256
|
if (entry.kind === "bash") {
|
|
250
257
|
const text = extractResultText(result);
|
|
251
258
|
entry.outputSnippet = text.slice(0, 500);
|
|
252
|
-
|
|
253
|
-
entry.exitCode = exitMatch ? Number(exitMatch[1]) : (isError ? 1 : 0);
|
|
259
|
+
entry.exitCode = resolveExitCode(text, isError);
|
|
254
260
|
}
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Resolve the exit code from a tool result's text. Handles the bash tool's
|
|
265
|
+
* prose marker, the gsd_exec / gsd_uat_exec JSON envelope (`"exit_code": N`),
|
|
266
|
+
* and a last-resort read of the run's persisted `.gsd/exec/<id>.meta.json`
|
|
267
|
+
* (covers truncated result text).
|
|
268
|
+
*/
|
|
269
|
+
function resolveExitCode(text: string, isError: boolean): number {
|
|
270
|
+
const proseMatch = text.match(/Command exited with code (\d+)/);
|
|
271
|
+
if (proseMatch) return Number(proseMatch[1]);
|
|
272
|
+
|
|
273
|
+
const jsonMatch = text.match(/"exit_code"\s*:\s*(-?\d+)/);
|
|
274
|
+
if (jsonMatch) return Number(jsonMatch[1]);
|
|
275
|
+
|
|
276
|
+
const metaMatch = text.match(/"meta_path"\s*:\s*"([^"]+)"/);
|
|
277
|
+
if (metaMatch) {
|
|
278
|
+
try {
|
|
279
|
+
const meta = JSON.parse(readFileSync(metaMatch[1], "utf-8")) as Record<string, unknown>;
|
|
280
|
+
if (typeof meta.exit_code === "number") return meta.exit_code;
|
|
281
|
+
} catch {
|
|
282
|
+
// Fall through to the isError heuristic
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return isError ? 1 : 0;
|
|
287
|
+
}
|
|
288
|
+
|
|
257
289
|
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
258
290
|
|
|
259
291
|
function extractResultText(result: unknown): string {
|
|
@@ -121,9 +121,14 @@ function findMatches(
|
|
|
121
121
|
const exact = bashCalls.filter(b => b.command.trim() === normalized);
|
|
122
122
|
if (exact.length > 0) return exact;
|
|
123
123
|
|
|
124
|
-
// Substring match: claimed is contained in actual or actual in claimed
|
|
124
|
+
// Substring match: claimed is contained in actual or actual in claimed.
|
|
125
|
+
// A claimed verification command typically appears verbatim inside a
|
|
126
|
+
// larger gsd_exec script body (cd prefix, multi-line scripts), so
|
|
127
|
+
// script-containing-claim is the common direction. Blank-command entries
|
|
128
|
+
// must be excluded — `"x".includes("")` is true, so they'd match anything.
|
|
125
129
|
const substring = bashCalls.filter(
|
|
126
|
-
b => b.command.
|
|
130
|
+
b => b.command.trim().length > 0 &&
|
|
131
|
+
(b.command.includes(normalized) || normalized.includes(b.command)),
|
|
127
132
|
);
|
|
128
133
|
if (substring.length > 0) return substring;
|
|
129
134
|
|
|
@@ -39,6 +39,20 @@ export interface FileChangeAudit {
|
|
|
39
39
|
|
|
40
40
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Build the effective allowlist for a unit's file-change audit.
|
|
44
|
+
*
|
|
45
|
+
* When GSD manages .gitignore (manage_gitignore unset or true), ensureGitignore()
|
|
46
|
+
* appends baseline patterns at auto-start and the edit rides into the task's
|
|
47
|
+
* auto-commit — so .gitignore changes must not be attributed to the task.
|
|
48
|
+
*/
|
|
49
|
+
export function effectiveFileChangeAllowlist(
|
|
50
|
+
baseAllowlist: string[],
|
|
51
|
+
manageGitignore: boolean | undefined,
|
|
52
|
+
): string[] {
|
|
53
|
+
return manageGitignore === false ? baseAllowlist : [...baseAllowlist, ".gitignore"];
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
/**
|
|
43
57
|
* Validate file changes after auto-commit for an execute-task unit.
|
|
44
58
|
* Returns null if task data is unavailable or DB is not loaded.
|
|
@@ -131,6 +131,48 @@ export function findTransition(
|
|
|
131
131
|
);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Edge-keyed legality check for the Phase Transition Invariant (ADR-030).
|
|
136
|
+
* `advance()` derives the next Phase and asserts the (from → to) edge here.
|
|
137
|
+
*
|
|
138
|
+
* The matrix is an assertion, not a decision-maker — `deriveState` already
|
|
139
|
+
* chose the Phase. A self-edge is trivially legal (no transition to assert). An
|
|
140
|
+
* edge is legal when some matrix entry permits it, honoring the `*` wildcard
|
|
141
|
+
* rows (e.g. any → blocked via manual-block, any → executing via
|
|
142
|
+
* retryable-failure).
|
|
143
|
+
*
|
|
144
|
+
* Note: the matrix is currently a sparse hardening spec, not a complete
|
|
145
|
+
* legal-edge graph, so `advance()` consumes this in advisory mode (telemetry
|
|
146
|
+
* only). It must be expanded to cover every edge `deriveState` emits before
|
|
147
|
+
* enforcement flips on.
|
|
148
|
+
*/
|
|
149
|
+
export function isLegalEdge(from: Phase, to: Phase): boolean {
|
|
150
|
+
if (from === to) return true;
|
|
151
|
+
return STATE_TRANSITION_MATRIX.some(
|
|
152
|
+
(entry) => (entry.from === from || entry.from === "*") && entry.to === to,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Thrown when an illegal derived Phase edge survives reconciliation. Carries
|
|
158
|
+
* both endpoints so Recovery Classification can report them. Recognized by
|
|
159
|
+
* class in `classifyFailure` and mapped to the `illegal-transition` kind.
|
|
160
|
+
*/
|
|
161
|
+
export class IllegalPhaseTransitionError extends Error {
|
|
162
|
+
// Explicit fields, not constructor parameter properties — strip-types
|
|
163
|
+
// consumers (workspace-index subprocess, integration tests) reject the
|
|
164
|
+
// parameter-property syntax with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.
|
|
165
|
+
readonly from: Phase;
|
|
166
|
+
readonly to: Phase;
|
|
167
|
+
|
|
168
|
+
constructor(from: Phase, to: Phase) {
|
|
169
|
+
super(`Illegal phase transition ${from} -> ${to} survived reconciliation`);
|
|
170
|
+
this.name = "IllegalPhaseTransitionError";
|
|
171
|
+
this.from = from;
|
|
172
|
+
this.to = to;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
134
176
|
export function validateTransitionMatrix(requiredEvents: readonly string[]): MatrixValidationResult {
|
|
135
177
|
const seen = new Set<string>();
|
|
136
178
|
const duplicateKeys: string[] = [];
|
|
@@ -71,27 +71,10 @@ import {
|
|
|
71
71
|
readinessNeedsDiscussion,
|
|
72
72
|
} from './milestone-readiness.js';
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`1. Review the validation details: \`/gsd status\``,
|
|
79
|
-
`2. If you fixed the missing evidence or issue, re-run milestone validation: \`/gsd validate-milestone\``,
|
|
80
|
-
`3. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
|
|
81
|
-
`4. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
|
|
82
|
-
`After validation or override passes, run \`/gsd auto\` to complete and merge the milestone.`,
|
|
83
|
-
].join("\n");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function formatNeedsRemediationBlocker(milestoneId: string): string {
|
|
87
|
-
return [
|
|
88
|
-
`Milestone ${milestoneId} is blocked because milestone validation returned needs-remediation, but all slices are complete.`,
|
|
89
|
-
`Fix options:`,
|
|
90
|
-
`1. Run \`/gsd dispatch reassess\` to add remediation slices, then run \`/gsd auto\``,
|
|
91
|
-
`2. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
|
|
92
|
-
`3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
|
|
93
|
-
].join("\n");
|
|
94
|
-
}
|
|
74
|
+
import {
|
|
75
|
+
needsAttentionBlockerGuidance as formatNeedsAttentionBlocker,
|
|
76
|
+
needsRemediationBlockerGuidance as formatNeedsRemediationBlocker,
|
|
77
|
+
} from './guidance.js';
|
|
95
78
|
|
|
96
79
|
/**
|
|
97
80
|
* A "ghost" milestone directory contains only META.json (and no substantive
|
|
@@ -1,17 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Status predicates for GSD state-machine
|
|
2
|
+
* Status predicates and the canonical status vocabulary for GSD state-machine
|
|
3
|
+
* guards (ADR-030).
|
|
3
4
|
*
|
|
4
|
-
* The DB
|
|
5
|
-
*
|
|
6
|
-
* "closed" (legacy/imported), and
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* The DB column is free-form `string` so legacy/imported rows still load. Three
|
|
6
|
+
* raw values besides canonical "complete"/"skipped" indicate "closed": "done"
|
|
7
|
+
* (legacy alias), "closed" (legacy/imported), and "skipped" (user-directed skip
|
|
8
|
+
* via rethink or backtrack). `RAW_CLOSED_STATUSES` is the single source for both
|
|
9
|
+
* `isClosedStatus()` and the SQL terminal-status fragment
|
|
10
|
+
* (`db/sql-constants.ts` derives `TERMINAL_STATUS_SQL` from it), replacing the
|
|
11
|
+
* prior independent definitions.
|
|
12
|
+
*
|
|
13
|
+
* `toStatus()` is the single seam where a free-form string becomes the canonical
|
|
14
|
+
* `Status` vocabulary; the Status Transition Core writes canonical, so the store
|
|
15
|
+
* converges over time without a forced migration.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Canonical, normalized entity-status vocabulary across milestones, slices, and
|
|
20
|
+
* tasks — the single source for both the `Status` type and the runtime
|
|
21
|
+
* membership set. The in-memory domain speaks `Status`; the DB column stays
|
|
22
|
+
* free-form.
|
|
23
|
+
*/
|
|
24
|
+
export const CANONICAL_STATUSES = [
|
|
25
|
+
"pending", "queued", "active", "parked", "in_progress", "blocked", "complete", "skipped", "deferred",
|
|
26
|
+
] as const;
|
|
27
|
+
export type Status = (typeof CANONICAL_STATUSES)[number];
|
|
28
|
+
const CANONICAL_STATUS_SET: ReadonlySet<string> = new Set(CANONICAL_STATUSES);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Raw status values that mean a unit is closed — the single source of truth.
|
|
32
|
+
* Includes legacy/imported aliases ("done", "closed") alongside canonical
|
|
33
|
+
* "complete"/"skipped" because the DB column is free-form and older rows /
|
|
34
|
+
* imports still carry them. Order matters: `TERMINAL_STATUS_SQL` is derived
|
|
35
|
+
* from this array verbatim.
|
|
36
|
+
*/
|
|
37
|
+
export const RAW_CLOSED_STATUSES = ["complete", "done", "skipped", "closed"] as const;
|
|
38
|
+
const RAW_CLOSED_SET: ReadonlySet<string> = new Set(RAW_CLOSED_STATUSES);
|
|
39
|
+
|
|
40
|
+
/** Free-form aliases mapped to their canonical Status on read. */
|
|
41
|
+
const ALIAS_TO_CANONICAL: Readonly<Record<string, Status>> = {
|
|
42
|
+
done: "complete",
|
|
43
|
+
closed: "complete",
|
|
44
|
+
planned: "pending",
|
|
45
|
+
"in-progress": "in_progress",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalize a free-form DB status string into the canonical `Status`
|
|
50
|
+
* vocabulary. Maps known aliases (done/closed → complete, planned → pending,
|
|
51
|
+
* in-progress → in_progress). An unrecognized/legacy value is **quarantined** —
|
|
52
|
+
* preserved verbatim rather than silently remapped to a wrong canonical state —
|
|
53
|
+
* so reads never fail and reconciliation/telemetry can surface it.
|
|
10
54
|
*/
|
|
55
|
+
export function toStatus(raw: string): Status {
|
|
56
|
+
const value = raw.trim();
|
|
57
|
+
if (CANONICAL_STATUS_SET.has(value)) return value as Status;
|
|
58
|
+
const alias = ALIAS_TO_CANONICAL[value];
|
|
59
|
+
if (alias) return alias;
|
|
60
|
+
return value as Status;
|
|
61
|
+
}
|
|
11
62
|
|
|
12
63
|
/** Returns true when a milestone, slice, or task status indicates closure. */
|
|
13
64
|
export function isClosedStatus(status: string): boolean {
|
|
14
|
-
return status
|
|
65
|
+
return RAW_CLOSED_SET.has(status);
|
|
15
66
|
}
|
|
16
67
|
|
|
17
68
|
/** Returns true when a slice status indicates it was deferred by a decision. */
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Stop Notice module — single owner of the auto/step-mode
|
|
3
|
+
// stop/pause notice vocabulary. Both sides of the wire live here: the
|
|
4
|
+
// formatters that produce the canonical prefixes (used by stopAuto/pauseAuto)
|
|
5
|
+
// and the classifiers that recognize them (used by the headless host to pick
|
|
6
|
+
// exit codes). Wording changes in this file keep emitter and detector in
|
|
7
|
+
// lockstep; round-trip tests enforce it.
|
|
8
|
+
|
|
9
|
+
export type StopNoticeKind = "stopped" | "blocked";
|
|
10
|
+
|
|
11
|
+
/** A reason string of the form "Blocked: …" marks a blocked stop. */
|
|
12
|
+
export function isBlockedStopReason(reason?: string | null): boolean {
|
|
13
|
+
return /^Blocked:\s*/i.test(reason ?? "");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Strip the "Blocked: " marker for display. */
|
|
17
|
+
export function stopNoticeDisplayReason(reason?: string | null): string {
|
|
18
|
+
return (reason ?? "").replace(/^Blocked:\s*/i, "").trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function stopNoticeKind(reason?: string | null): StopNoticeKind {
|
|
22
|
+
return isBlockedStopReason(reason) ? "blocked" : "stopped";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Canonical stop-notice prefix: "Auto-mode blocked — reason" / "Auto-mode stopped". */
|
|
26
|
+
export function formatStopNoticePrefix(reason?: string | null): string {
|
|
27
|
+
const displayReason = stopNoticeDisplayReason(reason);
|
|
28
|
+
const prefix = stopNoticeKind(reason) === "blocked" ? "Auto-mode blocked" : "Auto-mode stopped";
|
|
29
|
+
return displayReason ? `${prefix} — ${displayReason}` : prefix;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── Classification (headless host side) ────────────────────────────────
|
|
33
|
+
// The canonical lowercase prefixes the headless event loop recognizes in
|
|
34
|
+
// notify messages. Emitters above and ad-hoc emitters elsewhere must start
|
|
35
|
+
// their terminal notices with one of these.
|
|
36
|
+
|
|
37
|
+
export const PAUSED_NOTICE_PREFIXES = ["auto-mode paused", "step-mode paused"] as const;
|
|
38
|
+
|
|
39
|
+
export const TERMINAL_NOTICE_PREFIXES = [
|
|
40
|
+
"auto-mode stopped",
|
|
41
|
+
"step-mode stopped",
|
|
42
|
+
"auto-mode complete",
|
|
43
|
+
"no active milestone",
|
|
44
|
+
"auto-mode idle",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
/** Manual-resolution notices emitted before auto-mode can formally pause/stop. */
|
|
48
|
+
export function isManualResolutionNotice(message: string): boolean {
|
|
49
|
+
return (
|
|
50
|
+
message.includes("resolve manually and re-run /gsd auto") ||
|
|
51
|
+
message.includes("resolve conflicts manually and run /gsd auto to resume") ||
|
|
52
|
+
message.includes("resolve and run /gsd auto to resume")
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isPauseNotice(message: string): boolean {
|
|
57
|
+
return PAUSED_NOTICE_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isTerminalNotice(message: string): boolean {
|
|
61
|
+
return TERMINAL_NOTICE_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Pauses that do not require operator intervention in headless mode. */
|
|
65
|
+
export function isNonBlockingPauseNotice(message: string): boolean {
|
|
66
|
+
return message.includes("idempotent advance: unit already active");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isBlockedNoticeMessage(message: string): boolean {
|
|
70
|
+
return (
|
|
71
|
+
message.includes("blocked:") ||
|
|
72
|
+
(isPauseNotice(message) && !isNonBlockingPauseNotice(message)) ||
|
|
73
|
+
isManualResolutionNotice(message)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -5517,6 +5517,129 @@ test("dispatch Worktree Safety wins before stuck detection for execute-task with
|
|
|
5517
5517
|
);
|
|
5518
5518
|
});
|
|
5519
5519
|
|
|
5520
|
+
test("dispatch Worktree Safety honors degraded branch fallback instead of demanding the canonical worktree root", async (t) => {
|
|
5521
|
+
_resetPendingResolve();
|
|
5522
|
+
|
|
5523
|
+
const ctx = makeMockCtx();
|
|
5524
|
+
const pi = makeMockPi();
|
|
5525
|
+
const notifications: string[] = [];
|
|
5526
|
+
ctx.ui.notify = (msg: string) => { notifications.push(msg); };
|
|
5527
|
+
|
|
5528
|
+
// Worktree creation failed and the lifecycle fell back to the milestone
|
|
5529
|
+
// branch in the project root. The safety gate must validate against that
|
|
5530
|
+
// effective branch mode, not the configured worktree mode.
|
|
5531
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-wt-safety-degraded-"));
|
|
5532
|
+
t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
|
|
5533
|
+
|
|
5534
|
+
const s = makeLoopSession({
|
|
5535
|
+
basePath: projectRoot,
|
|
5536
|
+
originalBasePath: projectRoot,
|
|
5537
|
+
canonicalProjectRoot: projectRoot,
|
|
5538
|
+
isolationDegraded: true,
|
|
5539
|
+
});
|
|
5540
|
+
const deps = makeMockDeps({
|
|
5541
|
+
getIsolationMode: () => "worktree",
|
|
5542
|
+
});
|
|
5543
|
+
const result = await runDispatch(
|
|
5544
|
+
{
|
|
5545
|
+
ctx,
|
|
5546
|
+
pi,
|
|
5547
|
+
s,
|
|
5548
|
+
deps,
|
|
5549
|
+
prefs: undefined,
|
|
5550
|
+
iteration: 1,
|
|
5551
|
+
flowId: "test-flow",
|
|
5552
|
+
nextSeq: () => 1,
|
|
5553
|
+
},
|
|
5554
|
+
{
|
|
5555
|
+
state: {
|
|
5556
|
+
phase: "executing",
|
|
5557
|
+
activeMilestone: { id: "M001", title: "Test", status: "active" },
|
|
5558
|
+
activeSlice: { id: "S01", title: "Slice 1" },
|
|
5559
|
+
activeTask: { id: "T01" },
|
|
5560
|
+
registry: [{ id: "M001", status: "active" }],
|
|
5561
|
+
blockers: [],
|
|
5562
|
+
} as any,
|
|
5563
|
+
mid: "M001",
|
|
5564
|
+
midTitle: "Test",
|
|
5565
|
+
},
|
|
5566
|
+
{
|
|
5567
|
+
recentUnits: [],
|
|
5568
|
+
stuckRecoveryAttempts: 0,
|
|
5569
|
+
consecutiveFinalizeTimeouts: 0,
|
|
5570
|
+
},
|
|
5571
|
+
);
|
|
5572
|
+
|
|
5573
|
+
assert.equal(result.action, "next", "dispatch must proceed under degraded branch isolation");
|
|
5574
|
+
assert.ok(
|
|
5575
|
+
!notifications.some((n) => n.includes("Worktree Safety failed")),
|
|
5576
|
+
"degraded branch fallback must not trip a false invalid-root",
|
|
5577
|
+
);
|
|
5578
|
+
assert.ok(!deps.callLog.includes("stopAuto"), "auto-mode must not stop on the degraded fallback");
|
|
5579
|
+
});
|
|
5580
|
+
|
|
5581
|
+
test("dispatch Worktree Safety honors stranded branch recovery instead of demanding the canonical worktree root", async (t) => {
|
|
5582
|
+
_resetPendingResolve();
|
|
5583
|
+
|
|
5584
|
+
const ctx = makeMockCtx();
|
|
5585
|
+
const pi = makeMockPi();
|
|
5586
|
+
const notifications: string[] = [];
|
|
5587
|
+
ctx.ui.notify = (msg: string) => { notifications.push(msg); };
|
|
5588
|
+
|
|
5589
|
+
// Bootstrap adopted stranded work by checking out the milestone branch in
|
|
5590
|
+
// the project root (strandedRecoveryIsolationMode = "branch"). Isolation is
|
|
5591
|
+
// NOT degraded — the adoption is intentional. The safety gate must validate
|
|
5592
|
+
// against the effective branch mode, not the configured worktree mode.
|
|
5593
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-wt-safety-stranded-"));
|
|
5594
|
+
t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
|
|
5595
|
+
|
|
5596
|
+
const s = makeLoopSession({
|
|
5597
|
+
basePath: projectRoot,
|
|
5598
|
+
originalBasePath: projectRoot,
|
|
5599
|
+
canonicalProjectRoot: projectRoot,
|
|
5600
|
+
strandedRecoveryIsolationMode: "branch",
|
|
5601
|
+
});
|
|
5602
|
+
const deps = makeMockDeps({
|
|
5603
|
+
getIsolationMode: () => "worktree",
|
|
5604
|
+
});
|
|
5605
|
+
const result = await runDispatch(
|
|
5606
|
+
{
|
|
5607
|
+
ctx,
|
|
5608
|
+
pi,
|
|
5609
|
+
s,
|
|
5610
|
+
deps,
|
|
5611
|
+
prefs: undefined,
|
|
5612
|
+
iteration: 1,
|
|
5613
|
+
flowId: "test-flow",
|
|
5614
|
+
nextSeq: () => 1,
|
|
5615
|
+
},
|
|
5616
|
+
{
|
|
5617
|
+
state: {
|
|
5618
|
+
phase: "executing",
|
|
5619
|
+
activeMilestone: { id: "M001", title: "Test", status: "active" },
|
|
5620
|
+
activeSlice: { id: "S01", title: "Slice 1" },
|
|
5621
|
+
activeTask: { id: "T01" },
|
|
5622
|
+
registry: [{ id: "M001", status: "active" }],
|
|
5623
|
+
blockers: [],
|
|
5624
|
+
} as any,
|
|
5625
|
+
mid: "M001",
|
|
5626
|
+
midTitle: "Test",
|
|
5627
|
+
},
|
|
5628
|
+
{
|
|
5629
|
+
recentUnits: [],
|
|
5630
|
+
stuckRecoveryAttempts: 0,
|
|
5631
|
+
consecutiveFinalizeTimeouts: 0,
|
|
5632
|
+
},
|
|
5633
|
+
);
|
|
5634
|
+
|
|
5635
|
+
assert.equal(result.action, "next", "dispatch must proceed under stranded branch recovery");
|
|
5636
|
+
assert.ok(
|
|
5637
|
+
!notifications.some((n) => n.includes("Worktree Safety failed")),
|
|
5638
|
+
"stranded branch recovery must not trip a false invalid-root",
|
|
5639
|
+
);
|
|
5640
|
+
assert.ok(!deps.callLog.includes("stopAuto"), "auto-mode must not stop on stranded branch recovery");
|
|
5641
|
+
});
|
|
5642
|
+
|
|
5520
5643
|
test("runDispatch runs stuck detection while artifact verification retry is pending (#5719)", async (t) => {
|
|
5521
5644
|
_resetPendingResolve();
|
|
5522
5645
|
|
|
@@ -306,7 +306,9 @@ test("pauseAuto records the expected worktree path when paused from project root
|
|
|
306
306
|
|
|
307
307
|
const meta = readPausedSessionMetadata(base);
|
|
308
308
|
assert.ok(meta);
|
|
309
|
-
|
|
309
|
+
// No worktree exists yet, so the recorded path is the canonical
|
|
310
|
+
// .gsd-worktrees/ creation location (worktree-placement seam).
|
|
311
|
+
assert.equal(meta.worktreePath, join(base, ".gsd-worktrees", "M001"));
|
|
310
312
|
} finally {
|
|
311
313
|
autoSession.reset();
|
|
312
314
|
try {
|