gsd-pi 2.81.0 → 2.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -24
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +111 -8
- package/dist/resources/extensions/gsd/auto/phases.js +190 -97
- package/dist/resources/extensions/gsd/auto/run-unit.js +66 -3
- package/dist/resources/extensions/gsd/auto/session.js +9 -0
- package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +182 -178
- package/dist/resources/extensions/gsd/auto-dispatch.js +14 -11
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +6 -181
- package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
- package/dist/resources/extensions/gsd/auto-start.js +20 -23
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
- package/dist/resources/extensions/gsd/auto-verification.js +12 -6
- package/dist/resources/extensions/gsd/auto-worktree.js +8 -0
- package/dist/resources/extensions/gsd/auto.js +265 -76
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +13 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
- package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
- package/dist/resources/extensions/gsd/git-service.js +2 -1
- package/dist/resources/extensions/gsd/gsd-db.js +7 -23
- package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
- package/dist/resources/extensions/gsd/health-widget.js +4 -10
- package/dist/resources/extensions/gsd/markdown-renderer.js +0 -95
- package/dist/resources/extensions/gsd/native-git-bridge.js +14 -14
- package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
- package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +20 -2
- package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
- package/dist/resources/extensions/gsd/session-lock.js +40 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +247 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +50 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +87 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
- package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
- package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
- package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
- package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
- package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
- package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
- package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +722 -316
- package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -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/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/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/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/welcome-screen.d.ts +0 -7
- package/dist/welcome-screen.js +60 -69
- package/package.json +1 -1
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/package.json +2 -2
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +76 -9
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +13 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -41
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +86 -82
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +113 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +15 -42
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -104
- package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
- package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.js +9 -6
- package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
- package/packages/pi-tui/src/overlay-layout.ts +10 -7
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
- package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +105 -1
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +27 -26
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +9 -5
- package/src/resources/extensions/gsd/auto/loop.ts +113 -9
- package/src/resources/extensions/gsd/auto/phases.ts +144 -19
- package/src/resources/extensions/gsd/auto/run-unit.ts +69 -4
- package/src/resources/extensions/gsd/auto/session.ts +10 -0
- package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +230 -183
- package/src/resources/extensions/gsd/auto-dispatch.ts +15 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -209
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +22 -22
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
- package/src/resources/extensions/gsd/auto-verification.ts +12 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +8 -0
- package/src/resources/extensions/gsd/auto.ts +295 -75
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -2
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
- package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
- package/src/resources/extensions/gsd/git-service.ts +2 -0
- package/src/resources/extensions/gsd/gsd-db.ts +7 -23
- package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
- package/src/resources/extensions/gsd/health-widget.ts +6 -10
- package/src/resources/extensions/gsd/journal.ts +2 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +4 -95
- package/src/resources/extensions/gsd/native-git-bridge.ts +14 -13
- package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
- package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
- package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +20 -2
- package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
- package/src/resources/extensions/gsd/session-lock.ts +41 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +337 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +69 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +109 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
- package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
- package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
- package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +654 -176
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +291 -4
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +28 -1
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +20 -2
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +46 -11
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
- package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -217
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +65 -58
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +952 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +158 -58
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +572 -118
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +18 -0
- package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
- package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
- package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +1151 -524
- package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1544
- /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_ssgManifest.js +0 -0
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// File Purpose: Behavior tests for auto-loop cleanup after paused provider exits.
|
|
3
3
|
import { test } from "node:test";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
5
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
|
-
import { cleanupAfterLoopExit, rerootCommandSession } from "../auto.ts";
|
|
9
|
+
import { cleanupAfterLoopExit, rerootCommandSession, stopAuto } from "../auto.ts";
|
|
10
10
|
import { autoSession } from "../auto-runtime-state.ts";
|
|
11
|
+
import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
|
|
12
|
+
import { WorktreeLifecycle } from "../worktree-lifecycle.ts";
|
|
11
13
|
|
|
12
14
|
test("cleanupAfterLoopExit preserves paused auto badge after provider pause", async () => {
|
|
13
15
|
const base = mkdtempSync(join(tmpdir(), "gsd-paused-cleanup-"));
|
|
@@ -41,7 +43,7 @@ test("cleanupAfterLoopExit preserves paused auto badge after provider pause", as
|
|
|
41
43
|
}
|
|
42
44
|
});
|
|
43
45
|
|
|
44
|
-
test("cleanupAfterLoopExit clears status
|
|
46
|
+
test("cleanupAfterLoopExit clears status without replacing the last auto surface", async () => {
|
|
45
47
|
const statusCalls: unknown[] = [];
|
|
46
48
|
const widgetCalls: unknown[] = [];
|
|
47
49
|
|
|
@@ -60,7 +62,16 @@ test("cleanupAfterLoopExit clears status and widget when auto is not paused", as
|
|
|
60
62
|
} as any);
|
|
61
63
|
|
|
62
64
|
assert.deepEqual(statusCalls, [["gsd-auto", undefined]]);
|
|
63
|
-
assert.
|
|
65
|
+
assert.equal(
|
|
66
|
+
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
|
|
67
|
+
false,
|
|
68
|
+
"cleanup must not clear the last meaningful auto progress surface",
|
|
69
|
+
);
|
|
70
|
+
assert.equal(
|
|
71
|
+
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-outcome"),
|
|
72
|
+
false,
|
|
73
|
+
"cleanup must not replace the auto deck with a generic loop-ended card",
|
|
74
|
+
);
|
|
64
75
|
assert.equal(autoSession.active, false);
|
|
65
76
|
assert.equal(autoSession.paused, false);
|
|
66
77
|
} finally {
|
|
@@ -68,6 +79,122 @@ test("cleanupAfterLoopExit clears status and widget when auto is not paused", as
|
|
|
68
79
|
}
|
|
69
80
|
});
|
|
70
81
|
|
|
82
|
+
test("cleanupAfterLoopExit preserves completion roll-up after stopAuto reset", async () => {
|
|
83
|
+
const statusCalls: unknown[] = [];
|
|
84
|
+
const widgetCalls: unknown[] = [];
|
|
85
|
+
|
|
86
|
+
autoSession.reset();
|
|
87
|
+
autoSession.active = true;
|
|
88
|
+
autoSession.paused = false;
|
|
89
|
+
autoSession.completionStopInProgress = true;
|
|
90
|
+
autoSession.resetAfterStop({ preserveCompletionSurface: true });
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await cleanupAfterLoopExit({
|
|
94
|
+
hasUI: true,
|
|
95
|
+
ui: {
|
|
96
|
+
setStatus: (...args: unknown[]) => statusCalls.push(args),
|
|
97
|
+
setWidget: (...args: unknown[]) => widgetCalls.push(args),
|
|
98
|
+
setHeader: () => {},
|
|
99
|
+
notify: () => {},
|
|
100
|
+
},
|
|
101
|
+
} as any);
|
|
102
|
+
|
|
103
|
+
assert.deepEqual(statusCalls, [["gsd-auto", undefined]]);
|
|
104
|
+
assert.equal(
|
|
105
|
+
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
|
|
106
|
+
false,
|
|
107
|
+
"completion cleanup must not clear the roll-up progress widget",
|
|
108
|
+
);
|
|
109
|
+
assert.equal(
|
|
110
|
+
widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-outcome"),
|
|
111
|
+
false,
|
|
112
|
+
"completion cleanup must not replace the roll-up with a generic outcome card",
|
|
113
|
+
);
|
|
114
|
+
assert.equal(autoSession.completionStopInProgress, false);
|
|
115
|
+
} finally {
|
|
116
|
+
autoSession.reset();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("cleanupAfterLoopExit restores project root through lifecycle and preserves chdir", async (t) => {
|
|
121
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-cleanup-lifecycle-"));
|
|
122
|
+
const worktree = join(base, ".gsd", "worktrees", "M001");
|
|
123
|
+
const previousCwd = process.cwd();
|
|
124
|
+
let restoreCalls = 0;
|
|
125
|
+
const originalRestore = WorktreeLifecycle.prototype.restoreToProjectRoot;
|
|
126
|
+
t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function (this: WorktreeLifecycle) {
|
|
127
|
+
restoreCalls += 1;
|
|
128
|
+
return originalRestore.call(this);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
mkdirSync(worktree, { recursive: true });
|
|
132
|
+
autoSession.reset();
|
|
133
|
+
autoSession.active = true;
|
|
134
|
+
autoSession.basePath = worktree;
|
|
135
|
+
autoSession.originalBasePath = base;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await cleanupAfterLoopExit({
|
|
139
|
+
ui: {
|
|
140
|
+
setStatus: () => {},
|
|
141
|
+
setWidget: () => {},
|
|
142
|
+
notify: () => {},
|
|
143
|
+
},
|
|
144
|
+
} as any);
|
|
145
|
+
|
|
146
|
+
assert.equal(restoreCalls, 1);
|
|
147
|
+
assert.equal(autoSession.basePath, base);
|
|
148
|
+
assert.equal(realpathSync(process.cwd()), realpathSync(base));
|
|
149
|
+
} finally {
|
|
150
|
+
autoSession.reset();
|
|
151
|
+
process.chdir(previousCwd);
|
|
152
|
+
rmSync(base, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("cleanupAfterLoopExit keeps cleanup best-effort when lifecycle restore throws", async (t) => {
|
|
157
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-cleanup-restore-throw-"));
|
|
158
|
+
const worktree = join(base, ".gsd", "worktrees", "M001");
|
|
159
|
+
const previousCwd = process.cwd();
|
|
160
|
+
let restoreCalls = 0;
|
|
161
|
+
// ADR-016 phase 3 (#5693): the real `restoreToProjectRoot` assigns
|
|
162
|
+
// `s.basePath = s.originalBasePath` BEFORE any throwable work
|
|
163
|
+
// (rebuildGitService, cache invalidation). Mirror that ordering in the
|
|
164
|
+
// mock so the throw scenario reflects production: basePath is restored
|
|
165
|
+
// even when the verb throws partway through.
|
|
166
|
+
t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function (this: WorktreeLifecycle) {
|
|
167
|
+
restoreCalls += 1;
|
|
168
|
+
(this as unknown as { s: { basePath: string; originalBasePath: string } })
|
|
169
|
+
.s.basePath = (this as unknown as { s: { originalBasePath: string } }).s.originalBasePath;
|
|
170
|
+
throw new Error("restore failed");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
mkdirSync(worktree, { recursive: true });
|
|
174
|
+
autoSession.reset();
|
|
175
|
+
autoSession.active = true;
|
|
176
|
+
autoSession.basePath = worktree;
|
|
177
|
+
autoSession.originalBasePath = base;
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await cleanupAfterLoopExit({
|
|
181
|
+
ui: {
|
|
182
|
+
setStatus: () => {},
|
|
183
|
+
setWidget: () => {},
|
|
184
|
+
notify: () => {},
|
|
185
|
+
},
|
|
186
|
+
} as any);
|
|
187
|
+
|
|
188
|
+
assert.equal(restoreCalls, 1);
|
|
189
|
+
assert.equal(autoSession.basePath, base);
|
|
190
|
+
assert.equal(realpathSync(process.cwd()), realpathSync(base));
|
|
191
|
+
} finally {
|
|
192
|
+
autoSession.reset();
|
|
193
|
+
process.chdir(previousCwd);
|
|
194
|
+
rmSync(base, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
71
198
|
test("rerootCommandSession refreshes command workspace to project root", async () => {
|
|
72
199
|
const calls: string[] = [];
|
|
73
200
|
const result = await rerootCommandSession(
|
|
@@ -83,3 +210,163 @@ test("rerootCommandSession refreshes command workspace to project root", async (
|
|
|
83
210
|
assert.deepEqual(result, { status: "ok" });
|
|
84
211
|
assert.deepEqual(calls, ["/project/root"]);
|
|
85
212
|
});
|
|
213
|
+
|
|
214
|
+
test("stopAuto completion closeout reroots session, restores cwd, and preserves final widget", async (t) => {
|
|
215
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-completion-stop-"));
|
|
216
|
+
const previousCwd = process.cwd();
|
|
217
|
+
const widgetCalls: Array<[string, unknown]> = [];
|
|
218
|
+
const notifications: string[] = [];
|
|
219
|
+
const newSessionWorkspaces: string[] = [];
|
|
220
|
+
let restoreCalls = 0;
|
|
221
|
+
const originalRestore = WorktreeLifecycle.prototype.restoreToProjectRoot;
|
|
222
|
+
t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function (this: WorktreeLifecycle) {
|
|
223
|
+
restoreCalls += 1;
|
|
224
|
+
return originalRestore.call(this);
|
|
225
|
+
});
|
|
226
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M003");
|
|
227
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
228
|
+
writeFileSync(join(milestoneDir, "M003-SUMMARY.md"), [
|
|
229
|
+
"---",
|
|
230
|
+
"id: M003",
|
|
231
|
+
'title: "Budget tracking"',
|
|
232
|
+
"status: complete",
|
|
233
|
+
"key_decisions:",
|
|
234
|
+
" - Keep completion closeout in the same TUI surface.",
|
|
235
|
+
"key_files:",
|
|
236
|
+
" - src/resources/extensions/gsd/auto-dashboard.ts",
|
|
237
|
+
"lessons_learned:",
|
|
238
|
+
" - Milestone endings need report output, not auto-loop status.",
|
|
239
|
+
"---",
|
|
240
|
+
"",
|
|
241
|
+
"# M003: Budget tracking",
|
|
242
|
+
"",
|
|
243
|
+
"**Added budget warning output and provider roll-up details.**",
|
|
244
|
+
"",
|
|
245
|
+
"## Success Criteria Results",
|
|
246
|
+
"",
|
|
247
|
+
"Budget warnings appear at milestone completion.",
|
|
248
|
+
"",
|
|
249
|
+
"## Definition of Done Results",
|
|
250
|
+
"",
|
|
251
|
+
"Completion leaves the report surface visible.",
|
|
252
|
+
"",
|
|
253
|
+
"## Requirement Outcomes",
|
|
254
|
+
"",
|
|
255
|
+
"Users can see what shipped without opening a fresh session.",
|
|
256
|
+
"",
|
|
257
|
+
"## Deviations",
|
|
258
|
+
"",
|
|
259
|
+
"None.",
|
|
260
|
+
"",
|
|
261
|
+
"## Follow-ups",
|
|
262
|
+
"",
|
|
263
|
+
"None.",
|
|
264
|
+
"",
|
|
265
|
+
].join("\n"), "utf-8");
|
|
266
|
+
|
|
267
|
+
autoSession.reset();
|
|
268
|
+
openDatabase(join(base, "gsd-test.db"));
|
|
269
|
+
insertMilestone({ id: "M003", title: "Budget tracking", status: "complete" });
|
|
270
|
+
insertSlice({ id: "S01", milestoneId: "M003", title: "Complete slice", status: "complete", sequence: 1 });
|
|
271
|
+
insertSlice({ id: "S02", milestoneId: "M003", title: "Done slice", status: "done", sequence: 2 });
|
|
272
|
+
insertSlice({ id: "S03", milestoneId: "M003", title: "Pending slice", status: "active", sequence: 3 });
|
|
273
|
+
|
|
274
|
+
autoSession.active = true;
|
|
275
|
+
autoSession.paused = false;
|
|
276
|
+
autoSession.basePath = join(base, ".gsd", "worktrees", "M003");
|
|
277
|
+
autoSession.originalBasePath = base;
|
|
278
|
+
autoSession.currentMilestoneId = "M003";
|
|
279
|
+
autoSession.autoStartTime = Date.now() - 60_000;
|
|
280
|
+
autoSession.cmdCtx = {
|
|
281
|
+
newSession: async ({ workspaceRoot }: { workspaceRoot: string }) => {
|
|
282
|
+
newSessionWorkspaces.push(workspaceRoot);
|
|
283
|
+
widgetCalls.push(["gsd-progress", undefined]);
|
|
284
|
+
return { cancelled: false };
|
|
285
|
+
},
|
|
286
|
+
sessionManager: {
|
|
287
|
+
getEntries: () => [
|
|
288
|
+
{
|
|
289
|
+
type: "message",
|
|
290
|
+
message: {
|
|
291
|
+
role: "assistant",
|
|
292
|
+
usage: { input: 100, cacheRead: 900 },
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
getContextUsage: () => ({ percent: 0.9, contextWindow: 1_000_000 }),
|
|
298
|
+
model: { contextWindow: 1_000_000 },
|
|
299
|
+
} as any;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await stopAuto(
|
|
303
|
+
{
|
|
304
|
+
hasUI: true,
|
|
305
|
+
ui: {
|
|
306
|
+
setStatus: () => {},
|
|
307
|
+
setWidget: (key: string, value: unknown) => {
|
|
308
|
+
widgetCalls.push([key, value]);
|
|
309
|
+
},
|
|
310
|
+
setHeader: () => {},
|
|
311
|
+
notify: (message: string) => {
|
|
312
|
+
notifications.push(message);
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
modelRegistry: { find: () => null },
|
|
316
|
+
} as any,
|
|
317
|
+
{ events: { emit: () => {} } } as any,
|
|
318
|
+
"Milestone M003 complete",
|
|
319
|
+
{
|
|
320
|
+
completionWidget: {
|
|
321
|
+
milestoneId: "M003",
|
|
322
|
+
milestoneTitle: "Budget tracking",
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
assert.deepEqual(newSessionWorkspaces, [base], "completion stop must reroot command session to original project root");
|
|
328
|
+
assert.equal(restoreCalls, 1, "completion stop must restore project root through lifecycle");
|
|
329
|
+
assert.equal(realpathSync(process.cwd()), realpathSync(base), "completion stop must chdir back to project root");
|
|
330
|
+
assert.ok(
|
|
331
|
+
widgetCalls.some(([key, value]) => key === "gsd-progress" && typeof value === "function"),
|
|
332
|
+
"completion stop must install a final progress widget",
|
|
333
|
+
);
|
|
334
|
+
const lastProgressWidget = widgetCalls.filter(([key]) => key === "gsd-progress").at(-1);
|
|
335
|
+
assert.equal(typeof lastProgressWidget?.[1], "function", "completion stop must leave the final progress widget installed after reroot");
|
|
336
|
+
const factory = lastProgressWidget?.[1] as any;
|
|
337
|
+
const component = factory(
|
|
338
|
+
{ requestRender() {} },
|
|
339
|
+
{ fg: (_color: string, text: string) => text, bold: (text: string) => text },
|
|
340
|
+
);
|
|
341
|
+
const output = component.render(140).join("\n");
|
|
342
|
+
assert.match(output, /Milestone M003 roll-up/);
|
|
343
|
+
assert.match(output, /Outcome/);
|
|
344
|
+
assert.match(output, /Added budget warning output/);
|
|
345
|
+
assert.match(output, /Verification/);
|
|
346
|
+
assert.match(output, /Files: src\/resources\/extensions\/gsd\/auto-dashboard\.ts/);
|
|
347
|
+
assert.match(output, /Lessons: Milestone endings need report output/);
|
|
348
|
+
assert.match(output, /2\/3 slices/);
|
|
349
|
+
assert.match(output, /Next/);
|
|
350
|
+
assert.match(output, /Review the roll-up/);
|
|
351
|
+
assert.match(output, /\/gsd auto for next milestone/);
|
|
352
|
+
assert.doesNotMatch(output, /COMPLETE-MILESTONE/);
|
|
353
|
+
assert.doesNotMatch(output, /\/gsd auto to resume/);
|
|
354
|
+
assert.ok(
|
|
355
|
+
notifications.some(message => message.includes("Milestone M003 complete. Auto-mode finished this milestone.")),
|
|
356
|
+
"completion stop notification should describe completion, not an aborted pause",
|
|
357
|
+
);
|
|
358
|
+
assert.ok(
|
|
359
|
+
notifications.every(message => !message.includes("/gsd auto to resume")),
|
|
360
|
+
"completion stop notification must not tell users to resume a finished auto run",
|
|
361
|
+
);
|
|
362
|
+
assert.ok(
|
|
363
|
+
widgetCalls.every(([key, value]) => key !== "gsd-outcome" || value === undefined),
|
|
364
|
+
"completion stop should use the roll-up as the single final surface",
|
|
365
|
+
);
|
|
366
|
+
} finally {
|
|
367
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
368
|
+
autoSession.reset();
|
|
369
|
+
process.chdir(previousCwd);
|
|
370
|
+
rmSync(base, { recursive: true, force: true });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
autoSession,
|
|
6
|
+
clearToolInvocationError,
|
|
7
|
+
getAutoRuntimeSnapshot,
|
|
8
|
+
} from "../auto-runtime-state.ts";
|
|
5
9
|
|
|
6
10
|
test("getAutoRuntimeSnapshot includes orchestration phase when available", () => {
|
|
7
11
|
autoSession.reset();
|
|
@@ -28,6 +32,17 @@ test("getAutoRuntimeSnapshot includes orchestration phase when available", () =>
|
|
|
28
32
|
autoSession.reset();
|
|
29
33
|
});
|
|
30
34
|
|
|
35
|
+
test("clearToolInvocationError clears stale tool error state for active auto sessions", () => {
|
|
36
|
+
autoSession.reset();
|
|
37
|
+
autoSession.active = true;
|
|
38
|
+
autoSession.lastToolInvocationError = "gsd_task_complete: simulated transient tool error";
|
|
39
|
+
|
|
40
|
+
clearToolInvocationError();
|
|
41
|
+
|
|
42
|
+
assert.equal(autoSession.lastToolInvocationError, null);
|
|
43
|
+
autoSession.reset();
|
|
44
|
+
});
|
|
45
|
+
|
|
31
46
|
test("getAutoRuntimeSnapshot omits orchestration phase when seam not wired", () => {
|
|
32
47
|
autoSession.reset();
|
|
33
48
|
|
|
@@ -100,6 +100,14 @@ test("bootstrap aborts before starting next milestone when completed orphan merg
|
|
|
100
100
|
registerSigtermHandler: () => {},
|
|
101
101
|
lockBase: () => base,
|
|
102
102
|
buildLifecycle: () => ({
|
|
103
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
104
|
+
s.basePath = sessionBase;
|
|
105
|
+
if (originalBase !== undefined) {
|
|
106
|
+
s.originalBasePath = originalBase;
|
|
107
|
+
} else if (!s.originalBasePath) {
|
|
108
|
+
s.originalBasePath = sessionBase;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
103
111
|
exitMilestone: (milestoneId: string) => {
|
|
104
112
|
mergeCalls.push(milestoneId);
|
|
105
113
|
return {
|
|
@@ -109,6 +117,16 @@ test("bootstrap aborts before starting next milestone when completed orphan merg
|
|
|
109
117
|
};
|
|
110
118
|
},
|
|
111
119
|
enterMilestone: () => ({ ok: true, mode: "none", path: base }),
|
|
120
|
+
// ADR-016 phase 2 / B4 (#5622): the orphan-merge dance now goes
|
|
121
|
+
// through `adoptOrphanWorktree`. The mock invokes the callback
|
|
122
|
+
// and returns its result without exercising the swap-revert
|
|
123
|
+
// protocol — this test only cares about the merge call being
|
|
124
|
+
// recorded and the bootstrap returning `false` on failure.
|
|
125
|
+
adoptOrphanWorktree: <T extends { merged: boolean }>(
|
|
126
|
+
_mid: string,
|
|
127
|
+
_base: string,
|
|
128
|
+
run: () => T,
|
|
129
|
+
): T => run(),
|
|
112
130
|
}) as any,
|
|
113
131
|
},
|
|
114
132
|
{
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Regression tests for auto-unit closeout activity classification.
|
|
3
|
+
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isSuspiciousGhostCompletion,
|
|
9
|
+
snapshotUnitActivity,
|
|
10
|
+
} from "../auto-unit-closeout.ts";
|
|
11
|
+
|
|
12
|
+
function makeCtx(entries: unknown[]) {
|
|
13
|
+
return {
|
|
14
|
+
sessionManager: {
|
|
15
|
+
getEntries: () => entries,
|
|
16
|
+
},
|
|
17
|
+
} as any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test("isSuspiciousGhostCompletion rejects fast completions with no assistant output or tools", () => {
|
|
21
|
+
const startedAt = Date.now();
|
|
22
|
+
const ctx = makeCtx([]);
|
|
23
|
+
|
|
24
|
+
assert.equal(isSuspiciousGhostCompletion(ctx, startedAt, 500), true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("isSuspiciousGhostCompletion allows fast completions with assistant output", () => {
|
|
28
|
+
const startedAt = Date.now();
|
|
29
|
+
const ctx = makeCtx([
|
|
30
|
+
{
|
|
31
|
+
type: "message",
|
|
32
|
+
message: {
|
|
33
|
+
role: "assistant",
|
|
34
|
+
content: [{ type: "text", text: "Done." }],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
assert.equal(isSuspiciousGhostCompletion(ctx, startedAt, 500), false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("snapshotUnitActivity counts assistant messages and tool calls", () => {
|
|
43
|
+
const ctx = makeCtx([
|
|
44
|
+
{
|
|
45
|
+
type: "message",
|
|
46
|
+
message: {
|
|
47
|
+
role: "assistant",
|
|
48
|
+
content: [
|
|
49
|
+
{ type: "text", text: "Working." },
|
|
50
|
+
{ type: "toolCall", name: "read_file" },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: "message",
|
|
56
|
+
message: {
|
|
57
|
+
role: "user",
|
|
58
|
+
content: "continue",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
assert.deepEqual(snapshotUnitActivity(ctx, 1_000, 1_250), {
|
|
64
|
+
elapsedMs: 250,
|
|
65
|
+
toolCalls: 1,
|
|
66
|
+
assistantMessages: 1,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -182,7 +182,6 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
|
|
|
182
182
|
pruneQueueOrder: () => {},
|
|
183
183
|
isInAutoWorktree: () => false,
|
|
184
184
|
shouldUseWorktreeIsolation: () => false,
|
|
185
|
-
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
|
|
186
185
|
teardownAutoWorktree: () => {},
|
|
187
186
|
createAutoWorktree: () => "/tmp/wt",
|
|
188
187
|
captureIntegrationBranch: () => {},
|
|
@@ -420,6 +419,10 @@ describe("Custom engine loop integration", () => {
|
|
|
420
419
|
await autoLoop(ctx, pi, s, deps);
|
|
421
420
|
|
|
422
421
|
assert.deepEqual(turnResults, [{ status: "completed", failureClass: "none", error: undefined }]);
|
|
422
|
+
assert.ok(
|
|
423
|
+
deps.callLog.includes("journal:iteration-end"),
|
|
424
|
+
`complete workflow should emit iteration-end; log=${deps.callLog.join(",")}`,
|
|
425
|
+
);
|
|
423
426
|
assert.ok(
|
|
424
427
|
deps.callLog.indexOf("turnResult:completed") < deps.callLog.indexOf("stopAuto:Workflow complete"),
|
|
425
428
|
`turn should finalize before stopAuto; log=${deps.callLog.join(",")}`,
|
|
@@ -472,6 +475,10 @@ describe("Custom engine loop integration", () => {
|
|
|
472
475
|
assert.equal(turnResults[0].status, "stopped");
|
|
473
476
|
assert.equal(turnResults[0].failureClass, "manual-attention");
|
|
474
477
|
assert.match(turnResults[0].error ?? "", /custom-engine-dispatch-stop/);
|
|
478
|
+
assert.ok(
|
|
479
|
+
deps.callLog.includes("journal:iteration-end"),
|
|
480
|
+
`blocked workflow should emit iteration-end; log=${deps.callLog.join(",")}`,
|
|
481
|
+
);
|
|
475
482
|
assert.equal(s.currentTraceId, null);
|
|
476
483
|
assert.equal(s.currentTurnId, null);
|
|
477
484
|
assert.equal(pi.calls.length, 0, "blocked workflow should not dispatch a custom step");
|
|
@@ -664,11 +671,16 @@ describe("Custom engine loop integration", () => {
|
|
|
664
671
|
activeRunDir: runDir,
|
|
665
672
|
basePath: runDir,
|
|
666
673
|
});
|
|
674
|
+
const journalEvents: Array<{ eventType: string; data?: any }> = [];
|
|
667
675
|
const deps = makeMockDeps({
|
|
668
676
|
stopAuto: async (_ctx, _pi, reason) => {
|
|
669
677
|
deps.callLog.push(`stopAuto:${reason ?? "no-reason"}`);
|
|
670
678
|
s.active = false;
|
|
671
679
|
},
|
|
680
|
+
emitJournalEvent: (entry: any) => {
|
|
681
|
+
journalEvents.push(entry);
|
|
682
|
+
deps.callLog.push(`journal:${entry.eventType}`);
|
|
683
|
+
},
|
|
672
684
|
});
|
|
673
685
|
|
|
674
686
|
const resolver = setInterval(() => {
|
|
@@ -700,6 +712,21 @@ describe("Custom engine loop integration", () => {
|
|
|
700
712
|
assert.match(stopEntry ?? "", /requested retry 4 times without passing/);
|
|
701
713
|
const finalGraph = readGraph(runDir);
|
|
702
714
|
assert.equal(finalGraph.steps[0]?.status, "active", "failed verification must not reconcile the step complete");
|
|
715
|
+
|
|
716
|
+
const unitEndIndexes = journalEvents
|
|
717
|
+
.map((entry, index) => entry.eventType === "unit-end" ? index : -1)
|
|
718
|
+
.filter((index) => index >= 0);
|
|
719
|
+
const iterationEndIndexes = journalEvents
|
|
720
|
+
.map((entry, index) => entry.eventType === "iteration-end" ? index : -1)
|
|
721
|
+
.filter((index) => index >= 0);
|
|
722
|
+
assert.equal(unitEndIndexes.length, 4, "each custom verification retry/stop attempt must emit unit-end");
|
|
723
|
+
assert.equal(iterationEndIndexes.length, 4, "each custom verification retry/stop iteration must close after unit-end");
|
|
724
|
+
for (const [i, unitEndIndex] of unitEndIndexes.entries()) {
|
|
725
|
+
assert.ok(
|
|
726
|
+
iterationEndIndexes[i]! > unitEndIndex,
|
|
727
|
+
`custom verification attempt ${i + 1} should emit iteration-end after unit-end`,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
703
730
|
});
|
|
704
731
|
|
|
705
732
|
it("persists custom verification retry budget across a session restart", async () => {
|
|
@@ -273,7 +273,16 @@ test("deep project setup: bootstrap can start auto-mode without an active milest
|
|
|
273
273
|
shouldUseWorktreeIsolation: () => false,
|
|
274
274
|
registerSigtermHandler: () => {},
|
|
275
275
|
lockBase: () => base,
|
|
276
|
-
buildLifecycle: () => ({
|
|
276
|
+
buildLifecycle: () => ({
|
|
277
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
278
|
+
s.basePath = sessionBase;
|
|
279
|
+
if (originalBase !== undefined) {
|
|
280
|
+
s.originalBasePath = originalBase;
|
|
281
|
+
} else if (!s.originalBasePath) {
|
|
282
|
+
s.originalBasePath = sessionBase;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
}) as any,
|
|
277
286
|
},
|
|
278
287
|
{
|
|
279
288
|
classification: "none",
|
|
@@ -378,7 +387,16 @@ test("deep project setup: bootstrap continues queued M002 without milestone cont
|
|
|
378
387
|
shouldUseWorktreeIsolation: () => false,
|
|
379
388
|
registerSigtermHandler: () => {},
|
|
380
389
|
lockBase: () => base,
|
|
381
|
-
buildLifecycle: () => ({
|
|
390
|
+
buildLifecycle: () => ({
|
|
391
|
+
adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
|
|
392
|
+
s.basePath = sessionBase;
|
|
393
|
+
if (originalBase !== undefined) {
|
|
394
|
+
s.originalBasePath = originalBase;
|
|
395
|
+
} else if (!s.originalBasePath) {
|
|
396
|
+
s.originalBasePath = sessionBase;
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
}) as any,
|
|
382
400
|
},
|
|
383
401
|
{
|
|
384
402
|
classification: "none",
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Regression tests for complete-milestone dispatch guards.
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* dispatch-complete-milestone-guard.test.ts — #4324
|
|
3
6
|
*/
|
|
@@ -74,3 +77,44 @@ describe("completing-milestone dispatch guard (#4324)", () => {
|
|
|
74
77
|
assert.equal(result?.unitId, "M001");
|
|
75
78
|
});
|
|
76
79
|
});
|
|
80
|
+
|
|
81
|
+
describe("complete phase dispatch guard (#5683)", () => {
|
|
82
|
+
let base = "";
|
|
83
|
+
const rule = DISPATCH_RULES.find((candidate) => candidate.name === "complete → stop");
|
|
84
|
+
assert.ok(rule, "complete phase terminal rule should exist");
|
|
85
|
+
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
try { closeDatabase(); } catch { /* ignore */ }
|
|
88
|
+
if (base) rmSync(base, { recursive: true, force: true });
|
|
89
|
+
base = "";
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("dispatches complete-milestone when derived state is complete but DB milestone is still open", async () => {
|
|
93
|
+
base = makeBase();
|
|
94
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
95
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "in_progress" });
|
|
96
|
+
|
|
97
|
+
const ctx = buildDispatchCtx(base);
|
|
98
|
+
ctx.state.phase = "complete";
|
|
99
|
+
|
|
100
|
+
const result = await rule.match(ctx);
|
|
101
|
+
|
|
102
|
+
assert.equal(result?.action, "dispatch");
|
|
103
|
+
assert.equal(result?.unitType, "complete-milestone");
|
|
104
|
+
assert.equal(result?.unitId, "M001");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("stops when derived state is complete and DB milestone is closed", async () => {
|
|
108
|
+
base = makeBase();
|
|
109
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
110
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "complete" });
|
|
111
|
+
|
|
112
|
+
const ctx = buildDispatchCtx(base);
|
|
113
|
+
ctx.state.phase = "complete";
|
|
114
|
+
|
|
115
|
+
const result = await rule.match(ctx);
|
|
116
|
+
|
|
117
|
+
assert.equal(result?.action, "stop");
|
|
118
|
+
assert.equal(result?.reason, "All milestones complete.");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Visual contract tests for the GSD watch header renderer.
|
|
3
|
+
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { stripVTControlCharacters } from "node:util";
|
|
7
|
+
import { renderHeaderLines } from "../watch/header-renderer.ts";
|
|
8
|
+
import { splashPalette } from "../watch/splash-palette.ts";
|
|
9
|
+
|
|
10
|
+
function rgbPattern(hex: string): RegExp {
|
|
11
|
+
const cleaned = hex.replace("#", "");
|
|
12
|
+
const r = Number.parseInt(cleaned.slice(0, 2), 16);
|
|
13
|
+
const g = Number.parseInt(cleaned.slice(2, 4), 16);
|
|
14
|
+
const b = Number.parseInt(cleaned.slice(4, 6), 16);
|
|
15
|
+
return new RegExp(`\\x1b\\[38;2;${r};${g};${b}m`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("renderHeaderLines uses the command-center splash layout", () => {
|
|
19
|
+
const lines = renderHeaderLines(
|
|
20
|
+
{
|
|
21
|
+
model: "claude-sonnet-4-6",
|
|
22
|
+
provider: "anthropic",
|
|
23
|
+
directory: "~/Github/gsd-2",
|
|
24
|
+
branch: "feat/tui-refresh",
|
|
25
|
+
mcpServers: ["context7"],
|
|
26
|
+
},
|
|
27
|
+
120,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const raw = lines.join("\n");
|
|
31
|
+
const plain = stripVTControlCharacters(raw);
|
|
32
|
+
|
|
33
|
+
assert.match(raw, rgbPattern(splashPalette.border), "logo and divider should use the recommended olive border");
|
|
34
|
+
assert.match(raw, rgbPattern(splashPalette.accent), "header accents should use the recommended blue");
|
|
35
|
+
assert.match(plain, /Project Console/);
|
|
36
|
+
assert.match(plain, /\/gsd start/);
|
|
37
|
+
assert.match(plain, /\/gsd templates/);
|
|
38
|
+
assert.match(plain, /claude-sonnet-4-6/);
|
|
39
|
+
assert.match(plain, /Context7 ✓/);
|
|
40
|
+
});
|
|
@@ -85,6 +85,11 @@ describe("headless milestone bootstrap — parity with interactive flow", () =>
|
|
|
85
85
|
/Do not announce the ready phrase as something you are "about to" do/.test(section),
|
|
86
86
|
"single-milestone pre-condition must include the 'do not announce intent' guard",
|
|
87
87
|
);
|
|
88
|
+
assert.ok(/Next steps:/.test(section), "single-milestone handoff must include next steps");
|
|
89
|
+
assert.ok(/\/gsd auto/.test(section), "single-milestone handoff must mention /gsd auto");
|
|
90
|
+
assert.ok(/\/gsd status/.test(section), "single-milestone handoff must mention /gsd status");
|
|
91
|
+
assert.ok(/\/gsd visualize/.test(section), "single-milestone handoff must mention /gsd visualize");
|
|
92
|
+
assert.ok(/\/gsd notifications/.test(section), "single-milestone handoff must mention /gsd notifications");
|
|
88
93
|
});
|
|
89
94
|
|
|
90
95
|
test("discuss-headless multi-milestone pre-condition uses the non-bypassable checkbox format", () => {
|
|
@@ -113,5 +118,10 @@ describe("headless milestone bootstrap — parity with interactive flow", () =>
|
|
|
113
118
|
/gates_completed === total/.test(multiSection),
|
|
114
119
|
"multi-milestone pre-condition must still enforce gates_completed === total",
|
|
115
120
|
);
|
|
121
|
+
assert.ok(/Next steps:/.test(multiSection), "multi-milestone handoff must include next steps");
|
|
122
|
+
assert.ok(/\/gsd auto/.test(multiSection), "multi-milestone handoff must mention /gsd auto");
|
|
123
|
+
assert.ok(/\/gsd status/.test(multiSection), "multi-milestone handoff must mention /gsd status");
|
|
124
|
+
assert.ok(/\/gsd visualize/.test(multiSection), "multi-milestone handoff must mention /gsd visualize");
|
|
125
|
+
assert.ok(/\/gsd notifications/.test(multiSection), "multi-milestone handoff must mention /gsd notifications");
|
|
116
126
|
});
|
|
117
127
|
});
|