gsd-pi 2.77.0-dev.eaa4973bc → 2.78.0-dev.aeeb2ca00
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 +53 -17
- package/dist/claude-cli-check.js +46 -10
- package/dist/headless.js +49 -4
- package/dist/resource-loader.d.ts +40 -0
- package/dist/resource-loader.js +32 -13
- package/dist/resources/extensions/browser-tools/capture.js +9 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
- package/dist/resources/extensions/claude-code-cli/readiness.js +72 -16
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
- package/dist/resources/extensions/github-sync/templates.js +103 -0
- package/dist/resources/extensions/google-search/index.js +3 -2
- package/dist/resources/extensions/gsd/auto/loop.js +124 -2
- package/dist/resources/extensions/gsd/auto/phases.js +57 -39
- package/dist/resources/extensions/gsd/auto/session.js +6 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +142 -29
- package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +150 -64
- package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
- package/dist/resources/extensions/gsd/auto-recovery.js +197 -48
- package/dist/resources/extensions/gsd/auto-start.js +107 -29
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
- package/dist/resources/extensions/gsd/auto.js +76 -21
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
- package/dist/resources/extensions/gsd/component-loader.js +447 -0
- package/dist/resources/extensions/gsd/component-types.js +69 -0
- package/dist/resources/extensions/gsd/context-store.js +23 -7
- package/dist/resources/extensions/gsd/detection.js +49 -1
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -17
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/forensics.js +106 -0
- package/dist/resources/extensions/gsd/gate-registry.js +2 -2
- package/dist/resources/extensions/gsd/git-constants.js +28 -1
- package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
- package/dist/resources/extensions/gsd/git-service.js +126 -2
- package/dist/resources/extensions/gsd/gsd-db.js +6 -3
- package/dist/resources/extensions/gsd/guided-flow.js +39 -13
- package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
- package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
- package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
- package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
- package/dist/resources/extensions/gsd/model-router.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
- package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
- package/dist/resources/extensions/gsd/service-tier.js +5 -2
- package/dist/resources/extensions/gsd/session-lock.js +19 -10
- package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
- package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
- package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
- package/dist/resources/extensions/gsd/state.js +69 -58
- package/dist/resources/extensions/gsd/sync-lock.js +98 -42
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
- package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
- package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
- package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
- package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
- package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
- package/dist/resources/extensions/gsd/uok/writer.js +82 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +85 -8
- package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
- package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
- package/dist/resources/extensions/mcp-client/index.js +3 -1
- package/dist/resources/extensions/ollama/index.js +5 -1
- package/dist/resources/extensions/remote-questions/manager.js +11 -5
- 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 +11 -11
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +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 +11 -11
- package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -3
- package/packages/daemon/package.json +2 -2
- package/packages/daemon/src/logger.ts +4 -3
- package/packages/mcp-server/dist/server.d.ts +24 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +88 -87
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +15 -6
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/mcp-server.test.ts +25 -3
- package/packages/mcp-server/src/readers/graph.test.ts +87 -15
- package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
- package/packages/mcp-server/src/server.ts +131 -105
- package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
- package/packages/mcp-server/src/workflow-tools.ts +19 -6
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +2 -2
- package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
- package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
- package/packages/native/src/__tests__/ps.test.mjs +14 -8
- package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
- package/packages/native/src/__tests__/truncate.test.mjs +17 -2
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
- package/packages/pi-agent-core/src/agent.test.ts +96 -102
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/capability-patches.js +9 -2
- package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
- package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.js +43 -70
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +36 -11
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +44 -0
- package/packages/pi-ai/src/models/capability-patches.ts +10 -2
- package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
- package/packages/pi-ai/src/models/generated/openai.ts +17 -0
- package/packages/pi-ai/src/models.generated.test.ts +46 -73
- package/packages/pi-ai/src/models.test.ts +48 -11
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
- 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/dynamic-border.test.js +20 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -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 +30 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
- package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
- package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
- package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
- package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
- package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +37 -11
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
- package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts +14 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +19 -0
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/image.test.js +6 -5
- package/packages/pi-tui/dist/components/image.test.js.map +1 -1
- package/packages/pi-tui/dist/editor-component.d.ts +2 -0
- package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/packages/pi-tui/dist/editor-component.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +42 -11
- package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
- package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
- package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
- package/packages/pi-tui/src/components/editor.ts +22 -0
- package/packages/pi-tui/src/components/image.test.ts +10 -5
- package/packages/pi-tui/src/editor-component.ts +3 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/dist/rpc-client.test.js +101 -51
- package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/packages/rpc-client/src/rpc-client.test.ts +109 -52
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/pkg/package.json +1 -1
- package/scripts/install.js +15 -1
- package/src/resources/extensions/browser-tools/capture.ts +12 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +75 -16
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
- package/src/resources/extensions/github-sync/templates.ts +151 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
- package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
- package/src/resources/extensions/google-search/index.ts +3 -2
- package/src/resources/extensions/gsd/auto/loop.ts +142 -2
- package/src/resources/extensions/gsd/auto/phases.ts +62 -38
- package/src/resources/extensions/gsd/auto/session.ts +7 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +156 -29
- package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +163 -73
- package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
- package/src/resources/extensions/gsd/auto-recovery.ts +230 -51
- package/src/resources/extensions/gsd/auto-start.ts +127 -9
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
- package/src/resources/extensions/gsd/auto.ts +90 -23
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
- package/src/resources/extensions/gsd/component-loader.ts +598 -0
- package/src/resources/extensions/gsd/component-types.ts +362 -0
- package/src/resources/extensions/gsd/context-store.ts +25 -8
- package/src/resources/extensions/gsd/detection.ts +58 -1
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -20
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/forensics.ts +118 -1
- package/src/resources/extensions/gsd/gate-registry.ts +2 -2
- package/src/resources/extensions/gsd/git-constants.ts +30 -1
- package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
- package/src/resources/extensions/gsd/git-service.ts +149 -2
- package/src/resources/extensions/gsd/gsd-db.ts +6 -3
- package/src/resources/extensions/gsd/guided-flow.ts +57 -14
- package/src/resources/extensions/gsd/journal.ts +11 -1
- package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
- package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
- package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
- package/src/resources/extensions/gsd/model-router.ts +6 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
- package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
- package/src/resources/extensions/gsd/service-tier.ts +5 -2
- package/src/resources/extensions/gsd/session-lock.ts +20 -10
- package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
- package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
- package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
- package/src/resources/extensions/gsd/state.ts +76 -66
- package/src/resources/extensions/gsd/sync-lock.ts +97 -39
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
- package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +133 -292
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
- package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
- package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
- package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
- package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
- package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
- package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
- package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
- package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
- package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
- package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
- package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
- package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
- package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
- package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
- package/src/resources/extensions/gsd/types.ts +3 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
- package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
- package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
- package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
- package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
- package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
- package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
- package/src/resources/extensions/gsd/uok/writer.ts +113 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +108 -7
- package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
- package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
- package/src/resources/extensions/mcp-client/index.ts +3 -1
- package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
- package/src/resources/extensions/ollama/index.ts +5 -1
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
- package/src/resources/extensions/remote-questions/manager.ts +36 -4
- package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
- package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
- package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
- package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
- package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
- package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
- package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
- /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_ssgManifest.js +0 -0
|
@@ -106,19 +106,29 @@ describe("CombinedAutocompleteProvider — argument completions", () => {
|
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
describe("CombinedAutocompleteProvider — @ file prefix extraction", () => {
|
|
109
|
-
it("detects @ at start of line", () => {
|
|
109
|
+
it("detects @ at start of line and returns a valid suggestion shape", () => {
|
|
110
110
|
const provider = makeProvider();
|
|
111
|
-
// @ triggers fuzzy file search — we can't test the actual file results
|
|
112
|
-
// but we can test that getSuggestions returns null (no files in /tmp matching)
|
|
113
|
-
// rather than crashing
|
|
114
111
|
const result = provider.getSuggestions(["@nonexistent_xyz"], 0, 16);
|
|
115
|
-
//
|
|
116
|
-
|
|
112
|
+
// Either null (nothing matched) or a well-formed {items: Array, prefix: string}
|
|
113
|
+
// shape. Previous version's `result.items.length >= 0` was a tautology —
|
|
114
|
+
// array length is always ≥ 0; the whole expression could never fail.
|
|
115
|
+
if (result !== null) {
|
|
116
|
+
assert.ok(Array.isArray(result.items), "result.items must be an array");
|
|
117
|
+
// The @-prefix extraction strips the leading @ — prefix should be
|
|
118
|
+
// the raw text without the trigger character.
|
|
119
|
+
assert.equal(typeof result.prefix, "string", "prefix must be a string");
|
|
120
|
+
assert.ok(!result.prefix.startsWith("@"), `prefix must have the @ trigger stripped, got: ${JSON.stringify(result.prefix)}`);
|
|
121
|
+
}
|
|
117
122
|
});
|
|
118
|
-
it("detects @ after space", () => {
|
|
123
|
+
it("detects @ after space and returns a valid suggestion shape", () => {
|
|
119
124
|
const provider = makeProvider();
|
|
120
125
|
const result = provider.getSuggestions(["check @nonexistent_xyz"], 0, 22);
|
|
121
|
-
|
|
126
|
+
if (result !== null) {
|
|
127
|
+
assert.ok(Array.isArray(result.items), "result.items must be an array");
|
|
128
|
+
assert.equal(typeof result.prefix, "string", "prefix must be a string");
|
|
129
|
+
// The prefix must NOT include the word "check" that came before the @.
|
|
130
|
+
assert.ok(!result.prefix.includes("check"), `prefix must not include text before the @, got: ${JSON.stringify(result.prefix)}`);
|
|
131
|
+
}
|
|
122
132
|
});
|
|
123
133
|
it("returns null for bare @ with no query to avoid full tree walk (#1824)", () => {
|
|
124
134
|
const provider = makeProvider([], process.cwd());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autocomplete.test.js","sourceRoot":"","sources":["../../src/__tests__/autocomplete.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { SlashCommand } from \"../autocomplete.js\";\n\nfunction makeProvider(commands: SlashCommand[] = [], basePath: string = \"/tmp\") {\n\treturn new CombinedAutocompleteProvider(commands, basePath);\n}\n\nconst sampleCommands: SlashCommand[] = [\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model\" },\n\t{ name: \"session\", description: \"Show session info\" },\n\t{ name: \"export\", description: \"Export session\" },\n\t{ name: \"thinking\", description: \"Set thinking level\" },\n];\n\ndescribe(\"CombinedAutocompleteProvider — slash commands\", () => {\n\tit(\"returns all commands for bare /\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/\"], 0, 1);\n\t\tassert.ok(result, \"should return suggestions\");\n\t\tassert.equal(result!.items.length, sampleCommands.length);\n\t\tassert.equal(result!.prefix, \"/\");\n\t});\n\n\tit(\"filters commands by typed prefix\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/se\"], 0, 3);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 2); // settings, session\n\t\tassert.ok(result!.items.some((i) => i.value === \"settings\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"session\"));\n\t});\n\n\tit(\"returns null when no commands match\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/zzz\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"includes description in suggestions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/mod\"], 0, 4);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items[0]?.description, \"Select model\");\n\t});\n\n\tit(\"does not offer slash command suggestions mid-line\", () => {\n\t\tconst sentinelCommands: SlashCommand[] = [\n\t\t\t{ name: \"codexmidlinecommand\", description: \"Sentinel slash command\" },\n\t\t];\n\t\tconst provider = makeProvider(sentinelCommands);\n\t\tconst line = \"hello /codexmid\";\n\t\tconst result = provider.getSuggestions([line], 0, line.length);\n\n\t\tif (result === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.value !== \"codexmidlinecommand\"),\n\t\t\t\"mid-line slash-like text should not return slash command completions\",\n\t\t);\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.description !== \"Sentinel slash command\"),\n\t\t\t\"mid-line slash-like text should not return slash command metadata\",\n\t\t);\n\t});\n\n\tit(\"triggers slash commands after leading whitespace\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\" /se\"], 0, 5);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.prefix, \"/se\");\n\t\tassert.ok(result!.items.some((item) => item.value === \"settings\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — argument completions\", () => {\n\tit(\"returns argument completions for commands that support them\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"thinking\",\n\t\t\t\tdescription: \"Set thinking level\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst levels = [\"off\", \"low\", \"medium\", \"high\"];\n\t\t\t\t\tconst filtered = levels\n\t\t\t\t\t\t.filter((l) => l.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((l) => ({ value: l, label: l }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/thinking m\"], 0, 11);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 1);\n\t\tassert.equal(result!.items[0]?.value, \"medium\");\n\t});\n\n\tit(\"returns null for commands without argument completions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/settings foo\"], 0, 13);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"returns all arg completions for empty prefix after space\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"test\",\n\t\t\t\tdescription: \"Test command\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst subs = [\"start\", \"stop\", \"status\"];\n\t\t\t\t\tconst filtered = subs\n\t\t\t\t\t\t.filter((s) => s.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((s) => ({ value: s, label: s }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/test \"], 0, 6);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 3);\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — @ file prefix extraction\", () => {\n\tit(\"detects @ at start of line\", () => {\n\t\tconst provider = makeProvider();\n\t\t// @ triggers fuzzy file search — we can't test the actual file results\n\t\t// but we can test that getSuggestions returns null (no files in /tmp matching)\n\t\t// rather than crashing\n\t\tconst result = provider.getSuggestions([\"@nonexistent_xyz\"], 0, 16);\n\t\t// May return null or empty — the key thing is it doesn't crash\n\t\tassert.ok(result === null || result.items.length >= 0);\n\t});\n\n\tit(\"detects @ after space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"check @nonexistent_xyz\"], 0, 22);\n\t\tassert.ok(result === null || result.items.length >= 0);\n\t});\n\n\tit(\"returns null for bare @ with no query to avoid full tree walk (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\t// A bare \"@\" produces an empty rawPrefix after stripping the \"@\".\n\t\t// This must return null to avoid a synchronous full filesystem walk\n\t\t// via the native fuzzyFind addon, which freezes the TUI on large repos.\n\t\tconst result = provider.getSuggestions([\"@\"], 0, 1);\n\t\tassert.equal(result, null, \"bare @ should not trigger fuzzy file search\");\n\t});\n\n\tit(\"returns null for @ after space with no query (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\tconst result = provider.getSuggestions([\"look at @\"], 0, 9);\n\t\tassert.equal(result, null, \"@ after space with no query should not trigger fuzzy file search\");\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — applyCompletion\", () => {\n\tit(\"applies slash command completion with trailing space\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\"/se\"], 0, 3, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \"/settings \");\n\t\tassert.equal(result.cursorCol, 10); // after \"/settings \"\n\t});\n\n\tit(\"preserves leading whitespace when applying slash command completion\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\" /se\"], 0, 5, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \" /settings \");\n\t\tassert.equal(result.cursorCol, 12);\n\t});\n\n\tit(\"applies file path completion for @ prefix\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@src/\"],\n\t\t\t0,\n\t\t\t5,\n\t\t\t{ value: \"@src/index.ts\", label: \"index.ts\" },\n\t\t\t\"@src/\",\n\t\t);\n\t\tassert.equal(result.lines[0], \"@src/index.ts \");\n\t});\n\n\tit(\"applies directory completion without trailing space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@sr\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"@src/\", label: \"src/\" },\n\t\t\t\"@sr\",\n\t\t);\n\t\t// Directories should not get trailing space so user can continue typing\n\t\tassert.ok(!result.lines[0]!.endsWith(\" \"));\n\t});\n\n\tit(\"preserves text after cursor\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"/se and more text\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"settings\", label: \"settings\" },\n\t\t\t\"/se\",\n\t\t);\n\t\tassert.ok(result.lines[0]!.includes(\"and more text\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — force file suggestions\", () => {\n\tit(\"does not trigger for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getForceFileSuggestions([\"/set\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns false for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"/set\"], 0, 4), false);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns true for regular text\", () => {\n\t\tconst provider = makeProvider();\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"some text\"], 0, 9), true);\n\t});\n});\n"]}
|
|
1
|
+
{"version":3,"file":"autocomplete.test.js","sourceRoot":"","sources":["../../src/__tests__/autocomplete.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { SlashCommand } from \"../autocomplete.js\";\n\nfunction makeProvider(commands: SlashCommand[] = [], basePath: string = \"/tmp\") {\n\treturn new CombinedAutocompleteProvider(commands, basePath);\n}\n\nconst sampleCommands: SlashCommand[] = [\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model\" },\n\t{ name: \"session\", description: \"Show session info\" },\n\t{ name: \"export\", description: \"Export session\" },\n\t{ name: \"thinking\", description: \"Set thinking level\" },\n];\n\ndescribe(\"CombinedAutocompleteProvider — slash commands\", () => {\n\tit(\"returns all commands for bare /\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/\"], 0, 1);\n\t\tassert.ok(result, \"should return suggestions\");\n\t\tassert.equal(result!.items.length, sampleCommands.length);\n\t\tassert.equal(result!.prefix, \"/\");\n\t});\n\n\tit(\"filters commands by typed prefix\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/se\"], 0, 3);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 2); // settings, session\n\t\tassert.ok(result!.items.some((i) => i.value === \"settings\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"session\"));\n\t});\n\n\tit(\"returns null when no commands match\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/zzz\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"includes description in suggestions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/mod\"], 0, 4);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items[0]?.description, \"Select model\");\n\t});\n\n\tit(\"does not offer slash command suggestions mid-line\", () => {\n\t\tconst sentinelCommands: SlashCommand[] = [\n\t\t\t{ name: \"codexmidlinecommand\", description: \"Sentinel slash command\" },\n\t\t];\n\t\tconst provider = makeProvider(sentinelCommands);\n\t\tconst line = \"hello /codexmid\";\n\t\tconst result = provider.getSuggestions([line], 0, line.length);\n\n\t\tif (result === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.value !== \"codexmidlinecommand\"),\n\t\t\t\"mid-line slash-like text should not return slash command completions\",\n\t\t);\n\t\tassert.ok(\n\t\t\tresult.items.every((item) => item.description !== \"Sentinel slash command\"),\n\t\t\t\"mid-line slash-like text should not return slash command metadata\",\n\t\t);\n\t});\n\n\tit(\"triggers slash commands after leading whitespace\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\" /se\"], 0, 5);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.prefix, \"/se\");\n\t\tassert.ok(result!.items.some((item) => item.value === \"settings\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — argument completions\", () => {\n\tit(\"returns argument completions for commands that support them\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"thinking\",\n\t\t\t\tdescription: \"Set thinking level\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst levels = [\"off\", \"low\", \"medium\", \"high\"];\n\t\t\t\t\tconst filtered = levels\n\t\t\t\t\t\t.filter((l) => l.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((l) => ({ value: l, label: l }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/thinking m\"], 0, 11);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 1);\n\t\tassert.equal(result!.items[0]?.value, \"medium\");\n\t});\n\n\tit(\"returns null for commands without argument completions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/settings foo\"], 0, 13);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"returns all arg completions for empty prefix after space\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"test\",\n\t\t\t\tdescription: \"Test command\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst subs = [\"start\", \"stop\", \"status\"];\n\t\t\t\t\tconst filtered = subs\n\t\t\t\t\t\t.filter((s) => s.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((s) => ({ value: s, label: s }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/test \"], 0, 6);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 3);\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — @ file prefix extraction\", () => {\n\tit(\"detects @ at start of line and returns a valid suggestion shape\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"@nonexistent_xyz\"], 0, 16);\n\t\t// Either null (nothing matched) or a well-formed {items: Array, prefix: string}\n\t\t// shape. Previous version's `result.items.length >= 0` was a tautology —\n\t\t// array length is always ≥ 0; the whole expression could never fail.\n\t\tif (result !== null) {\n\t\t\tassert.ok(Array.isArray(result.items), \"result.items must be an array\");\n\t\t\t// The @-prefix extraction strips the leading @ — prefix should be\n\t\t\t// the raw text without the trigger character.\n\t\t\tassert.equal(typeof result.prefix, \"string\", \"prefix must be a string\");\n\t\t\tassert.ok(\n\t\t\t\t!result.prefix.startsWith(\"@\"),\n\t\t\t\t`prefix must have the @ trigger stripped, got: ${JSON.stringify(result.prefix)}`,\n\t\t\t);\n\t\t}\n\t});\n\n\tit(\"detects @ after space and returns a valid suggestion shape\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"check @nonexistent_xyz\"], 0, 22);\n\t\tif (result !== null) {\n\t\t\tassert.ok(Array.isArray(result.items), \"result.items must be an array\");\n\t\t\tassert.equal(typeof result.prefix, \"string\", \"prefix must be a string\");\n\t\t\t// The prefix must NOT include the word \"check\" that came before the @.\n\t\t\tassert.ok(\n\t\t\t\t!result.prefix.includes(\"check\"),\n\t\t\t\t`prefix must not include text before the @, got: ${JSON.stringify(result.prefix)}`,\n\t\t\t);\n\t\t}\n\t});\n\n\tit(\"returns null for bare @ with no query to avoid full tree walk (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\t// A bare \"@\" produces an empty rawPrefix after stripping the \"@\".\n\t\t// This must return null to avoid a synchronous full filesystem walk\n\t\t// via the native fuzzyFind addon, which freezes the TUI on large repos.\n\t\tconst result = provider.getSuggestions([\"@\"], 0, 1);\n\t\tassert.equal(result, null, \"bare @ should not trigger fuzzy file search\");\n\t});\n\n\tit(\"returns null for @ after space with no query (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\tconst result = provider.getSuggestions([\"look at @\"], 0, 9);\n\t\tassert.equal(result, null, \"@ after space with no query should not trigger fuzzy file search\");\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — applyCompletion\", () => {\n\tit(\"applies slash command completion with trailing space\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\"/se\"], 0, 3, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \"/settings \");\n\t\tassert.equal(result.cursorCol, 10); // after \"/settings \"\n\t});\n\n\tit(\"preserves leading whitespace when applying slash command completion\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\" /se\"], 0, 5, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \" /settings \");\n\t\tassert.equal(result.cursorCol, 12);\n\t});\n\n\tit(\"applies file path completion for @ prefix\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@src/\"],\n\t\t\t0,\n\t\t\t5,\n\t\t\t{ value: \"@src/index.ts\", label: \"index.ts\" },\n\t\t\t\"@src/\",\n\t\t);\n\t\tassert.equal(result.lines[0], \"@src/index.ts \");\n\t});\n\n\tit(\"applies directory completion without trailing space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@sr\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"@src/\", label: \"src/\" },\n\t\t\t\"@sr\",\n\t\t);\n\t\t// Directories should not get trailing space so user can continue typing\n\t\tassert.ok(!result.lines[0]!.endsWith(\" \"));\n\t});\n\n\tit(\"preserves text after cursor\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"/se and more text\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"settings\", label: \"settings\" },\n\t\t\t\"/se\",\n\t\t);\n\t\tassert.ok(result.lines[0]!.includes(\"and more text\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — force file suggestions\", () => {\n\tit(\"does not trigger for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getForceFileSuggestions([\"/set\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns false for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"/set\"], 0, 4), false);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns true for regular text\", () => {\n\t\tconst provider = makeProvider();\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"some text\"], 0, 9), true);\n\t});\n});\n"]}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
// pi-tui — Overlay Layout Tests (backdrop dimming)
|
|
2
|
+
//
|
|
3
|
+
// These tests previously coupled to literal ANSI escape bytes
|
|
4
|
+
// (`\x1b[2m`, `\x1b[38;5;240m`) and would break if the palette index or
|
|
5
|
+
// SGR spelling changed despite identical rendered output — Goodhart's law:
|
|
6
|
+
// the test measures escape codes, not dimming.
|
|
7
|
+
//
|
|
8
|
+
// We now parse the SGR escape codes into a semantic style state and assert
|
|
9
|
+
// on the visible contract: the covered-but-outside-overlay region is dim,
|
|
10
|
+
// has a non-default foreground (so the eye can distinguish foreground from
|
|
11
|
+
// background), and does not paint the terminal background (so user themes
|
|
12
|
+
// are preserved). The overlay content itself is reachable via plain-text
|
|
13
|
+
// lookup after stripping ANSI.
|
|
2
14
|
import { describe, it } from "node:test";
|
|
3
15
|
import assert from "node:assert/strict";
|
|
4
16
|
import { compositeOverlays } from "../overlay-layout.js";
|
|
@@ -10,8 +22,102 @@ function makeEntry(lines, options) {
|
|
|
10
22
|
focusOrder: 1,
|
|
11
23
|
};
|
|
12
24
|
}
|
|
25
|
+
function sgrStateAtGlyph(line, targetGlyph) {
|
|
26
|
+
const state = { dim: false, fg: "default", bg: "default" };
|
|
27
|
+
// Walk codes and visible chars, tracking visible-glyph position.
|
|
28
|
+
let visibleSeen = "";
|
|
29
|
+
let i = 0;
|
|
30
|
+
while (i < line.length) {
|
|
31
|
+
if (line[i] === "\x1b" && line[i + 1] === "[") {
|
|
32
|
+
// Read until final byte in 0x40-0x7E
|
|
33
|
+
let j = i + 2;
|
|
34
|
+
while (j < line.length) {
|
|
35
|
+
const c = line.charCodeAt(j);
|
|
36
|
+
if (c >= 0x40 && c <= 0x7e)
|
|
37
|
+
break;
|
|
38
|
+
j++;
|
|
39
|
+
}
|
|
40
|
+
const final = line[j];
|
|
41
|
+
if (final === "m") {
|
|
42
|
+
const paramString = line.slice(i + 2, j);
|
|
43
|
+
applySgr(state, paramString);
|
|
44
|
+
}
|
|
45
|
+
i = j + 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// Skip other escape sequences (OSC hyperlinks etc) conservatively:
|
|
49
|
+
// if we ever hit a non-SGR escape, just step past the ESC.
|
|
50
|
+
if (line[i] === "\x1b") {
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
visibleSeen += line[i];
|
|
55
|
+
if (visibleSeen.endsWith(targetGlyph)) {
|
|
56
|
+
// We've just consumed the last char of targetGlyph — return the
|
|
57
|
+
// state that was in effect for the whole match.
|
|
58
|
+
return state;
|
|
59
|
+
}
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Target glyph ${JSON.stringify(targetGlyph)} not found in line`);
|
|
63
|
+
}
|
|
64
|
+
function applySgr(state, paramString) {
|
|
65
|
+
// Empty params == reset
|
|
66
|
+
const parts = paramString === "" ? ["0"] : paramString.split(";");
|
|
67
|
+
let k = 0;
|
|
68
|
+
while (k < parts.length) {
|
|
69
|
+
const n = parts[k] === "" ? 0 : Number(parts[k]);
|
|
70
|
+
if (n === 0) {
|
|
71
|
+
state.dim = false;
|
|
72
|
+
state.fg = "default";
|
|
73
|
+
state.bg = "default";
|
|
74
|
+
}
|
|
75
|
+
else if (n === 2) {
|
|
76
|
+
state.dim = true;
|
|
77
|
+
}
|
|
78
|
+
else if (n === 22) {
|
|
79
|
+
state.dim = false;
|
|
80
|
+
}
|
|
81
|
+
else if (n === 39) {
|
|
82
|
+
state.fg = "default";
|
|
83
|
+
}
|
|
84
|
+
else if (n === 49) {
|
|
85
|
+
state.bg = "default";
|
|
86
|
+
}
|
|
87
|
+
else if ((n >= 30 && n <= 37) || (n >= 90 && n <= 97)) {
|
|
88
|
+
state.fg = "set";
|
|
89
|
+
}
|
|
90
|
+
else if ((n >= 40 && n <= 47) || (n >= 100 && n <= 107)) {
|
|
91
|
+
state.bg = "set";
|
|
92
|
+
}
|
|
93
|
+
else if (n === 38) {
|
|
94
|
+
state.fg = "set";
|
|
95
|
+
// Skip colour-model parameters: 38;5;N or 38;2;R;G;B
|
|
96
|
+
if (parts[k + 1] === "5") {
|
|
97
|
+
k += 2;
|
|
98
|
+
}
|
|
99
|
+
else if (parts[k + 1] === "2") {
|
|
100
|
+
k += 4;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (n === 48) {
|
|
104
|
+
state.bg = "set";
|
|
105
|
+
if (parts[k + 1] === "5") {
|
|
106
|
+
k += 2;
|
|
107
|
+
}
|
|
108
|
+
else if (parts[k + 1] === "2") {
|
|
109
|
+
k += 4;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
k++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function stripAnsi(line) {
|
|
116
|
+
// Remove CSI sequences. Good enough for these tests.
|
|
117
|
+
return line.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
118
|
+
}
|
|
13
119
|
describe("compositeOverlays — backdrop", () => {
|
|
14
|
-
it("dims base lines when backdrop is true", () => {
|
|
120
|
+
it("dims base lines outside the overlay when backdrop is true", () => {
|
|
15
121
|
const base = ["hello world", "second line"];
|
|
16
122
|
const overlay = makeEntry(["OVERLAY"], {
|
|
17
123
|
width: 7,
|
|
@@ -19,13 +125,15 @@ describe("compositeOverlays — backdrop", () => {
|
|
|
19
125
|
backdrop: true,
|
|
20
126
|
});
|
|
21
127
|
const result = compositeOverlays(base, [overlay], 20, 20, 2);
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
assert.ok(
|
|
128
|
+
// "second line" is below the overlay (which is anchored top-left with
|
|
129
|
+
// a single visible row), so every glyph of that text should be
|
|
130
|
+
// rendered with the dim attribute active.
|
|
131
|
+
const line = result.find((l) => stripAnsi(l).includes("second line"));
|
|
132
|
+
assert.ok(line, "should have a line containing 'second line'");
|
|
133
|
+
const state = sgrStateAtGlyph(line, "second line");
|
|
134
|
+
assert.equal(state.dim, true, "base line should be dimmed (SGR 2)");
|
|
27
135
|
});
|
|
28
|
-
it("backdrop
|
|
136
|
+
it("backdrop applies a non-default foreground colour and leaves background untouched", () => {
|
|
29
137
|
const base = ["hello world", "second line"];
|
|
30
138
|
const overlay = makeEntry(["OV"], {
|
|
31
139
|
width: 2,
|
|
@@ -33,11 +141,11 @@ describe("compositeOverlays — backdrop", () => {
|
|
|
33
141
|
backdrop: true,
|
|
34
142
|
});
|
|
35
143
|
const result = compositeOverlays(base, [overlay], 20, 20, 2);
|
|
36
|
-
|
|
37
|
-
const line = result.find((l) => l.includes("second line"));
|
|
144
|
+
const line = result.find((l) => stripAnsi(l).includes("second line"));
|
|
38
145
|
assert.ok(line, "should have a line containing 'second line'");
|
|
39
|
-
|
|
40
|
-
assert.
|
|
146
|
+
const state = sgrStateAtGlyph(line, "second line");
|
|
147
|
+
assert.equal(state.fg, "set", "backdrop must set a foreground colour");
|
|
148
|
+
assert.equal(state.bg, "default", "backdrop must not paint a background (preserves user's terminal theme)");
|
|
41
149
|
});
|
|
42
150
|
it("does not dim when backdrop is false/absent", () => {
|
|
43
151
|
const base = ["hello world", "second line"];
|
|
@@ -46,10 +154,10 @@ describe("compositeOverlays — backdrop", () => {
|
|
|
46
154
|
anchor: "top-left",
|
|
47
155
|
});
|
|
48
156
|
const result = compositeOverlays(base, [overlay], 20, 20, 2);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
assert.
|
|
157
|
+
const line = result.find((l) => stripAnsi(l).includes("second line"));
|
|
158
|
+
assert.ok(line, "should have a line containing 'second line'");
|
|
159
|
+
const state = sgrStateAtGlyph(line, "second line");
|
|
160
|
+
assert.equal(state.dim, false, "base line should not be dimmed when no backdrop");
|
|
53
161
|
});
|
|
54
162
|
it("overlay content renders on top of dimmed background", () => {
|
|
55
163
|
const base = ["aaaaaaaaaa"];
|
|
@@ -59,8 +167,11 @@ describe("compositeOverlays — backdrop", () => {
|
|
|
59
167
|
backdrop: true,
|
|
60
168
|
});
|
|
61
169
|
const result = compositeOverlays(base, [overlay], 10, 10, 1);
|
|
62
|
-
//
|
|
63
|
-
|
|
170
|
+
// Find the row that (after stripping styling) contains the overlay
|
|
171
|
+
// text. We don't use positional `result[0]` so the test survives if
|
|
172
|
+
// the row ordering changes.
|
|
173
|
+
const overlayRow = result.find((l) => stripAnsi(l).includes("XX"));
|
|
174
|
+
assert.ok(overlayRow, "overlay text should be composited into some rendered row");
|
|
64
175
|
});
|
|
65
176
|
});
|
|
66
177
|
//# sourceMappingURL=overlay-layout.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay-layout.test.js","sourceRoot":"","sources":["../../src/__tests__/overlay-layout.test.ts"],"names":[],"mappings":"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","sourcesContent":["// pi-tui — Overlay Layout Tests (backdrop dimming)\n\nimport { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { compositeOverlays, type OverlayEntry } from \"../overlay-layout.js\";\n\nfunction makeEntry(\n\tlines: string[],\n\toptions?: OverlayEntry[\"options\"],\n): OverlayEntry {\n\treturn {\n\t\tcomponent: { render: () => lines },\n\t\toptions,\n\t\thidden: false,\n\t\tfocusOrder: 1,\n\t};\n}\n\ndescribe(\"compositeOverlays — backdrop\", () => {\n\tit(\"dims base lines when backdrop is true\", () => {\n\t\tconst base = [\"hello world\", \"second line\"];\n\t\tconst overlay = makeEntry([\"OVERLAY\"], {\n\t\t\twidth: 7,\n\t\t\tanchor: \"top-left\",\n\t\t\tbackdrop: true,\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 20, 20, 2);\n\n\t\t// All base lines in viewport should contain dim escape (\\x1b[2m)\n\t\t// The overlay line itself is composited on top, but underlying lines get dimmed\n\t\tconst dimmedLine = result.find((l) => l.includes(\"second line\"));\n\t\tassert.ok(dimmedLine, \"should have a line containing 'second line'\");\n\t\tassert.ok(dimmedLine.includes(\"\\x1b[2m\"), \"base line should be dimmed\");\n\t});\n\n\tit(\"backdrop uses gray foreground for dimming\", () => {\n\t\tconst base = [\"hello world\", \"second line\"];\n\t\tconst overlay = makeEntry([\"OV\"], {\n\t\t\twidth: 2,\n\t\t\tanchor: \"top-left\",\n\t\t\tbackdrop: true,\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 20, 20, 2);\n\n\t\t// Check a non-overlay line for backdrop codes (dim + gray fg, no bg)\n\t\tconst line = result.find((l) => l.includes(\"second line\"));\n\t\tassert.ok(line, \"should have a line containing 'second line'\");\n\t\tassert.ok(line.includes(\"\\x1b[38;5;240m\"), \"backdrop should set gray foreground\");\n\t\tassert.ok(!line.includes(\"\\x1b[48;\"), \"backdrop should not set background color\");\n\t});\n\n\tit(\"does not dim when backdrop is false/absent\", () => {\n\t\tconst base = [\"hello world\", \"second line\"];\n\t\tconst overlay = makeEntry([\"OVERLAY\"], {\n\t\t\twidth: 7,\n\t\t\tanchor: \"top-left\",\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 20, 20, 2);\n\n\t\t// Lines not covered by overlay should remain undimmed\n\t\tconst secondLine = result.find((l) => l.includes(\"second line\"));\n\t\tassert.ok(secondLine, \"should have a line containing 'second line'\");\n\t\tassert.ok(!secondLine.includes(\"\\x1b[2m\"), \"base line should not be dimmed\");\n\t});\n\n\tit(\"overlay content renders on top of dimmed background\", () => {\n\t\tconst base = [\"aaaaaaaaaa\"];\n\t\tconst overlay = makeEntry([\"XX\"], {\n\t\t\twidth: 2,\n\t\t\tanchor: \"top-left\",\n\t\t\tbackdrop: true,\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 10, 10, 1);\n\n\t\t// The first line should contain the overlay text\n\t\tassert.ok(result[0].includes(\"XX\"), \"overlay text should be composited\");\n\t});\n});\n"]}
|
|
1
|
+
{"version":3,"file":"overlay-layout.test.js","sourceRoot":"","sources":["../../src/__tests__/overlay-layout.test.ts"],"names":[],"mappings":"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","sourcesContent":["// pi-tui — Overlay Layout Tests (backdrop dimming)\n//\n// These tests previously coupled to literal ANSI escape bytes\n// (`\\x1b[2m`, `\\x1b[38;5;240m`) and would break if the palette index or\n// SGR spelling changed despite identical rendered output — Goodhart's law:\n// the test measures escape codes, not dimming.\n//\n// We now parse the SGR escape codes into a semantic style state and assert\n// on the visible contract: the covered-but-outside-overlay region is dim,\n// has a non-default foreground (so the eye can distinguish foreground from\n// background), and does not paint the terminal background (so user themes\n// are preserved). The overlay content itself is reachable via plain-text\n// lookup after stripping ANSI.\n\nimport { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { compositeOverlays, type OverlayEntry } from \"../overlay-layout.js\";\n\nfunction makeEntry(\n\tlines: string[],\n\toptions?: OverlayEntry[\"options\"],\n): OverlayEntry {\n\treturn {\n\t\tcomponent: { render: () => lines },\n\t\toptions,\n\t\thidden: false,\n\t\tfocusOrder: 1,\n\t};\n}\n\n/**\n * Parse a line's ANSI SGR state immediately before the first occurrence of a\n * target substring in the rendered (ANSI-stripped) text. Walks `\\x1b[...m`\n * sequences left-to-right, maintaining a running state so we can ask what\n * the terminal is doing when it reaches the target glyphs.\n *\n * Semantic fields:\n * - dim: SGR 2 active and not reset by SGR 22 / 0\n * - fg: foreground: \"default\" | \"set\" | the raw numeric parameters\n * - bg: background: \"default\" | \"set\"\n */\ntype SgrState = { dim: boolean; fg: \"default\" | \"set\"; bg: \"default\" | \"set\" };\n\nfunction sgrStateAtGlyph(line: string, targetGlyph: string): SgrState {\n\tconst state: SgrState = { dim: false, fg: \"default\", bg: \"default\" };\n\t// Walk codes and visible chars, tracking visible-glyph position.\n\tlet visibleSeen = \"\";\n\tlet i = 0;\n\twhile (i < line.length) {\n\t\tif (line[i] === \"\\x1b\" && line[i + 1] === \"[\") {\n\t\t\t// Read until final byte in 0x40-0x7E\n\t\t\tlet j = i + 2;\n\t\t\twhile (j < line.length) {\n\t\t\t\tconst c = line.charCodeAt(j);\n\t\t\t\tif (c >= 0x40 && c <= 0x7e) break;\n\t\t\t\tj++;\n\t\t\t}\n\t\t\tconst final = line[j];\n\t\t\tif (final === \"m\") {\n\t\t\t\tconst paramString = line.slice(i + 2, j);\n\t\t\t\tapplySgr(state, paramString);\n\t\t\t}\n\t\t\ti = j + 1;\n\t\t\tcontinue;\n\t\t}\n\t\t// Skip other escape sequences (OSC hyperlinks etc) conservatively:\n\t\t// if we ever hit a non-SGR escape, just step past the ESC.\n\t\tif (line[i] === \"\\x1b\") {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\t\tvisibleSeen += line[i];\n\t\tif (visibleSeen.endsWith(targetGlyph)) {\n\t\t\t// We've just consumed the last char of targetGlyph — return the\n\t\t\t// state that was in effect for the whole match.\n\t\t\treturn state;\n\t\t}\n\t\ti++;\n\t}\n\tthrow new Error(`Target glyph ${JSON.stringify(targetGlyph)} not found in line`);\n}\n\nfunction applySgr(state: SgrState, paramString: string): void {\n\t// Empty params == reset\n\tconst parts = paramString === \"\" ? [\"0\"] : paramString.split(\";\");\n\tlet k = 0;\n\twhile (k < parts.length) {\n\t\tconst n = parts[k] === \"\" ? 0 : Number(parts[k]);\n\t\tif (n === 0) {\n\t\t\tstate.dim = false;\n\t\t\tstate.fg = \"default\";\n\t\t\tstate.bg = \"default\";\n\t\t} else if (n === 2) {\n\t\t\tstate.dim = true;\n\t\t} else if (n === 22) {\n\t\t\tstate.dim = false;\n\t\t} else if (n === 39) {\n\t\t\tstate.fg = \"default\";\n\t\t} else if (n === 49) {\n\t\t\tstate.bg = \"default\";\n\t\t} else if ((n >= 30 && n <= 37) || (n >= 90 && n <= 97)) {\n\t\t\tstate.fg = \"set\";\n\t\t} else if ((n >= 40 && n <= 47) || (n >= 100 && n <= 107)) {\n\t\t\tstate.bg = \"set\";\n\t\t} else if (n === 38) {\n\t\t\tstate.fg = \"set\";\n\t\t\t// Skip colour-model parameters: 38;5;N or 38;2;R;G;B\n\t\t\tif (parts[k + 1] === \"5\") {\n\t\t\t\tk += 2;\n\t\t\t} else if (parts[k + 1] === \"2\") {\n\t\t\t\tk += 4;\n\t\t\t}\n\t\t} else if (n === 48) {\n\t\t\tstate.bg = \"set\";\n\t\t\tif (parts[k + 1] === \"5\") {\n\t\t\t\tk += 2;\n\t\t\t} else if (parts[k + 1] === \"2\") {\n\t\t\t\tk += 4;\n\t\t\t}\n\t\t}\n\t\tk++;\n\t}\n}\n\nfunction stripAnsi(line: string): string {\n\t// Remove CSI sequences. Good enough for these tests.\n\treturn line.replace(/\\x1b\\[[0-?]*[ -/]*[@-~]/g, \"\");\n}\n\ndescribe(\"compositeOverlays — backdrop\", () => {\n\tit(\"dims base lines outside the overlay when backdrop is true\", () => {\n\t\tconst base = [\"hello world\", \"second line\"];\n\t\tconst overlay = makeEntry([\"OVERLAY\"], {\n\t\t\twidth: 7,\n\t\t\tanchor: \"top-left\",\n\t\t\tbackdrop: true,\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 20, 20, 2);\n\n\t\t// \"second line\" is below the overlay (which is anchored top-left with\n\t\t// a single visible row), so every glyph of that text should be\n\t\t// rendered with the dim attribute active.\n\t\tconst line = result.find((l) => stripAnsi(l).includes(\"second line\"));\n\t\tassert.ok(line, \"should have a line containing 'second line'\");\n\n\t\tconst state = sgrStateAtGlyph(line, \"second line\");\n\t\tassert.equal(state.dim, true, \"base line should be dimmed (SGR 2)\");\n\t});\n\n\tit(\"backdrop applies a non-default foreground colour and leaves background untouched\", () => {\n\t\tconst base = [\"hello world\", \"second line\"];\n\t\tconst overlay = makeEntry([\"OV\"], {\n\t\t\twidth: 2,\n\t\t\tanchor: \"top-left\",\n\t\t\tbackdrop: true,\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 20, 20, 2);\n\n\t\tconst line = result.find((l) => stripAnsi(l).includes(\"second line\"));\n\t\tassert.ok(line, \"should have a line containing 'second line'\");\n\n\t\tconst state = sgrStateAtGlyph(line, \"second line\");\n\t\tassert.equal(state.fg, \"set\", \"backdrop must set a foreground colour\");\n\t\tassert.equal(\n\t\t\tstate.bg,\n\t\t\t\"default\",\n\t\t\t\"backdrop must not paint a background (preserves user's terminal theme)\",\n\t\t);\n\t});\n\n\tit(\"does not dim when backdrop is false/absent\", () => {\n\t\tconst base = [\"hello world\", \"second line\"];\n\t\tconst overlay = makeEntry([\"OVERLAY\"], {\n\t\t\twidth: 7,\n\t\t\tanchor: \"top-left\",\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 20, 20, 2);\n\n\t\tconst line = result.find((l) => stripAnsi(l).includes(\"second line\"));\n\t\tassert.ok(line, \"should have a line containing 'second line'\");\n\n\t\tconst state = sgrStateAtGlyph(line, \"second line\");\n\t\tassert.equal(state.dim, false, \"base line should not be dimmed when no backdrop\");\n\t});\n\n\tit(\"overlay content renders on top of dimmed background\", () => {\n\t\tconst base = [\"aaaaaaaaaa\"];\n\t\tconst overlay = makeEntry([\"XX\"], {\n\t\t\twidth: 2,\n\t\t\tanchor: \"top-left\",\n\t\t\tbackdrop: true,\n\t\t});\n\n\t\tconst result = compositeOverlays(base, [overlay], 10, 10, 1);\n\n\t\t// Find the row that (after stripping styling) contains the overlay\n\t\t// text. We don't use positional `result[0]` so the test survives if\n\t\t// the row ordering changes.\n\t\tconst overlayRow = result.find((l) => stripAnsi(l).includes(\"XX\"));\n\t\tassert.ok(overlayRow, \"overlay text should be composited into some rendered row\");\n\t});\n});\n"]}
|
|
@@ -1,51 +1,77 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
4
3
|
import { StdinBuffer } from "../stdin-buffer.js";
|
|
4
|
+
// These tests use node:test's mock.timers to advance virtual time.
|
|
5
|
+
// They previously relied on wall-clock delays (delay(20), delay(150))
|
|
6
|
+
// racing the OS scheduler. On Windows the default setTimeout resolution is
|
|
7
|
+
// ~15.6ms, so a real-time delay of 20ms sometimes fired only one of two
|
|
8
|
+
// pending timers — hence the flake referenced in issue #4795.
|
|
9
|
+
//
|
|
10
|
+
// By mocking setTimeout/clearTimeout we control exactly when the buffer's
|
|
11
|
+
// sequence timeout and stale timeout fire, eliminating scheduler-induced
|
|
12
|
+
// flake and making the tests deterministic on every platform.
|
|
5
13
|
describe("StdinBuffer", () => {
|
|
6
|
-
it("flushes a lone Escape keypress",
|
|
14
|
+
it("flushes a lone Escape keypress after the sequence timeout", (t) => {
|
|
15
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
7
16
|
const buffer = new StdinBuffer({ timeout: 5 });
|
|
8
17
|
const received = [];
|
|
9
18
|
buffer.on("data", (sequence) => received.push(sequence));
|
|
10
19
|
buffer.process("\x1b");
|
|
11
|
-
|
|
20
|
+
// Before the timeout fires the lone ESC is still buffered.
|
|
21
|
+
assert.deepEqual(received, []);
|
|
22
|
+
assert.equal(buffer.getBuffer(), "\x1b");
|
|
23
|
+
// Advance past the sequence timeout.
|
|
24
|
+
t.mock.timers.tick(5);
|
|
12
25
|
assert.deepEqual(received, ["\x1b"]);
|
|
13
26
|
assert.equal(buffer.getBuffer(), "");
|
|
14
27
|
});
|
|
15
|
-
it("keeps split CSI focus and mouse sequences buffered until completion",
|
|
28
|
+
it("keeps split CSI focus and mouse sequences buffered until completion", (t) => {
|
|
29
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
16
30
|
const buffer = new StdinBuffer({ timeout: 5 });
|
|
17
31
|
const received = [];
|
|
18
32
|
buffer.on("data", (sequence) => received.push(sequence));
|
|
19
33
|
buffer.process("\x1b[");
|
|
20
|
-
|
|
34
|
+
// Even after the normal timeout elapses, an incomplete CSI prefix must
|
|
35
|
+
// remain buffered (not emitted as literal text) so split escape
|
|
36
|
+
// sequences stay intact.
|
|
37
|
+
t.mock.timers.tick(5);
|
|
21
38
|
assert.deepEqual(received, []);
|
|
22
39
|
assert.equal(buffer.getBuffer(), "\x1b[");
|
|
23
40
|
buffer.process("I");
|
|
24
41
|
assert.deepEqual(received, ["\x1b[I"]);
|
|
25
42
|
assert.equal(buffer.getBuffer(), "");
|
|
26
43
|
buffer.process("\x1b[<35;20;");
|
|
27
|
-
|
|
44
|
+
t.mock.timers.tick(5);
|
|
28
45
|
assert.deepEqual(received, ["\x1b[I"]);
|
|
29
46
|
assert.equal(buffer.getBuffer(), "\x1b[<35;20;");
|
|
30
47
|
buffer.process("5m");
|
|
31
48
|
assert.deepEqual(received, ["\x1b[I", "\x1b[<35;20;5m"]);
|
|
32
49
|
assert.equal(buffer.getBuffer(), "");
|
|
33
50
|
});
|
|
34
|
-
it("flushes a stale incomplete escape prefix after the stale timeout",
|
|
35
|
-
|
|
51
|
+
it("flushes a stale incomplete escape prefix after the stale timeout", (t) => {
|
|
52
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
53
|
+
const buffer = new StdinBuffer({ timeout: 20, staleTimeout: 40 });
|
|
36
54
|
const received = [];
|
|
37
55
|
buffer.on("data", (sequence) => received.push(sequence));
|
|
38
56
|
buffer.process("\x1b[");
|
|
39
|
-
|
|
57
|
+
// Sequence timeout: keeps the incomplete prefix buffered and starts
|
|
58
|
+
// the stale timer.
|
|
59
|
+
t.mock.timers.tick(20);
|
|
60
|
+
assert.deepEqual(received, []);
|
|
61
|
+
assert.equal(buffer.getBuffer(), "\x1b[");
|
|
62
|
+
// Stale timer fires — prefix is emitted as-is.
|
|
63
|
+
t.mock.timers.tick(40);
|
|
40
64
|
assert.deepEqual(received, ["\x1b["]);
|
|
41
65
|
assert.equal(buffer.getBuffer(), "");
|
|
42
66
|
});
|
|
43
|
-
it("still allows an incomplete escape prefix to complete before the stale timeout",
|
|
67
|
+
it("still allows an incomplete escape prefix to complete before the stale timeout", (t) => {
|
|
68
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
44
69
|
const buffer = new StdinBuffer({ timeout: 5, staleTimeout: 30 });
|
|
45
70
|
const received = [];
|
|
46
71
|
buffer.on("data", (sequence) => received.push(sequence));
|
|
47
72
|
buffer.process("\x1b[");
|
|
48
|
-
|
|
73
|
+
// Advance past the sequence timeout (but not the stale timeout).
|
|
74
|
+
t.mock.timers.tick(10);
|
|
49
75
|
buffer.process("I");
|
|
50
76
|
assert.deepEqual(received, ["\x1b[I"]);
|
|
51
77
|
assert.equal(buffer.getBuffer(), "");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdin-buffer.test.js","sourceRoot":"","sources":["../../src/__tests__/stdin-buffer.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"stdin-buffer.test.js","sourceRoot":"","sources":["../../src/__tests__/stdin-buffer.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\n\nimport { StdinBuffer } from \"../stdin-buffer.js\";\n\n// These tests use node:test's mock.timers to advance virtual time.\n// They previously relied on wall-clock delays (delay(20), delay(150))\n// racing the OS scheduler. On Windows the default setTimeout resolution is\n// ~15.6ms, so a real-time delay of 20ms sometimes fired only one of two\n// pending timers — hence the flake referenced in issue #4795.\n//\n// By mocking setTimeout/clearTimeout we control exactly when the buffer's\n// sequence timeout and stale timeout fire, eliminating scheduler-induced\n// flake and making the tests deterministic on every platform.\n\ndescribe(\"StdinBuffer\", () => {\n\tit(\"flushes a lone Escape keypress after the sequence timeout\", (t) => {\n\t\tt.mock.timers.enable({ apis: [\"setTimeout\"] });\n\t\tconst buffer = new StdinBuffer({ timeout: 5 });\n\t\tconst received: string[] = [];\n\t\tbuffer.on(\"data\", (sequence) => received.push(sequence));\n\n\t\tbuffer.process(\"\\x1b\");\n\n\t\t// Before the timeout fires the lone ESC is still buffered.\n\t\tassert.deepEqual(received, []);\n\t\tassert.equal(buffer.getBuffer(), \"\\x1b\");\n\n\t\t// Advance past the sequence timeout.\n\t\tt.mock.timers.tick(5);\n\n\t\tassert.deepEqual(received, [\"\\x1b\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\t});\n\n\tit(\"keeps split CSI focus and mouse sequences buffered until completion\", (t) => {\n\t\tt.mock.timers.enable({ apis: [\"setTimeout\"] });\n\t\tconst buffer = new StdinBuffer({ timeout: 5 });\n\t\tconst received: string[] = [];\n\t\tbuffer.on(\"data\", (sequence) => received.push(sequence));\n\n\t\tbuffer.process(\"\\x1b[\");\n\t\t// Even after the normal timeout elapses, an incomplete CSI prefix must\n\t\t// remain buffered (not emitted as literal text) so split escape\n\t\t// sequences stay intact.\n\t\tt.mock.timers.tick(5);\n\t\tassert.deepEqual(received, []);\n\t\tassert.equal(buffer.getBuffer(), \"\\x1b[\");\n\n\t\tbuffer.process(\"I\");\n\t\tassert.deepEqual(received, [\"\\x1b[I\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\n\t\tbuffer.process(\"\\x1b[<35;20;\");\n\t\tt.mock.timers.tick(5);\n\t\tassert.deepEqual(received, [\"\\x1b[I\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\\x1b[<35;20;\");\n\n\t\tbuffer.process(\"5m\");\n\t\tassert.deepEqual(received, [\"\\x1b[I\", \"\\x1b[<35;20;5m\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\t});\n\n\tit(\"flushes a stale incomplete escape prefix after the stale timeout\", (t) => {\n\t\tt.mock.timers.enable({ apis: [\"setTimeout\"] });\n\t\tconst buffer = new StdinBuffer({ timeout: 20, staleTimeout: 40 });\n\t\tconst received: string[] = [];\n\t\tbuffer.on(\"data\", (sequence) => received.push(sequence));\n\n\t\tbuffer.process(\"\\x1b[\");\n\n\t\t// Sequence timeout: keeps the incomplete prefix buffered and starts\n\t\t// the stale timer.\n\t\tt.mock.timers.tick(20);\n\t\tassert.deepEqual(received, []);\n\t\tassert.equal(buffer.getBuffer(), \"\\x1b[\");\n\n\t\t// Stale timer fires — prefix is emitted as-is.\n\t\tt.mock.timers.tick(40);\n\n\t\tassert.deepEqual(received, [\"\\x1b[\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\t});\n\n\tit(\"still allows an incomplete escape prefix to complete before the stale timeout\", (t) => {\n\t\tt.mock.timers.enable({ apis: [\"setTimeout\"] });\n\t\tconst buffer = new StdinBuffer({ timeout: 5, staleTimeout: 30 });\n\t\tconst received: string[] = [];\n\t\tbuffer.on(\"data\", (sequence) => received.push(sequence));\n\n\t\tbuffer.process(\"\\x1b[\");\n\t\t// Advance past the sequence timeout (but not the stale timeout).\n\t\tt.mock.timers.tick(10);\n\t\tbuffer.process(\"I\");\n\n\t\tassert.deepEqual(received, [\"\\x1b[I\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\t});\n});\n"]}
|
|
@@ -20,36 +20,24 @@ function makeTerminal() {
|
|
|
20
20
|
setTitle() { },
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
it("resets debounce flag when content grows back", () => {
|
|
43
|
-
const tui = new TUI(makeTerminal());
|
|
44
|
-
const anyTui = tui;
|
|
45
|
-
anyTui.clearOnShrink = true;
|
|
46
|
-
anyTui._shrinkDebounceActive = true;
|
|
47
|
-
// Simulating the else branch: content grew back or no shrink
|
|
48
|
-
// The code sets _shrinkDebounceActive = false in the else branch
|
|
49
|
-
anyTui._shrinkDebounceActive = false;
|
|
50
|
-
assert.equal(anyTui._shrinkDebounceActive, false);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
23
|
+
// TUI clearOnShrink debounce — tests removed in #4794 (ref #4784).
|
|
24
|
+
//
|
|
25
|
+
// The previous tests mutated private fields (`_shrinkDebounceActive`,
|
|
26
|
+
// `maxLinesRendered`) and then asserted the values they just wrote —
|
|
27
|
+
// pure tautologies that never exercised the real debounce path in
|
|
28
|
+
// `renderNow()` (tui.ts:734-754). A regression that narrowed the
|
|
29
|
+
// condition, reversed the flag flip, or dropped the "keep
|
|
30
|
+
// maxLinesRendered" rule would have passed all of them.
|
|
31
|
+
//
|
|
32
|
+
// A proper test would (a) render a component that produces N lines to
|
|
33
|
+
// establish `maxLinesRendered`, (b) swap in a component that produces
|
|
34
|
+
// N-k lines to trigger the shrink branch, and (c) observe terminal
|
|
35
|
+
// writes to confirm the debounce defers/commits the full redraw on the
|
|
36
|
+
// expected render call.
|
|
37
|
+
//
|
|
38
|
+
// That test setup requires exposing enough of the render path (or
|
|
39
|
+
// extracting the debounce decision into a pure helper) — deferred to a
|
|
40
|
+
// separate refactor PR rather than shipping a tautology. See #4794.
|
|
53
41
|
describe("TUI", () => {
|
|
54
42
|
it("does not swallow a bare Escape keypress while waiting for the cell-size response", () => {
|
|
55
43
|
const tui = new TUI(makeTerminal());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.test.js","sourceRoot":"","sources":["../../src/__tests__/tui.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAI3C,SAAS,YAAY;IACpB,OAAO;QACN,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,EAAE;QACX,IAAI,EAAE,EAAE;QACR,mBAAmB,EAAE,KAAK;QAC1B,KAAK,KAAI,CAAC;QACV,IAAI,KAAI,CAAC;QACT,UAAU,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC1B,KAAK,KAAI,CAAC;QACV,MAAM,KAAI,CAAC;QACX,UAAU,KAAI,CAAC;QACf,UAAU,KAAI,CAAC;QACf,SAAS,KAAI,CAAC;QACd,eAAe,KAAI,CAAC;QACpB,WAAW,KAAI,CAAC;QAChB,QAAQ,KAAI,CAAC;KACb,CAAC;AACH,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"tui.test.js","sourceRoot":"","sources":["../../src/__tests__/tui.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\n\nimport { Container, TUI } from \"../tui.js\";\nimport type { Component } from \"../tui.js\";\nimport type { Terminal } from \"../terminal.js\";\n\nfunction makeTerminal(): Terminal {\n\treturn {\n\t\tisTTY: true,\n\t\tcolumns: 80,\n\t\trows: 24,\n\t\tkittyProtocolActive: false,\n\t\tstart() {},\n\t\tstop() {},\n\t\tdrainInput: async () => {},\n\t\twrite() {},\n\t\tmoveBy() {},\n\t\thideCursor() {},\n\t\tshowCursor() {},\n\t\tclearLine() {},\n\t\tclearFromCursor() {},\n\t\tclearScreen() {},\n\t\tsetTitle() {},\n\t};\n}\n\n// TUI clearOnShrink debounce — tests removed in #4794 (ref #4784).\n//\n// The previous tests mutated private fields (`_shrinkDebounceActive`,\n// `maxLinesRendered`) and then asserted the values they just wrote —\n// pure tautologies that never exercised the real debounce path in\n// `renderNow()` (tui.ts:734-754). A regression that narrowed the\n// condition, reversed the flag flip, or dropped the \"keep\n// maxLinesRendered\" rule would have passed all of them.\n//\n// A proper test would (a) render a component that produces N lines to\n// establish `maxLinesRendered`, (b) swap in a component that produces\n// N-k lines to trigger the shrink branch, and (c) observe terminal\n// writes to confirm the debounce defers/commits the full redraw on the\n// expected render call.\n//\n// That test setup requires exposing enough of the render path (or\n// extracting the debounce decision into a pure helper) — deferred to a\n// separate refactor PR rather than shipping a tautology. See #4794.\n\ndescribe(\"TUI\", () => {\n\tit(\"does not swallow a bare Escape keypress while waiting for the cell-size response\", () => {\n\t\tconst tui = new TUI(makeTerminal());\n\t\tconst received: string[] = [];\n\n\t\ttui.setFocus({\n\t\t\trender: () => [],\n\t\t\thandleInput: (data: string) => {\n\t\t\t\treceived.push(data);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t});\n\n\t\tconst anyTui = tui as any;\n\t\tanyTui.cellSizeQueryPending = true;\n\t\tanyTui.inputBuffer = \"\";\n\n\t\tanyTui.handleInput(\"\\x1b\");\n\n\t\tassert.deepEqual(received, [\"\\x1b\"]);\n\t\tassert.equal(anyTui.cellSizeQueryPending, false);\n\t\tassert.equal(anyTui.inputBuffer, \"\");\n\t});\n});\n\ndescribe(\"Container\", () => {\n\tfunction makeDisposableChild(counter: { disposed: number }): Component & { dispose(): void } {\n\t\treturn {\n\t\t\trender: () => [],\n\t\t\tinvalidate() {},\n\t\t\tdispose() {\n\t\t\t\tcounter.disposed++;\n\t\t\t},\n\t\t};\n\t}\n\n\tit(\"detachChildren() removes children without disposing them\", () => {\n\t\tconst c = new Container();\n\t\tconst counter = { disposed: 0 };\n\t\tc.addChild(makeDisposableChild(counter));\n\t\tc.addChild(makeDisposableChild(counter));\n\n\t\tc.detachChildren();\n\n\t\tassert.equal(c.children.length, 0);\n\t\tassert.equal(counter.disposed, 0);\n\t});\n\n\tit(\"clear() still disposes children (regression guard for detach/dispose split)\", () => {\n\t\tconst c = new Container();\n\t\tconst counter = { disposed: 0 };\n\t\tc.addChild(makeDisposableChild(counter));\n\t\tc.addChild(makeDisposableChild(counter));\n\n\t\tc.clear();\n\n\t\tassert.equal(c.children.length, 0);\n\t\tassert.equal(counter.disposed, 2);\n\t});\n});\n"]}
|