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
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Tests for the GSD health widget state and footer hint rendering.
|
|
3
|
+
|
|
1
4
|
import test from "node:test";
|
|
2
5
|
import assert from "node:assert/strict";
|
|
3
6
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -9,6 +12,7 @@ import {
|
|
|
9
12
|
formatRelativeTime,
|
|
10
13
|
type HealthWidgetData,
|
|
11
14
|
} from "../health-widget-core.ts";
|
|
15
|
+
import { HEALTH_WIDGET_ACTIVE_HINTS } from "../health-widget.ts";
|
|
12
16
|
import { registerHooks } from "../bootstrap/register-hooks.ts";
|
|
13
17
|
|
|
14
18
|
function makeTempDir(prefix: string): string {
|
|
@@ -75,13 +79,11 @@ test("buildHealthLines: none state shows single onboarding line pointing at /gsd
|
|
|
75
79
|
assert.match(lines[0]!, /\/gsd/);
|
|
76
80
|
});
|
|
77
81
|
|
|
78
|
-
test("buildHealthLines: initialized state shows
|
|
82
|
+
test("buildHealthLines: initialized state shows concise initialized line", (t) => {
|
|
79
83
|
const lines = buildHealthLines(activeData({ projectState: "initialized" }));
|
|
80
84
|
assert.equal(lines.length, 1, "renders exactly one line");
|
|
81
85
|
assert.ok(!/System OK|Budget|Last commit/.test(lines[0]!), "no active-project chrome");
|
|
82
|
-
|
|
83
|
-
assert.match(lines[0]!, /\/gsd/);
|
|
84
|
-
assert.match(lines[0]!, /initiali[sz]ed|setup/i);
|
|
86
|
+
assert.equal(lines[0], " GSD Project Initialized");
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
test("buildHealthLines: active state with ledger-driven spend shows spent summary", (t) => {
|
|
@@ -91,6 +93,14 @@ test("buildHealthLines: active state with ledger-driven spend shows spent summar
|
|
|
91
93
|
assert.match(lines[0]!, /Spent: 42\.0¢/);
|
|
92
94
|
});
|
|
93
95
|
|
|
96
|
+
test("health widget active hints include visualization and notifications", () => {
|
|
97
|
+
assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd auto to run/);
|
|
98
|
+
assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd status for overview/);
|
|
99
|
+
assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd visualize to inspect/);
|
|
100
|
+
assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd notifications for history/);
|
|
101
|
+
assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd help/);
|
|
102
|
+
});
|
|
103
|
+
|
|
94
104
|
test("buildHealthLines: active state with budget ceiling shows percent summary", (t) => {
|
|
95
105
|
const lines = buildHealthLines(activeData({ budgetSpent: 2.5, budgetCeiling: 10 }));
|
|
96
106
|
assert.equal(lines.length, 1);
|
|
@@ -608,6 +608,32 @@ describe('git-service', async () => {
|
|
|
608
608
|
rmSync(repo, { recursive: true, force: true });
|
|
609
609
|
});
|
|
610
610
|
|
|
611
|
+
test('GitServiceImpl: task context keyFiles ignores gitignored build outputs', () => {
|
|
612
|
+
const repo = initTempRepo();
|
|
613
|
+
const svc = new GitServiceImpl(repo);
|
|
614
|
+
|
|
615
|
+
createFile(repo, ".gitignore", "dist/\n");
|
|
616
|
+
runGit(repo, ["add", ".gitignore"]);
|
|
617
|
+
runGit(repo, ["commit", "-F", "-"], { input: "ignore dist" });
|
|
618
|
+
|
|
619
|
+
createFile(repo, "src/task.ts", "export const task = true;");
|
|
620
|
+
createFile(repo, "dist/task.js", "export const task = true;");
|
|
621
|
+
|
|
622
|
+
const msg = svc.autoCommit("execute-task", "M001/S01/T01", [], {
|
|
623
|
+
taskId: "S01/T01",
|
|
624
|
+
taskTitle: "implement scoped task",
|
|
625
|
+
oneLiner: "Added scoped task implementation",
|
|
626
|
+
keyFiles: ["src/task.ts", "dist/task.js"],
|
|
627
|
+
});
|
|
628
|
+
assert.ok(msg !== null, "autoCommit should commit non-ignored key files");
|
|
629
|
+
|
|
630
|
+
const committed = run("git show --name-only --format= HEAD", repo);
|
|
631
|
+
assert.ok(committed.includes("src/task.ts"), "non-ignored key file is committed");
|
|
632
|
+
assert.ok(!committed.includes("dist/task.js"), "ignored build output is not committed");
|
|
633
|
+
|
|
634
|
+
rmSync(repo, { recursive: true, force: true });
|
|
635
|
+
});
|
|
636
|
+
|
|
611
637
|
// ─── GitServiceImpl: empty-after-staging guard ─────────────────────────
|
|
612
638
|
|
|
613
639
|
test('GitServiceImpl: empty-after-staging guard', () => {
|
|
@@ -62,8 +62,8 @@ import {
|
|
|
62
62
|
renderRoadmapCheckboxes,
|
|
63
63
|
renderAllFromDb,
|
|
64
64
|
detectStaleRenders,
|
|
65
|
-
repairStaleRenders,
|
|
66
65
|
} from "../../markdown-renderer.ts";
|
|
66
|
+
import { repairStaleRenders } from "../../state-reconciliation/drift/stale-render.ts";
|
|
67
67
|
|
|
68
68
|
// ── State derivation ──────────────────────────────────────────────────────
|
|
69
69
|
import {
|
|
@@ -85,7 +85,49 @@ function cleanup(dir: string): void {
|
|
|
85
85
|
try { rmSync(dir, { recursive: true, force: true }); } catch { /* */ }
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Write `.gsd/preferences.md` with `git.isolation: branch` so the merge
|
|
90
|
+
* routes through the standalone Lifecycle merge body in branch mode rather
|
|
91
|
+
* than falling through `getIsolationMode === "none"` to a skipped result.
|
|
92
|
+
*
|
|
93
|
+
* Parallel-merge in production runs with `git.isolation: worktree` and a
|
|
94
|
+
* real auto-worktree on disk; these integration tests use branch mode as a
|
|
95
|
+
* simpler equivalent that exercises the same merge path without requiring
|
|
96
|
+
* `git worktree add` setup. ADR-016 phase 2 / A2 routes parallel-merge
|
|
97
|
+
* through the Module, which performs mode detection — branch mode keeps
|
|
98
|
+
* the test simple while still going through the Module.
|
|
99
|
+
*/
|
|
100
|
+
function setupBranchIsolation(repo: string): void {
|
|
101
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
102
|
+
writeFileSync(
|
|
103
|
+
join(repo, ".gsd", "preferences.md"),
|
|
104
|
+
"## Git\n- isolation: branch\n",
|
|
105
|
+
);
|
|
106
|
+
// Commit on main so subsequent `git checkout main` restores the file.
|
|
107
|
+
// Without this commit, `createMilestoneBranch`'s checkout dance carries
|
|
108
|
+
// the uncommitted file onto the milestone branch and back-checking-out
|
|
109
|
+
// main loses the preference file before `mergeMilestoneStandalone`
|
|
110
|
+
// reads it (ADR-016 phase 2 / A2).
|
|
111
|
+
try {
|
|
112
|
+
run("git add .gsd/preferences.md", repo);
|
|
113
|
+
run('git commit -m "test: add branch-isolation preferences"', repo);
|
|
114
|
+
} catch { /* file may already be committed */ }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Set up a milestone roadmap file in .gsd/milestones/<MID>/ AND commit it on
|
|
119
|
+
* the current branch.
|
|
120
|
+
*
|
|
121
|
+
* The commit is required after ADR-016 phase 2 / A2 because the standalone
|
|
122
|
+
* Module-level merge (now used by parallel-merge) reads the roadmap _after_
|
|
123
|
+
* its branch checkout. Uncommitted roadmaps on `main` survive a checkout
|
|
124
|
+
* for the first milestone but get committed onto the milestone branch by
|
|
125
|
+
* `autoCommitDirtyState`, which then doesn't reproduce them on the next
|
|
126
|
+
* milestone branch's checkout.
|
|
127
|
+
*
|
|
128
|
+
* Real production runs commit roadmap files on each milestone branch as
|
|
129
|
+
* part of the unit's normal output; tests should mirror that.
|
|
130
|
+
*/
|
|
89
131
|
function setupRoadmap(repo: string, mid: string, title: string, slices: string[]): void {
|
|
90
132
|
const dir = join(repo, ".gsd", "milestones", mid);
|
|
91
133
|
mkdirSync(dir, { recursive: true });
|
|
@@ -94,6 +136,10 @@ function setupRoadmap(repo: string, mid: string, title: string, slices: string[]
|
|
|
94
136
|
join(dir, `${mid}-ROADMAP.md`),
|
|
95
137
|
`# ${mid}: ${title}\n\n## Slices\n${sliceLines}\n`,
|
|
96
138
|
);
|
|
139
|
+
try {
|
|
140
|
+
run(`git add .gsd/milestones/${mid}/${mid}-ROADMAP.md`, repo);
|
|
141
|
+
run(`git commit -m "test: add roadmap for ${mid}"`, repo);
|
|
142
|
+
} catch { /* file may already be committed; not a git repo; etc. */ }
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
/** Create a milestone branch with file changes, then return to main. */
|
|
@@ -234,15 +280,36 @@ test("formatMergeResults — mixed results", () => {
|
|
|
234
280
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
235
281
|
|
|
236
282
|
test("mergeCompletedMilestone — missing roadmap returns error result", async () => {
|
|
237
|
-
const
|
|
238
|
-
|
|
283
|
+
const savedCwd = process.cwd();
|
|
284
|
+
// Use a real git repo so the standalone's branch-mode merge body can
|
|
285
|
+
// call `getCurrentBranch` without throwing. The roadmap is intentionally
|
|
286
|
+
// omitted so the merge ends in the "no-roadmap" branch.
|
|
287
|
+
const repo = createTempRepo();
|
|
239
288
|
try {
|
|
240
|
-
|
|
289
|
+
setupBranchIsolation(repo);
|
|
290
|
+
// Create a milestone branch but no roadmap file — this is the
|
|
291
|
+
// condition under test.
|
|
292
|
+
run("git checkout -b milestone/M999", repo);
|
|
293
|
+
writeFileSync(join(repo, "feature.ts"), "export {};\n");
|
|
294
|
+
run("git add .", repo);
|
|
295
|
+
run('git commit -m "M999 feature"', repo);
|
|
296
|
+
run("git checkout main", repo);
|
|
297
|
+
|
|
298
|
+
process.chdir(repo);
|
|
299
|
+
const result = await mergeCompletedMilestone(repo, "M999");
|
|
300
|
+
|
|
241
301
|
assert.equal(result.success, false);
|
|
242
|
-
|
|
302
|
+
// A2 widens the error surface vs. the legacy bypass: the standalone
|
|
303
|
+
// routes through the Module, so the no-roadmap path teardowns the
|
|
304
|
+
// (non-existent) worktree branch and surfaces a typed failure.
|
|
305
|
+
// What matters is `success: false`; the exact wording can shift as
|
|
306
|
+
// the standalone evolves.
|
|
307
|
+
assert.ok(result.error, "should report a non-empty error");
|
|
243
308
|
assert.equal(result.milestoneId, "M999");
|
|
244
309
|
} finally {
|
|
245
|
-
|
|
310
|
+
process.chdir(savedCwd);
|
|
311
|
+
try { run("git reset --hard HEAD", repo); } catch { /* */ }
|
|
312
|
+
cleanup(repo);
|
|
246
313
|
}
|
|
247
314
|
});
|
|
248
315
|
|
|
@@ -251,14 +318,24 @@ test("mergeCompletedMilestone — clean merge, session status cleaned up", async
|
|
|
251
318
|
const repo = createTempRepo();
|
|
252
319
|
|
|
253
320
|
try {
|
|
321
|
+
// Route the merge through the Module's branch-mode path. With default
|
|
322
|
+
// `isolation: none`, the standalone returns `{ mode: "skipped" }`
|
|
323
|
+
// (ADR-016 phase 2 / A2). Branch mode exercises the same Module-level
|
|
324
|
+
// merge body without requiring an on-disk auto-worktree.
|
|
325
|
+
setupBranchIsolation(repo);
|
|
326
|
+
|
|
327
|
+
// Set up roadmap on main BEFORE the milestone branch is created so the
|
|
328
|
+
// roadmap is in the milestone branch's history. After A2, the standalone
|
|
329
|
+
// reads the roadmap _inside_ the merge body (after the branch checkout)
|
|
330
|
+
// — the file must therefore exist in the milestone branch's tree, not
|
|
331
|
+
// just as an uncommitted file on main.
|
|
332
|
+
setupRoadmap(repo, "M010", "Auth System", ["S01: JWT module"]);
|
|
333
|
+
|
|
254
334
|
// Create milestone branch with a new file
|
|
255
335
|
createMilestoneBranch(repo, "M010", [
|
|
256
336
|
{ name: "auth.ts", content: "export const auth = true;\n" },
|
|
257
337
|
]);
|
|
258
338
|
|
|
259
|
-
// Set up roadmap
|
|
260
|
-
setupRoadmap(repo, "M010", "Auth System", ["S01: JWT module"]);
|
|
261
|
-
|
|
262
339
|
// Write session status to verify cleanup
|
|
263
340
|
writeSessionStatus(repo, {
|
|
264
341
|
milestoneId: "M010",
|
|
@@ -309,6 +386,11 @@ test("mergeCompletedMilestone — conflict returns structured error with file li
|
|
|
309
386
|
const repo = createTempRepo();
|
|
310
387
|
|
|
311
388
|
try {
|
|
389
|
+
setupBranchIsolation(repo);
|
|
390
|
+
|
|
391
|
+
// Roadmap committed on main BEFORE branching — see "clean merge" test.
|
|
392
|
+
setupRoadmap(repo, "M020", "Conflict Test", ["S01: Conflict scenario"]);
|
|
393
|
+
|
|
312
394
|
// Create milestone branch that modifies README.md
|
|
313
395
|
run("git checkout -b milestone/M020", repo);
|
|
314
396
|
writeFileSync(join(repo, "README.md"), "# M020 version\n");
|
|
@@ -321,9 +403,6 @@ test("mergeCompletedMilestone — conflict returns structured error with file li
|
|
|
321
403
|
run("git add .", repo);
|
|
322
404
|
run('git commit -m "main changes README"', repo);
|
|
323
405
|
|
|
324
|
-
// Set up roadmap
|
|
325
|
-
setupRoadmap(repo, "M020", "Conflict Test", ["S01: Conflict scenario"]);
|
|
326
|
-
|
|
327
406
|
process.chdir(repo);
|
|
328
407
|
const result = await mergeCompletedMilestone(repo, "M020");
|
|
329
408
|
|
|
@@ -350,6 +429,12 @@ test("mergeAllCompleted — merges in sequential order", async () => {
|
|
|
350
429
|
const repo = createTempRepo();
|
|
351
430
|
|
|
352
431
|
try {
|
|
432
|
+
setupBranchIsolation(repo);
|
|
433
|
+
|
|
434
|
+
// Roadmaps committed on main BEFORE branching — see "clean merge" test.
|
|
435
|
+
setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
|
|
436
|
+
setupRoadmap(repo, "M002", "Dashboard", ["S01: Dashboard module"]);
|
|
437
|
+
|
|
353
438
|
// M001: adds auth.ts
|
|
354
439
|
createMilestoneBranch(repo, "M001", [
|
|
355
440
|
{ name: "auth.ts", content: "export const auth = true;\n" },
|
|
@@ -359,9 +444,6 @@ test("mergeAllCompleted — merges in sequential order", async () => {
|
|
|
359
444
|
{ name: "dashboard.ts", content: "export const dash = true;\n" },
|
|
360
445
|
]);
|
|
361
446
|
|
|
362
|
-
setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
|
|
363
|
-
setupRoadmap(repo, "M002", "Dashboard", ["S01: Dashboard module"]);
|
|
364
|
-
|
|
365
447
|
const workers = [
|
|
366
448
|
makeWorker({ milestoneId: "M002", startedAt: 100 }),
|
|
367
449
|
makeWorker({ milestoneId: "M001", startedAt: 200 }),
|
|
@@ -373,9 +455,9 @@ test("mergeAllCompleted — merges in sequential order", async () => {
|
|
|
373
455
|
// Both should succeed
|
|
374
456
|
assert.equal(results.length, 2, "should have two results");
|
|
375
457
|
assert.equal(results[0]!.milestoneId, "M001", "M001 merged first (sequential)");
|
|
376
|
-
assert.equal(results[0]!.success, true,
|
|
458
|
+
assert.equal(results[0]!.success, true, `M001 should succeed: ${results[0]!.error}`);
|
|
377
459
|
assert.equal(results[1]!.milestoneId, "M002", "M002 merged second");
|
|
378
|
-
assert.equal(results[1]!.success, true,
|
|
460
|
+
assert.equal(results[1]!.success, true, `M002 should succeed: ${results[1]!.error}`);
|
|
379
461
|
|
|
380
462
|
// Both files on main
|
|
381
463
|
assert.ok(existsSync(join(repo, "auth.ts")), "auth.ts on main");
|
|
@@ -391,6 +473,12 @@ test("mergeAllCompleted — stops on first conflict, skips later milestones", as
|
|
|
391
473
|
const repo = createTempRepo();
|
|
392
474
|
|
|
393
475
|
try {
|
|
476
|
+
setupBranchIsolation(repo);
|
|
477
|
+
|
|
478
|
+
// Roadmaps committed on main BEFORE branching — see "clean merge" test.
|
|
479
|
+
setupRoadmap(repo, "M001", "Conflict milestone", ["S01: Conflict test"]);
|
|
480
|
+
setupRoadmap(repo, "M002", "Clean milestone", ["S01: Clean test"]);
|
|
481
|
+
|
|
394
482
|
// M001: modifies README.md (will conflict with main)
|
|
395
483
|
run("git checkout -b milestone/M001", repo);
|
|
396
484
|
writeFileSync(join(repo, "README.md"), "# M001 version\n");
|
|
@@ -408,9 +496,6 @@ test("mergeAllCompleted — stops on first conflict, skips later milestones", as
|
|
|
408
496
|
run("git add .", repo);
|
|
409
497
|
run('git commit -m "main diverges README"', repo);
|
|
410
498
|
|
|
411
|
-
setupRoadmap(repo, "M001", "Conflict milestone", ["S01: Conflict test"]);
|
|
412
|
-
setupRoadmap(repo, "M002", "Clean milestone", ["S01: Clean test"]);
|
|
413
|
-
|
|
414
499
|
const workers = [
|
|
415
500
|
makeWorker({ milestoneId: "M001" }),
|
|
416
501
|
makeWorker({ milestoneId: "M002" }),
|
|
@@ -442,6 +527,12 @@ test("mergeAllCompleted — by-completion order respects startedAt", async () =>
|
|
|
442
527
|
const repo = createTempRepo();
|
|
443
528
|
|
|
444
529
|
try {
|
|
530
|
+
setupBranchIsolation(repo);
|
|
531
|
+
|
|
532
|
+
// Roadmaps committed on main BEFORE branching — see "clean merge" test.
|
|
533
|
+
setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
|
|
534
|
+
setupRoadmap(repo, "M002", "Feature", ["S01: Feature module"]);
|
|
535
|
+
|
|
445
536
|
// M001: adds auth.ts (started later)
|
|
446
537
|
createMilestoneBranch(repo, "M001", [
|
|
447
538
|
{ name: "auth.ts", content: "export const auth = true;\n" },
|
|
@@ -451,9 +542,6 @@ test("mergeAllCompleted — by-completion order respects startedAt", async () =>
|
|
|
451
542
|
{ name: "feature.ts", content: "export const feature = true;\n" },
|
|
452
543
|
]);
|
|
453
544
|
|
|
454
|
-
setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
|
|
455
|
-
setupRoadmap(repo, "M002", "Feature", ["S01: Feature module"]);
|
|
456
|
-
|
|
457
545
|
const workers = [
|
|
458
546
|
makeWorker({ milestoneId: "M001", startedAt: 2000 }),
|
|
459
547
|
makeWorker({ milestoneId: "M002", startedAt: 1000 }),
|
|
@@ -549,11 +637,15 @@ test("mergeAllCompleted — discovers DB-complete milestones when workers show e
|
|
|
549
637
|
const repo = createTempRepo();
|
|
550
638
|
|
|
551
639
|
try {
|
|
640
|
+
setupBranchIsolation(repo);
|
|
641
|
+
|
|
642
|
+
// Roadmap committed on main BEFORE branching — see "clean merge" test.
|
|
643
|
+
setupRoadmap(repo, "M011", "Feature System", ["S01: Feature module"]);
|
|
644
|
+
|
|
552
645
|
// Create milestone branch with a file
|
|
553
646
|
createMilestoneBranch(repo, "M011", [
|
|
554
647
|
{ name: "feature.ts", content: "export const feature = true;\n" },
|
|
555
648
|
]);
|
|
556
|
-
setupRoadmap(repo, "M011", "Feature System", ["S01: Feature module"]);
|
|
557
649
|
|
|
558
650
|
// Set up canonical DB showing M011 is complete
|
|
559
651
|
setupCanonicalDbWithWorktree(repo, "M011");
|
|
@@ -75,7 +75,6 @@ function makeMockDeps(
|
|
|
75
75
|
pruneQueueOrder: () => {},
|
|
76
76
|
isInAutoWorktree: () => false,
|
|
77
77
|
shouldUseWorktreeIsolation: () => false,
|
|
78
|
-
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
|
|
79
78
|
teardownAutoWorktree: () => {},
|
|
80
79
|
createAutoWorktree: () => "/tmp/wt",
|
|
81
80
|
captureIntegrationBranch: () => {},
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
renderPlanFromDb,
|
|
25
25
|
renderTaskPlanFromDb,
|
|
26
26
|
detectStaleRenders,
|
|
27
|
-
repairStaleRenders,
|
|
28
27
|
} from '../markdown-renderer.ts';
|
|
28
|
+
import { repairStaleRenders } from '../state-reconciliation/drift/stale-render.ts';
|
|
29
29
|
import {
|
|
30
30
|
parseRoadmap,
|
|
31
31
|
parsePlan,
|
|
@@ -29,20 +29,25 @@
|
|
|
29
29
|
|
|
30
30
|
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
31
31
|
import assert from "node:assert/strict";
|
|
32
|
-
import { mkdtempSync, rmSync, mkdirSync } from "node:fs";
|
|
32
|
+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
|
|
33
33
|
import { join } from "node:path";
|
|
34
34
|
import { tmpdir } from "node:os";
|
|
35
35
|
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
WorktreeLifecycle,
|
|
38
|
+
type WorktreeLifecycleDeps,
|
|
39
|
+
type WorktreeLifecycleTestOverrides,
|
|
40
|
+
} from "../worktree-lifecycle.ts";
|
|
37
41
|
import { WorktreeStateProjection } from "../worktree-state-projection.ts";
|
|
38
42
|
import { MergeConflictError } from "../git-service.ts";
|
|
39
43
|
import type { AutoSession } from "../auto/session.ts";
|
|
40
44
|
|
|
41
|
-
// Test-local:
|
|
42
|
-
// (shouldUseWorktreeIsolation, syncWorktreeStateBack,
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
45
|
+
// Test-local: extras the WorktreeResolver-era fixture passed but Lifecycle
|
|
46
|
+
// does not need (shouldUseWorktreeIsolation, syncWorktreeStateBack,
|
|
47
|
+
// captureIntegrationBranch). Lifecycle ignores them via structural typing.
|
|
48
|
+
// The C1-C4-inlined primitive overrides come from
|
|
49
|
+
// `WorktreeLifecycleTestOverrides`, the test seam exported by the Module.
|
|
50
|
+
type LegacyTestDeps = WorktreeLifecycleDeps & WorktreeLifecycleTestOverrides & {
|
|
46
51
|
shouldUseWorktreeIsolation?: () => boolean;
|
|
47
52
|
syncWorktreeStateBack?: (
|
|
48
53
|
mainBasePath: string,
|
|
@@ -50,6 +55,7 @@ type LegacyTestDeps = WorktreeLifecycleDeps & {
|
|
|
50
55
|
milestoneId: string,
|
|
51
56
|
) => { synced: string[] };
|
|
52
57
|
captureIntegrationBranch?: (basePath: string, mid: string | undefined) => void;
|
|
58
|
+
GitServiceImpl?: new (basePath: string, gitConfig: unknown) => unknown;
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
/**
|
|
@@ -96,7 +102,12 @@ function makeDeps(
|
|
|
96
102
|
enterAutoWorktree: () => "",
|
|
97
103
|
enterBranchModeForMilestone: () => undefined,
|
|
98
104
|
getAutoWorktreePath: () => null,
|
|
99
|
-
autoCommitCurrentBranch: (
|
|
105
|
+
autoCommitCurrentBranch: (
|
|
106
|
+
_basePath: string,
|
|
107
|
+
_unitType: string,
|
|
108
|
+
_unitId: string,
|
|
109
|
+
_taskContext?: unknown,
|
|
110
|
+
) => null,
|
|
100
111
|
getCurrentBranch: () => "worktree/M001",
|
|
101
112
|
checkoutBranch: () => undefined,
|
|
102
113
|
autoWorktreeBranch: (mid: string) => `worktree/${mid}`,
|
|
@@ -109,6 +120,8 @@ function makeDeps(
|
|
|
109
120
|
invalidateAllCaches: () => undefined,
|
|
110
121
|
captureIntegrationBranch: () => undefined,
|
|
111
122
|
worktreeProjection: new WorktreeStateProjection(),
|
|
123
|
+
// ADR-016 phase 2 / C4 (#5627): GitServiceImpl constructor → factory.
|
|
124
|
+
gitServiceFactory: () => ({}) as never,
|
|
112
125
|
...overrides,
|
|
113
126
|
};
|
|
114
127
|
}
|
|
@@ -128,14 +141,36 @@ function makeNotifyCtx(): {
|
|
|
128
141
|
|
|
129
142
|
describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", () => {
|
|
130
143
|
let baseDir: string;
|
|
144
|
+
const savedCwd = process.cwd();
|
|
131
145
|
|
|
132
146
|
beforeEach(() => {
|
|
133
147
|
baseDir = mkdtempSync(join(tmpdir(), "merge-conflict-stops-loop-"));
|
|
134
148
|
// Fake out a milestone directory so mergeAndExit reaches mergeMilestoneToMain.
|
|
135
149
|
mkdirSync(join(baseDir, ".gsd", "milestones", "M001"), { recursive: true });
|
|
150
|
+
// ADR-016 phase 2 / C1 (#5624): worktree-lifecycle.ts now calls
|
|
151
|
+
// node:fs.readFileSync directly (the dep was retired), so the roadmap
|
|
152
|
+
// file must exist on disk for the test to reach mergeMilestoneToMain.
|
|
153
|
+
writeFileSync(
|
|
154
|
+
join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
|
|
155
|
+
"# M001\n",
|
|
156
|
+
);
|
|
157
|
+
// ADR-016 phase 2 / C3 (#5626): `getIsolationMode` is also inlined.
|
|
158
|
+
// Without explicit isolation preferences the mode defaults to "none"
|
|
159
|
+
// and the merge short-circuits before the test's mocked
|
|
160
|
+
// `mergeMilestoneToMain` is reached. Write a preferences file so the
|
|
161
|
+
// standalone routes through worktree-mode merge.
|
|
162
|
+
writeFileSync(
|
|
163
|
+
join(baseDir, ".gsd", "preferences.md"),
|
|
164
|
+
"## Git\n- isolation: worktree\n",
|
|
165
|
+
);
|
|
136
166
|
});
|
|
137
167
|
|
|
138
168
|
afterEach(() => {
|
|
169
|
+
// ADR-016 phase 2 / C2 (#5625): the inlined `mergeMilestoneStandalone`
|
|
170
|
+
// chdirs into the project root before the merge body runs. Restore
|
|
171
|
+
// cwd before deleting `baseDir` so the next test's `process.cwd()`
|
|
172
|
+
// doesn't fail with ENOENT.
|
|
173
|
+
try { process.chdir(savedCwd); } catch { /* best-effort */ }
|
|
139
174
|
try {
|
|
140
175
|
rmSync(baseDir, { recursive: true, force: true });
|
|
141
176
|
} catch {
|
|
@@ -147,7 +182,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
|
|
|
147
182
|
const conflicted = ["src/feature.ts", "README.md"];
|
|
148
183
|
const roadmapPath = join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
|
|
149
184
|
const deps = makeDeps({
|
|
150
|
-
resolveMilestoneFile: (_base, _mid, type) =>
|
|
185
|
+
resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
|
|
151
186
|
type === "ROADMAP" ? roadmapPath : null,
|
|
152
187
|
readFileSync: () => "# M001\n",
|
|
153
188
|
mergeMilestoneToMain: () => {
|
|
@@ -178,7 +213,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
|
|
|
178
213
|
const roadmapPath = join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
|
|
179
214
|
class FakePermError extends Error {}
|
|
180
215
|
const deps = makeDeps({
|
|
181
|
-
resolveMilestoneFile: (_base, _mid, type) =>
|
|
216
|
+
resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
|
|
182
217
|
type === "ROADMAP" ? roadmapPath : null,
|
|
183
218
|
readFileSync: () => "# M001\n",
|
|
184
219
|
mergeMilestoneToMain: () => {
|
|
@@ -204,7 +239,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
|
|
|
204
239
|
test("successful merge does not throw", () => {
|
|
205
240
|
const roadmapPath = join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
|
|
206
241
|
const deps = makeDeps({
|
|
207
|
-
resolveMilestoneFile: (_base, _mid, type) =>
|
|
242
|
+
resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
|
|
208
243
|
type === "ROADMAP" ? roadmapPath : null,
|
|
209
244
|
readFileSync: () => "# M001\n",
|
|
210
245
|
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
|
|
@@ -1,55 +1,53 @@
|
|
|
1
|
-
// GSD
|
|
2
|
-
//
|
|
3
|
-
// Mirrors the private wrapText from notification-overlay.ts so its contract
|
|
4
|
-
// can be exercised without exporting internals.
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Regression tests for notification overlay wrapping and width-safe rendering.
|
|
5
3
|
|
|
6
4
|
import { describe, test } from "node:test";
|
|
7
5
|
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { visibleWidth } from "@gsd/pi-tui";
|
|
11
|
+
import { appendNotification, initNotificationStore, _resetNotificationStore } from "../notification-store.ts";
|
|
12
|
+
import { GSDNotificationOverlay, notificationOverlayOptions } from "../notification-overlay.ts";
|
|
13
|
+
import { wrapVisibleText } from "../tui/render-kit.ts";
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
const fakeTheme = {
|
|
16
|
+
fg: (_color: string, text: string) => text,
|
|
17
|
+
bold: (text: string) => text,
|
|
18
|
+
};
|
|
12
19
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
function assertLinesFit(lines: string[], width: number): void {
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
assert.ok(
|
|
23
|
+
visibleWidth(line) <= width,
|
|
24
|
+
`line exceeds maxWidth: visibleWidth=${visibleWidth(line)} max=${width}: "${line}"`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
describe("notification overlay — wrapText", () => {
|
|
22
30
|
test("short text returns single line", () => {
|
|
23
|
-
const result =
|
|
31
|
+
const result = wrapVisibleText("hello world", 80);
|
|
24
32
|
assert.deepStrictEqual(result, ["hello world"]);
|
|
25
33
|
});
|
|
26
34
|
|
|
27
35
|
test("long text wraps at word boundaries without exceeding maxWidth", () => {
|
|
28
36
|
const text = "This is a long notification message that should wrap across multiple lines";
|
|
29
|
-
const result =
|
|
37
|
+
const result = wrapVisibleText(text, 40);
|
|
30
38
|
assert.ok(result.length > 1, `expected multiple lines, got ${result.length}`);
|
|
31
|
-
|
|
32
|
-
assert.ok(
|
|
33
|
-
visibleWidth(line) <= 40,
|
|
34
|
-
`line exceeds maxWidth: "${line}" (${visibleWidth(line)})`,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
39
|
+
assertLinesFit(result, 40);
|
|
37
40
|
});
|
|
38
41
|
|
|
39
42
|
test("single word exceeding maxWidth is broken to fit column budget", () => {
|
|
40
|
-
const result =
|
|
41
|
-
|
|
42
|
-
assert.ok(
|
|
43
|
-
visibleWidth(line) <= 10,
|
|
44
|
-
`line exceeds maxWidth: "${line}" (${visibleWidth(line)})`,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
43
|
+
const result = wrapVisibleText("superlongwordthatexceedsmaxwidth", 10);
|
|
44
|
+
assertLinesFit(result, 10);
|
|
47
45
|
});
|
|
48
46
|
|
|
49
47
|
test("preserves all words across wrapped lines", () => {
|
|
50
48
|
const words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot"];
|
|
51
49
|
const text = words.join(" ");
|
|
52
|
-
const result =
|
|
50
|
+
const result = wrapVisibleText(text, 15);
|
|
53
51
|
const rejoined = result.join(" ");
|
|
54
52
|
for (const w of words) {
|
|
55
53
|
assert.ok(rejoined.includes(w), `missing word: ${w}`);
|
|
@@ -70,23 +68,62 @@ describe("notification overlay — wrapText", () => {
|
|
|
70
68
|
"(expired — will auto-refresh) ✗ google — not configured " +
|
|
71
69
|
"(aistudio.google.com/apikey) ✗ groq — not configured";
|
|
72
70
|
const maxWidth = 118;
|
|
73
|
-
const result =
|
|
74
|
-
|
|
75
|
-
assert.ok(
|
|
76
|
-
visibleWidth(line) <= maxWidth,
|
|
77
|
-
`line exceeds column budget: visibleWidth=${visibleWidth(line)} max=${maxWidth}: "${line}"`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
71
|
+
const result = wrapVisibleText(msg, maxWidth);
|
|
72
|
+
assertLinesFit(result, maxWidth);
|
|
80
73
|
});
|
|
81
74
|
|
|
82
75
|
test("unbreakable long token (URL) is clamped to maxWidth", () => {
|
|
83
76
|
const url = "https://example.com/" + "a".repeat(200);
|
|
84
|
-
const result =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
const result = wrapVisibleText(url, 40);
|
|
78
|
+
assertLinesFit(result, 40);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("real overlay render fits common terminal widths", (t) => {
|
|
82
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-notification-overlay-"));
|
|
83
|
+
t.after(() => {
|
|
84
|
+
_resetNotificationStore();
|
|
85
|
+
rmSync(dir, { recursive: true, force: true });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
initNotificationStore(dir);
|
|
89
|
+
appendNotification("A long notification with " + "x".repeat(180), "warning");
|
|
90
|
+
|
|
91
|
+
const overlay = new GSDNotificationOverlay({ requestRender() {} }, fakeTheme as any, () => {});
|
|
92
|
+
t.after(() => overlay.dispose());
|
|
93
|
+
|
|
94
|
+
for (const width of [40, 80, 120]) {
|
|
95
|
+
assertLinesFit(overlay.render(width), width);
|
|
96
|
+
overlay.invalidate();
|
|
90
97
|
}
|
|
91
98
|
});
|
|
99
|
+
|
|
100
|
+
test("rendered height stays within the configured overlay max height", (t) => {
|
|
101
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-notification-overlay-height-"));
|
|
102
|
+
const originalRowsDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "rows");
|
|
103
|
+
Object.defineProperty(process.stdout, "rows", { value: 40, configurable: true });
|
|
104
|
+
|
|
105
|
+
t.after(() => {
|
|
106
|
+
if (originalRowsDescriptor) {
|
|
107
|
+
Object.defineProperty(process.stdout, "rows", originalRowsDescriptor);
|
|
108
|
+
} else {
|
|
109
|
+
delete (process.stdout as { rows?: number }).rows;
|
|
110
|
+
}
|
|
111
|
+
_resetNotificationStore();
|
|
112
|
+
rmSync(dir, { recursive: true, force: true });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
initNotificationStore(dir);
|
|
116
|
+
for (let i = 0; i < 80; i++) {
|
|
117
|
+
appendNotification(`notification-${i + 1} with enough text to exercise clipping`, "warning");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const overlay = new GSDNotificationOverlay({ requestRender() {} }, fakeTheme as any, () => {});
|
|
121
|
+
t.after(() => overlay.dispose());
|
|
122
|
+
|
|
123
|
+
const rendered = overlay.render(100);
|
|
124
|
+
const maxHeight = Math.floor((40 * 52) / 100);
|
|
125
|
+
assert.ok(rendered.length <= maxHeight, `expected ${rendered.length} lines to fit maxHeight ${maxHeight}`);
|
|
126
|
+
assert.ok(rendered.at(-1)?.includes("╯"), "bottom border should remain visible");
|
|
127
|
+
assert.equal(notificationOverlayOptions().maxHeight, "52%");
|
|
128
|
+
});
|
|
92
129
|
});
|