lazycodex-ai 5.1.47 → 5.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1194 -752
- package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
- package/dist/cli/service-auth/commands.d.ts +2 -4
- package/dist/cli/service-auth/index.d.ts +1 -1
- package/dist/cli/service-auth/keystore.d.ts +26 -2
- package/dist/cli/service-auth/protocol.d.ts +7 -1
- package/dist/cli/service-auth/session.d.ts +6 -0
- package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
- package/dist/cli-node/index.js +1194 -752
- package/package.json +3 -1
- package/packages/lsp-core/package.json +45 -0
- package/packages/lsp-core/src/index.ts +30 -0
- package/packages/lsp-core/src/lsp/bun-command-output.ts +82 -0
- package/packages/lsp-core/src/lsp/bun-project-checks.test.ts +170 -0
- package/packages/lsp-core/src/lsp/bun-project-checks.ts +223 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.ts +14 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +52 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +471 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
- package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +213 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +290 -0
- package/packages/lsp-core/src/lsp/client.ts +398 -0
- package/packages/lsp-core/src/lsp/config-loader.ts +254 -0
- package/packages/lsp-core/src/lsp/connection.ts +95 -0
- package/packages/lsp-core/src/lsp/constants.ts +17 -0
- package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +90 -0
- package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +221 -0
- package/packages/lsp-core/src/lsp/directory-diagnostics.ts +187 -0
- package/packages/lsp-core/src/lsp/effective-extension.ts +10 -0
- package/packages/lsp-core/src/lsp/errors.ts +89 -0
- package/packages/lsp-core/src/lsp/fake-bun-test-support.ts +102 -0
- package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
- package/packages/lsp-core/src/lsp/fixtures/disposed-stdin-epipe-probe.ts +28 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +234 -0
- package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
- package/packages/lsp-core/src/lsp/format-document.ts +90 -0
- package/packages/lsp-core/src/lsp/formatters.ts +144 -0
- package/packages/lsp-core/src/lsp/infer-extension.ts +66 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection.ts +369 -0
- package/packages/lsp-core/src/lsp/language-mappings.ts +173 -0
- package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
- package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
- package/packages/lsp-core/src/lsp/manager.ts +495 -0
- package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
- package/packages/lsp-core/src/lsp/process-pipe-errors.test.ts +21 -0
- package/packages/lsp-core/src/lsp/process-signal-cleanup.ts +21 -0
- package/packages/lsp-core/src/lsp/process.ts +221 -0
- package/packages/lsp-core/src/lsp/server-definitions.ts +205 -0
- package/packages/lsp-core/src/lsp/server-install-state.ts +67 -0
- package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
- package/packages/lsp-core/src/lsp/server-installation.ts +218 -0
- package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
- package/packages/lsp-core/src/lsp/server-resolution.ts +117 -0
- package/packages/lsp-core/src/lsp/startup-failure.ts +1 -0
- package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
- package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
- package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
- package/packages/lsp-core/src/lsp/transport.ts +339 -0
- package/packages/lsp-core/src/lsp/types.ts +134 -0
- package/packages/lsp-core/src/lsp/utils.test.ts +72 -0
- package/packages/lsp-core/src/lsp/utils.ts +40 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +218 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +194 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +150 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.ts +392 -0
- package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +132 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
- package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
- package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
- package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
- package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
- package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
- package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
- package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
- package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
- package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
- package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
- package/packages/lsp-core/src/lsp/workspace-edit.ts +55 -0
- package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -0
- package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
- package/packages/lsp-core/src/mcp-protocol-pin.test.ts +97 -0
- package/packages/lsp-core/src/mcp.ts +122 -0
- package/packages/lsp-core/src/missing-dependency-result.test.ts +135 -0
- package/packages/lsp-core/src/missing-dependency-result.ts +79 -0
- package/packages/lsp-core/src/post-edit/file-location.ts +55 -0
- package/packages/lsp-core/src/post-edit/index.ts +3 -0
- package/packages/lsp-core/src/post-edit/not-installed-guidance.ts +36 -0
- package/packages/lsp-core/src/post-edit/not-installed.test.ts +224 -0
- package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
- package/packages/lsp-core/src/post-edit/orchestration.ts +205 -0
- package/packages/lsp-core/src/request-context.test.ts +200 -0
- package/packages/lsp-core/src/request-context.ts +235 -0
- package/packages/lsp-core/src/tool-surface.test.ts +173 -0
- package/packages/lsp-core/src/tools/bun-directory-diagnostics.test.ts +113 -0
- package/packages/lsp-core/src/tools/bun-directory-diagnostics.ts +75 -0
- package/packages/lsp-core/src/tools/definitions.ts +156 -0
- package/packages/lsp-core/src/tools/diagnostics.ts +133 -0
- package/packages/lsp-core/src/tools/format.test.ts +56 -0
- package/packages/lsp-core/src/tools/format.ts +56 -0
- package/packages/lsp-core/src/tools/index.ts +11 -0
- package/packages/lsp-core/src/tools/install-decision.ts +39 -0
- package/packages/lsp-core/src/tools/navigation.ts +88 -0
- package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
- package/packages/lsp-core/src/tools/parameters.ts +49 -0
- package/packages/lsp-core/src/tools/rename.ts +69 -0
- package/packages/lsp-core/src/tools/result.ts +5 -0
- package/packages/lsp-core/src/tools/runtime.ts +21 -0
- package/packages/lsp-core/src/tools/schema.ts +9 -0
- package/packages/lsp-core/src/tools/status.ts +28 -0
- package/packages/lsp-core/src/tools/symbols.ts +99 -0
- package/packages/lsp-core/src/tools/types.ts +116 -0
- package/packages/lsp-core/src/tools.ts +1 -0
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/README.md +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
- package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
- package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
- package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +16 -16
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/shared/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
- package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
- package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
- package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -259
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +68 -24
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +14 -6
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +16 -15
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +40 -62
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +9 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +87 -29
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
- package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
- package/packages/shared-skills/skill-source-filter.mjs +6 -2
- package/packages/shared-skills/skills/ast-grep/README.md +1 -1
- package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
- package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
- package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
- package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
- package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
- package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -259
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +68 -24
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +14 -6
- package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
- package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +16 -15
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -109
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
- package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
- package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +40 -62
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +9 -6
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
- package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
- package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +6 -560
- package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +87 -29
- package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
- package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createWorkspaceEditTestHarness,
|
|
7
|
+
readEvents,
|
|
8
|
+
renameTextEdit,
|
|
9
|
+
waitForEventCount,
|
|
10
|
+
waitForEventCountBySubscription,
|
|
11
|
+
} from "./workspace-apply-edit-test-support.js";
|
|
12
|
+
|
|
13
|
+
const harness = createWorkspaceEditTestHarness();
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await harness.cleanup();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("LspClient workspace mutation lease", () => {
|
|
20
|
+
it("#given no mutating request #when the server calls applyEdit #then the request is rejected as unscoped", async () => {
|
|
21
|
+
const context = await harness.makeClient({
|
|
22
|
+
unscopedApplyEditOnDidOpen: renameTextEdit("before", "after", { version: 1 }),
|
|
23
|
+
});
|
|
24
|
+
const response = waitForEventCountBySubscription(
|
|
25
|
+
context.events,
|
|
26
|
+
(event) => event.type === "clientResponse" && event.method === "workspace/applyEdit",
|
|
27
|
+
1,
|
|
28
|
+
);
|
|
29
|
+
await context.client.openFile(context.source);
|
|
30
|
+
const responses = await response;
|
|
31
|
+
|
|
32
|
+
expect(responses[0]?.result).toEqual({
|
|
33
|
+
applied: false,
|
|
34
|
+
failureReason: expect.stringContaining("active workspace mutation"),
|
|
35
|
+
});
|
|
36
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const before = 1;\n");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("#given one valid server apply #when the server applies twice #then the repeated request is rejected", async () => {
|
|
40
|
+
const context = await harness.makeClient({
|
|
41
|
+
renameSteps: [
|
|
42
|
+
{ applyEdit: renameTextEdit("before", "after", { version: 1 }), applyTwice: true, renameResult: "same" },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
await context.client.openFile(context.source);
|
|
46
|
+
|
|
47
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
48
|
+
|
|
49
|
+
expect(result.apply.success).toBe(true);
|
|
50
|
+
const responses = readEvents(context.events).filter(
|
|
51
|
+
(event) => event.type === "clientResponse" && event.method === "workspace/applyEdit",
|
|
52
|
+
);
|
|
53
|
+
expect(responses).toHaveLength(2);
|
|
54
|
+
expect(responses[0]?.result).toEqual({ applied: true });
|
|
55
|
+
expect(responses[1]?.result).toEqual({
|
|
56
|
+
applied: false,
|
|
57
|
+
failureReason: expect.stringContaining("already handled"),
|
|
58
|
+
});
|
|
59
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const after = 1;\n");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("#given two concurrent server applies #when one enters commit #then the other is rejected without a second write", async () => {
|
|
63
|
+
const context = await harness.makeClient({
|
|
64
|
+
renameSteps: [
|
|
65
|
+
{
|
|
66
|
+
applyEdit: renameTextEdit("before", "after", { version: 1 }),
|
|
67
|
+
applyConcurrently: true,
|
|
68
|
+
renameResult: "same",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
let writes = 0;
|
|
73
|
+
context.client.setWorkspaceEditIo({
|
|
74
|
+
writeFile(path, content) {
|
|
75
|
+
writes += 1;
|
|
76
|
+
writeFileSync(path, content, "utf-8");
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
await context.client.openFile(context.source);
|
|
80
|
+
|
|
81
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
82
|
+
|
|
83
|
+
expect(result.apply.success).toBe(true);
|
|
84
|
+
expect(writes).toBe(1);
|
|
85
|
+
const responses = readEvents(context.events).filter(
|
|
86
|
+
(event) => event.type === "clientResponse" && event.method === "workspace/applyEdit",
|
|
87
|
+
);
|
|
88
|
+
expect(responses.map((response) => response.result)).toContainEqual({ applied: true });
|
|
89
|
+
const rejection = responses.find(
|
|
90
|
+
(response) => typeof response.result === "object" && response.result !== null && "failureReason" in response.result,
|
|
91
|
+
);
|
|
92
|
+
expect(rejection?.result).toMatchObject({ applied: false });
|
|
93
|
+
expect(JSON.stringify(rejection?.result)).toMatch(/already (?:in progress|handled)/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("#given a rename lease is held #when another rename starts #then it is rejected without a second request", async () => {
|
|
97
|
+
const context = await harness.makeClient({
|
|
98
|
+
renameSteps: [
|
|
99
|
+
{
|
|
100
|
+
renameResult: "edit",
|
|
101
|
+
renameEdit: renameTextEdit("before", "after", { version: 1 }),
|
|
102
|
+
responseDelayMs: 150,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
await context.client.openFile(context.source);
|
|
107
|
+
const firstPromise = context.client.rename(context.source, 1, 6, "after");
|
|
108
|
+
await waitForEventCount(
|
|
109
|
+
context.events,
|
|
110
|
+
(event) => event.type === "clientRequest" && event.method === "textDocument/rename",
|
|
111
|
+
1,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const concurrent = await context.client.rename(context.source, 1, 6, "other_");
|
|
115
|
+
const first = await firstPromise;
|
|
116
|
+
|
|
117
|
+
expect(concurrent.apply.success).toBe(false);
|
|
118
|
+
expect(concurrent.apply.errors.join("\n")).toContain("workspace mutation is already in progress");
|
|
119
|
+
expect(first.apply.success).toBe(true);
|
|
120
|
+
const requests = readEvents(context.events).filter(
|
|
121
|
+
(event) => event.type === "clientRequest" && event.method === "textDocument/rename",
|
|
122
|
+
);
|
|
123
|
+
expect(requests).toHaveLength(1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("#given no server apply #when rename directly returns an edit #then it is applied exactly once", async () => {
|
|
127
|
+
const context = await harness.makeClient({
|
|
128
|
+
renameSteps: [{ renameResult: "edit", renameEdit: renameTextEdit("before", "after", { version: 1 }) }],
|
|
129
|
+
});
|
|
130
|
+
await context.client.openFile(context.source);
|
|
131
|
+
|
|
132
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
133
|
+
|
|
134
|
+
expect(result.apply.success).toBe(true);
|
|
135
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const after = 1;\n");
|
|
136
|
+
expect(readEvents(context.events).filter((event) => event.method === "workspace/applyEdit")).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("#given cancellation before a delayed server apply #when the rename request is aborted #then no file is mutated", async () => {
|
|
140
|
+
const controller = new AbortController();
|
|
141
|
+
const context = await harness.makeClient({
|
|
142
|
+
renameSteps: [
|
|
143
|
+
{
|
|
144
|
+
applyEdit: renameTextEdit("before", "after", { version: 1 }),
|
|
145
|
+
applyDelayMs: 100,
|
|
146
|
+
renameResult: "same",
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
await context.client.openFile(context.source);
|
|
151
|
+
const rename = context.client.rename(context.source, 1, 6, "after", controller.signal);
|
|
152
|
+
await waitForEventCount(
|
|
153
|
+
context.events,
|
|
154
|
+
(event) => event.type === "clientRequest" && event.method === "textDocument/rename",
|
|
155
|
+
1,
|
|
156
|
+
);
|
|
157
|
+
controller.abort();
|
|
158
|
+
|
|
159
|
+
await expect(rename).rejects.toThrow(/cancel/i);
|
|
160
|
+
|
|
161
|
+
const [cancel] = await waitForEventCount(
|
|
162
|
+
context.events,
|
|
163
|
+
(event) => event.type === "clientNotification" && event.method === "$/cancelRequest",
|
|
164
|
+
1,
|
|
165
|
+
);
|
|
166
|
+
expect(cancel?.params).toEqual({ id: 2 });
|
|
167
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const before = 1;\n");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("#given cancellation after the commit gate #when applyEdit finishes #then rename reports one completed late-abort mutation", async () => {
|
|
171
|
+
const controller = new AbortController();
|
|
172
|
+
const context = await harness.makeClient({
|
|
173
|
+
renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 1 }), renameResult: "same" }],
|
|
174
|
+
});
|
|
175
|
+
let writes = 0;
|
|
176
|
+
context.client.setWorkspaceEditIo({
|
|
177
|
+
writeFile(path, content) {
|
|
178
|
+
writes += 1;
|
|
179
|
+
writeFileSync(path, content, "utf-8");
|
|
180
|
+
controller.abort();
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
await context.client.openFile(context.source);
|
|
184
|
+
|
|
185
|
+
const result = await context.client.rename(context.source, 1, 6, "after", controller.signal);
|
|
186
|
+
|
|
187
|
+
expect(result.apply).toMatchObject({ success: true, lateAbort: true, totalEdits: 1 });
|
|
188
|
+
expect(writes).toBe(1);
|
|
189
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const after = 1;\n");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("#given lease-backed and bare clients #when initialized #then applyEdit and annotations are advertised honestly", async () => {
|
|
193
|
+
const active = await harness.makeClient({});
|
|
194
|
+
const inactive = await harness.makeBareConnection({});
|
|
195
|
+
|
|
196
|
+
const activeCapabilities = initializeCapabilities(active.events);
|
|
197
|
+
const inactiveCapabilities = initializeCapabilities(inactive.events);
|
|
198
|
+
expect(activeCapabilities.workspace?.applyEdit).toBe(true);
|
|
199
|
+
expect(inactiveCapabilities.workspace?.applyEdit).toBeUndefined();
|
|
200
|
+
expect(activeCapabilities.textDocument?.rename?.honorsChangeAnnotations).toBeUndefined();
|
|
201
|
+
expect(activeCapabilities.workspace?.workspaceEdit?.changeAnnotationSupport).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
interface InitializeCapabilities {
|
|
206
|
+
readonly workspace?: {
|
|
207
|
+
readonly applyEdit?: boolean;
|
|
208
|
+
readonly workspaceEdit?: { readonly changeAnnotationSupport?: unknown };
|
|
209
|
+
};
|
|
210
|
+
readonly textDocument?: { readonly rename?: { readonly honorsChangeAnnotations?: boolean } };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function initializeCapabilities(path: string): InitializeCapabilities {
|
|
214
|
+
const initialize = readEvents(path).find(
|
|
215
|
+
(event) => event.type === "clientRequest" && event.method === "initialize",
|
|
216
|
+
);
|
|
217
|
+
return (initialize?.params as { capabilities?: InitializeCapabilities } | undefined)?.capabilities ?? {};
|
|
218
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import type { WorkspaceEdit } from "./types.js";
|
|
7
|
+
import {
|
|
8
|
+
createWorkspaceEditTestHarness,
|
|
9
|
+
readEvents,
|
|
10
|
+
renameTextEdit,
|
|
11
|
+
} from "./workspace-apply-edit-test-support.js";
|
|
12
|
+
|
|
13
|
+
const harness = createWorkspaceEditTestHarness();
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await harness.cleanup();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("LspClient workspace edit synchronization", () => {
|
|
20
|
+
it("#given an open edited document #when applyEdit commits #then didChange precedes didSave with the next version", async () => {
|
|
21
|
+
const context = await harness.makeClient({
|
|
22
|
+
renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 1 }), renameResult: "same" }],
|
|
23
|
+
});
|
|
24
|
+
await context.client.openFile(context.source);
|
|
25
|
+
|
|
26
|
+
await context.client.rename(context.source, 1, 6, "after");
|
|
27
|
+
|
|
28
|
+
const notifications = readEvents(context.events).filter((event) => event.type === "clientNotification");
|
|
29
|
+
const changeIndex = notifications.findIndex((event) => event.method === "textDocument/didChange");
|
|
30
|
+
const saveIndex = notifications.findIndex((event) => event.method === "textDocument/didSave");
|
|
31
|
+
expect(changeIndex).toBeGreaterThan(-1);
|
|
32
|
+
expect(saveIndex).toBeGreaterThan(changeIndex);
|
|
33
|
+
expect(notifications[changeIndex]?.params).toMatchObject({ textDocument: { version: 2 } });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("#given an open file resource rename #when applyEdit commits #then didClose and didOpen move document state", async () => {
|
|
37
|
+
const edit: WorkspaceEdit = {
|
|
38
|
+
documentChanges: [{ kind: "rename", oldUri: "file:///placeholder", newUri: "file:///destination" }],
|
|
39
|
+
};
|
|
40
|
+
const context = await harness.makeClient({ renameSteps: [{ applyEdit: edit, renameResult: "null" }] });
|
|
41
|
+
await context.client.openFile(context.source);
|
|
42
|
+
|
|
43
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
44
|
+
|
|
45
|
+
expect(result.apply.success).toBe(true);
|
|
46
|
+
expect(existsSync(context.source)).toBe(false);
|
|
47
|
+
expect(readFileSync(context.destination, "utf-8")).toBe("const before = 1;\n");
|
|
48
|
+
expect(context.client.getOpenDocumentVersion(context.source)).toBeUndefined();
|
|
49
|
+
expect(context.client.getOpenDocumentVersion(context.destination)).toBe(1);
|
|
50
|
+
const methods = readEvents(context.events).map((event) => event.method);
|
|
51
|
+
expect(methods.indexOf("textDocument/didClose")).toBeLessThan(methods.lastIndexOf("textDocument/didOpen"));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("#given an open file deletion #when applyEdit commits #then didClose removes document state", async () => {
|
|
55
|
+
const edit: WorkspaceEdit = { documentChanges: [{ kind: "delete", uri: "file:///placeholder" }] };
|
|
56
|
+
const context = await harness.makeClient({ renameSteps: [{ applyEdit: edit, renameResult: "null" }] });
|
|
57
|
+
await context.client.openFile(context.source);
|
|
58
|
+
|
|
59
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
60
|
+
|
|
61
|
+
expect(result.apply.success).toBe(true);
|
|
62
|
+
expect(existsSync(context.source)).toBe(false);
|
|
63
|
+
expect(context.client.getOpenDocumentVersion(context.source)).toBeUndefined();
|
|
64
|
+
expect(readEvents(context.events).some((event) => event.method === "textDocument/didClose")).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("#given a closed edited file #when applyEdit commits #then one changed watched-file event is emitted", async () => {
|
|
68
|
+
const edit = renameTextEdit("closed", "after_", { version: null, uri: "file:///closed" });
|
|
69
|
+
const context = await harness.makeClient({ renameSteps: [{ applyEdit: edit, renameResult: "null" }] });
|
|
70
|
+
await context.client.openFile(context.source);
|
|
71
|
+
|
|
72
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
73
|
+
|
|
74
|
+
expect(result.apply.success).toBe(true);
|
|
75
|
+
expect(readFileSync(context.closed, "utf-8")).toBe("const after_ = 1;\n");
|
|
76
|
+
const watched = readEvents(context.events).find(
|
|
77
|
+
(event) => event.type === "clientNotification" && event.method === "workspace/didChangeWatchedFiles",
|
|
78
|
+
);
|
|
79
|
+
expect(watched?.params).toEqual({ changes: [{ uri: pathToFileURL(realpathSync(context.closed)).href, type: 2 }] });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("#given a new closed file #when applyEdit creates it #then one created watched-file event is emitted", async () => {
|
|
83
|
+
const edit: WorkspaceEdit = { documentChanges: [{ kind: "create", uri: "file:///created" }] };
|
|
84
|
+
const context = await harness.makeClient({ renameSteps: [{ applyEdit: edit, renameResult: "null" }] });
|
|
85
|
+
await context.client.openFile(context.source);
|
|
86
|
+
|
|
87
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
88
|
+
|
|
89
|
+
expect(result.apply.success).toBe(true);
|
|
90
|
+
expect(readFileSync(context.created, "utf-8")).toBe("");
|
|
91
|
+
const watched = readEvents(context.events).find(
|
|
92
|
+
(event) => event.type === "clientNotification" && event.method === "workspace/didChangeWatchedFiles",
|
|
93
|
+
);
|
|
94
|
+
expect(watched?.params).toEqual({ changes: [{ uri: pathToFileURL(realpathSync(context.created)).href, type: 1 }] });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("#given an open file overwritten by create #when applyEdit commits #then didClose and didOpen reset its state", async () => {
|
|
98
|
+
const edit: WorkspaceEdit = {
|
|
99
|
+
documentChanges: [{ kind: "create", uri: "file:///closed", options: { overwrite: true } }],
|
|
100
|
+
};
|
|
101
|
+
const context = await harness.makeClient({ renameSteps: [{ applyEdit: edit, renameResult: "null" }] });
|
|
102
|
+
await context.client.openFile(context.source);
|
|
103
|
+
await context.client.openFile(context.closed);
|
|
104
|
+
|
|
105
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
106
|
+
|
|
107
|
+
expect(result.apply.success).toBe(true);
|
|
108
|
+
expect(readFileSync(context.closed, "utf-8")).toBe("");
|
|
109
|
+
expect(context.client.getOpenDocumentVersion(context.closed)).toBe(1);
|
|
110
|
+
const methods = readEvents(context.events).map((event) => event.method);
|
|
111
|
+
expect(methods.indexOf("textDocument/didClose")).toBeLessThan(methods.lastIndexOf("textDocument/didOpen"));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, realpathSync, rmSync, watch, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { LspClient } from "./client.js";
|
|
7
|
+
import { LspClientConnection } from "./connection.js";
|
|
8
|
+
import type { LspClientOptions } from "./client.js";
|
|
9
|
+
import type { LspClientTransport } from "./transport.js";
|
|
10
|
+
import type { Diagnostic, ResolvedServer, WorkspaceEdit } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const fixturePath = join(dirname(fileURLToPath(import.meta.url)), "fixtures", "workspace-edit-server.mjs");
|
|
13
|
+
|
|
14
|
+
export interface RecordedEvent {
|
|
15
|
+
readonly type: string;
|
|
16
|
+
readonly id?: string | number;
|
|
17
|
+
readonly method?: string;
|
|
18
|
+
readonly params?: unknown;
|
|
19
|
+
readonly result?: unknown;
|
|
20
|
+
readonly error?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface WorkspaceEditTestContext {
|
|
24
|
+
readonly client: LspClient;
|
|
25
|
+
readonly workspace: string;
|
|
26
|
+
readonly source: string;
|
|
27
|
+
readonly destination: string;
|
|
28
|
+
readonly closed: string;
|
|
29
|
+
readonly created: string;
|
|
30
|
+
readonly events: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createWorkspaceEditTestHarness() {
|
|
34
|
+
const tempDirectories: string[] = [];
|
|
35
|
+
const clients: LspClientTransport[] = [];
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
async cleanup(): Promise<void> {
|
|
39
|
+
for (const client of clients.splice(0)) await client.stop();
|
|
40
|
+
for (const directory of tempDirectories.splice(0)) rmSync(directory, { recursive: true, force: true });
|
|
41
|
+
},
|
|
42
|
+
async makeClient(
|
|
43
|
+
scenario: Record<string, unknown>,
|
|
44
|
+
clientOptions: Partial<LspClientOptions> = {},
|
|
45
|
+
): Promise<WorkspaceEditTestContext> {
|
|
46
|
+
const fixture = createFixture(scenario, tempDirectories);
|
|
47
|
+
const client = new LspClient(fixture.workspace, fixture.server, {
|
|
48
|
+
requestTimeoutMs: 5_000,
|
|
49
|
+
initializeTimeoutMs: 5_000,
|
|
50
|
+
...clientOptions,
|
|
51
|
+
});
|
|
52
|
+
clients.push(client);
|
|
53
|
+
await client.start();
|
|
54
|
+
await client.initialize();
|
|
55
|
+
return { client, ...fixture.paths };
|
|
56
|
+
},
|
|
57
|
+
async makeBareConnection(
|
|
58
|
+
scenario: Record<string, unknown>,
|
|
59
|
+
): Promise<{ readonly connection: LspClientConnection; readonly events: string }> {
|
|
60
|
+
const fixture = createFixture(scenario, tempDirectories);
|
|
61
|
+
const connection = new LspClientConnection(fixture.workspace, fixture.server, {
|
|
62
|
+
requestTimeoutMs: 5_000,
|
|
63
|
+
initializeTimeoutMs: 5_000,
|
|
64
|
+
});
|
|
65
|
+
clients.push(connection);
|
|
66
|
+
await connection.start();
|
|
67
|
+
await connection.initialize();
|
|
68
|
+
return { connection, events: fixture.paths.events };
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createFixture(scenario: Record<string, unknown>, tempDirectories: string[]) {
|
|
74
|
+
// The fixture server echoes URIs built from the canonical temp path, so the client root must be
|
|
75
|
+
// canonical too. On macOS tmpdir() is a /var -> /private/var symlink; without realpath the
|
|
76
|
+
// publishes target a document the client never opened (#9476).
|
|
77
|
+
const workspace = mkdtempSync(join(realpathSync(tmpdir()), "lsp-apply-edit-"));
|
|
78
|
+
tempDirectories.push(workspace);
|
|
79
|
+
const source = join(workspace, "source.ts");
|
|
80
|
+
const destination = join(workspace, "destination.ts");
|
|
81
|
+
const closed = join(workspace, "closed.ts");
|
|
82
|
+
const created = join(workspace, "created.ts");
|
|
83
|
+
const scenarioPath = join(workspace, "scenario.json");
|
|
84
|
+
const events = join(workspace, "events.jsonl");
|
|
85
|
+
writeFileSync(source, "const before = 1;\n", "utf-8");
|
|
86
|
+
writeFileSync(closed, "const closed = 1;\n", "utf-8");
|
|
87
|
+
const uriByPlaceholder = new Map([
|
|
88
|
+
["file:///placeholder", pathToFileURL(source).href],
|
|
89
|
+
["file:///destination", pathToFileURL(destination).href],
|
|
90
|
+
["file:///closed", pathToFileURL(closed).href],
|
|
91
|
+
["file:///created", pathToFileURL(created).href],
|
|
92
|
+
]);
|
|
93
|
+
writeFileSync(scenarioPath, JSON.stringify(replacePlaceholderUris(scenario, uriByPlaceholder)), "utf-8");
|
|
94
|
+
writeFileSync(events, "", "utf-8");
|
|
95
|
+
const server: ResolvedServer = {
|
|
96
|
+
id: "workspace-edit-fixture",
|
|
97
|
+
command: [process.env["NODE_BINARY"] ?? "node", fixturePath, scenarioPath, events],
|
|
98
|
+
extensions: [".ts"],
|
|
99
|
+
priority: 0,
|
|
100
|
+
};
|
|
101
|
+
return { workspace, server, paths: { workspace, source, destination, closed, created, events } };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function replacePlaceholderUris(value: unknown, uriByPlaceholder: ReadonlyMap<string, string>): unknown {
|
|
105
|
+
if (Array.isArray(value)) return value.map((item) => replacePlaceholderUris(item, uriByPlaceholder));
|
|
106
|
+
if (typeof value !== "object" || value === null) {
|
|
107
|
+
return typeof value === "string" ? (uriByPlaceholder.get(value) ?? value) : value;
|
|
108
|
+
}
|
|
109
|
+
return Object.fromEntries(
|
|
110
|
+
Object.entries(value).map(([key, item]) => [key, replacePlaceholderUris(item, uriByPlaceholder)]),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface RenameTextEditTarget {
|
|
115
|
+
readonly version: number | null;
|
|
116
|
+
readonly uri?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function renameTextEdit(before: string, after: string, target: RenameTextEditTarget): WorkspaceEdit {
|
|
120
|
+
const uri = target.uri ?? "file:///placeholder";
|
|
121
|
+
return {
|
|
122
|
+
documentChanges: [
|
|
123
|
+
{
|
|
124
|
+
textDocument: { uri, version: target.version },
|
|
125
|
+
edits: [
|
|
126
|
+
{
|
|
127
|
+
range: { start: { line: 0, character: 6 }, end: { line: 0, character: 6 + before.length } },
|
|
128
|
+
newText: after,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function diagnostic(message: string): Diagnostic {
|
|
137
|
+
return { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } }, message };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function readEvents(path: string): readonly RecordedEvent[] {
|
|
141
|
+
const events: RecordedEvent[] = [];
|
|
142
|
+
for (const line of readFileSync(path, "utf-8").split("\n")) {
|
|
143
|
+
if (!line) continue;
|
|
144
|
+
const value: unknown = JSON.parse(line);
|
|
145
|
+
if (isRecordedEvent(value)) events.push(value);
|
|
146
|
+
}
|
|
147
|
+
return events;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function waitForEventCount(
|
|
151
|
+
path: string,
|
|
152
|
+
predicate: (event: RecordedEvent) => boolean,
|
|
153
|
+
count: number,
|
|
154
|
+
): Promise<readonly RecordedEvent[]> {
|
|
155
|
+
const deadline = Date.now() + 2_000;
|
|
156
|
+
while (Date.now() < deadline) {
|
|
157
|
+
const matches = readEvents(path).filter(predicate);
|
|
158
|
+
if (matches.length >= count) return matches;
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
160
|
+
}
|
|
161
|
+
return readEvents(path).filter(predicate);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function waitForEventCountBySubscription(
|
|
165
|
+
path: string,
|
|
166
|
+
predicate: (event: RecordedEvent) => boolean,
|
|
167
|
+
count: number,
|
|
168
|
+
): Promise<readonly RecordedEvent[]> {
|
|
169
|
+
const initial = readEvents(path).filter(predicate);
|
|
170
|
+
if (initial.length >= count) return initial;
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
let watcher: ReturnType<typeof watch> | undefined;
|
|
173
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
174
|
+
let settled = false;
|
|
175
|
+
const finish = (matches: readonly RecordedEvent[]): void => {
|
|
176
|
+
if (settled) return;
|
|
177
|
+
settled = true;
|
|
178
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
179
|
+
watcher?.close();
|
|
180
|
+
resolve(matches);
|
|
181
|
+
};
|
|
182
|
+
const check = (): void => {
|
|
183
|
+
const matches = readEvents(path).filter(predicate);
|
|
184
|
+
if (matches.length >= count) finish(matches);
|
|
185
|
+
};
|
|
186
|
+
watcher = watch(path, { persistent: false }, check);
|
|
187
|
+
timeout = setTimeout(() => finish(readEvents(path).filter(predicate)), 2_000);
|
|
188
|
+
check();
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isRecordedEvent(value: unknown): value is RecordedEvent {
|
|
193
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
194
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createWorkspaceEditTestHarness,
|
|
8
|
+
diagnostic,
|
|
9
|
+
readEvents,
|
|
10
|
+
renameTextEdit,
|
|
11
|
+
} from "./workspace-apply-edit-test-support.js";
|
|
12
|
+
|
|
13
|
+
const harness = createWorkspaceEditTestHarness();
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await harness.cleanup();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("LspClient workspace/applyEdit", () => {
|
|
20
|
+
it("#given a server-applied edit returned again by rename #when rename completes #then one write is reused truthfully", async () => {
|
|
21
|
+
const context = await harness.makeClient({
|
|
22
|
+
renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 1 }), renameResult: "same" }],
|
|
23
|
+
diagnostics: [diagnostic("fresh")],
|
|
24
|
+
});
|
|
25
|
+
let writes = 0;
|
|
26
|
+
context.client.setWorkspaceEditIo({
|
|
27
|
+
writeFile(path, content) {
|
|
28
|
+
writes += 1;
|
|
29
|
+
writeFileSync(path, content, "utf-8");
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
await context.client.openFile(context.source);
|
|
33
|
+
|
|
34
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
35
|
+
|
|
36
|
+
expect(result.apply.success).toBe(true);
|
|
37
|
+
expect(result.apply.totalEdits).toBe(1);
|
|
38
|
+
expect(writes).toBe(1);
|
|
39
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const after = 1;\n");
|
|
40
|
+
expect(context.client.getOpenDocumentVersion(context.source)).toBe(2);
|
|
41
|
+
expect(context.client.getStoredDiagnostics(pathToFileURL(realpathSync(context.source)).href)).toEqual([
|
|
42
|
+
diagnostic("fresh"),
|
|
43
|
+
]);
|
|
44
|
+
const responses = readEvents(context.events).filter(
|
|
45
|
+
(event) => event.type === "clientResponse" && event.method === "workspace/applyEdit",
|
|
46
|
+
);
|
|
47
|
+
expect(responses).toHaveLength(1);
|
|
48
|
+
expect(responses[0]?.result).toEqual({ applied: true });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("#given a server-applied edit and a null rename result #when rename completes #then the recorded result is reused", async () => {
|
|
52
|
+
const context = await harness.makeClient({
|
|
53
|
+
renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 1 }), renameResult: "null" }],
|
|
54
|
+
});
|
|
55
|
+
await context.client.openFile(context.source);
|
|
56
|
+
|
|
57
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
58
|
+
|
|
59
|
+
expect(result.edit).toBeNull();
|
|
60
|
+
expect(result.apply.success).toBe(true);
|
|
61
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const after = 1;\n");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("#given two versioned server edits #when applied sequentially #then the synchronized version accepts the second edit", async () => {
|
|
65
|
+
const context = await harness.makeClient({
|
|
66
|
+
renameSteps: [
|
|
67
|
+
{ applyEdit: renameTextEdit("before", "first_", { version: 1 }), renameResult: "same" },
|
|
68
|
+
{ applyEdit: renameTextEdit("first_", "second", { version: 2 }), renameResult: "same" },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
await context.client.openFile(context.source);
|
|
72
|
+
|
|
73
|
+
const first = await context.client.rename(context.source, 1, 6, "first_");
|
|
74
|
+
const second = await context.client.rename(context.source, 1, 6, "second");
|
|
75
|
+
|
|
76
|
+
expect(first.apply.success).toBe(true);
|
|
77
|
+
expect(second.apply.success).toBe(true);
|
|
78
|
+
expect(context.client.getOpenDocumentVersion(context.source)).toBe(3);
|
|
79
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const second = 1;\n");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("#given a mismatched document version #when the server requests applyEdit #then mutation is rejected truthfully", async () => {
|
|
83
|
+
const context = await harness.makeClient({
|
|
84
|
+
renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 9 }), renameResult: "same" }],
|
|
85
|
+
});
|
|
86
|
+
await context.client.openFile(context.source);
|
|
87
|
+
|
|
88
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
89
|
+
|
|
90
|
+
expect(result.apply.success).toBe(false);
|
|
91
|
+
expect(result.apply.errors.join("\n")).toContain("document version");
|
|
92
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const before = 1;\n");
|
|
93
|
+
const response = readEvents(context.events).find(
|
|
94
|
+
(event) => event.type === "clientResponse" && event.method === "workspace/applyEdit",
|
|
95
|
+
);
|
|
96
|
+
expect(response?.result).toEqual({
|
|
97
|
+
applied: false,
|
|
98
|
+
failureReason: expect.stringContaining("document version"),
|
|
99
|
+
failedChange: 0,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("#given a server-applied edit and a different rename result #when rename completes #then conflict is reported without a second mutation", async () => {
|
|
104
|
+
const context = await harness.makeClient({
|
|
105
|
+
renameSteps: [
|
|
106
|
+
{
|
|
107
|
+
applyEdit: renameTextEdit("before", "after", { version: 1 }),
|
|
108
|
+
renameResult: "different",
|
|
109
|
+
differentEdit: renameTextEdit("before", "other_", { version: 1 }),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
let writes = 0;
|
|
114
|
+
context.client.setWorkspaceEditIo({
|
|
115
|
+
writeFile(path, content) {
|
|
116
|
+
writes += 1;
|
|
117
|
+
writeFileSync(path, content, "utf-8");
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
await context.client.openFile(context.source);
|
|
121
|
+
|
|
122
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
123
|
+
|
|
124
|
+
expect(result.apply.success).toBe(false);
|
|
125
|
+
expect(result.apply.errors.join("\n")).toContain("conflicts with server-applied workspace edit");
|
|
126
|
+
expect(writes).toBe(1);
|
|
127
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const after = 1;\n");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("#given a real commit I/O failure #when the server requests applyEdit #then both protocol and rename result report failure", async () => {
|
|
131
|
+
const context = await harness.makeClient({
|
|
132
|
+
renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 1 }), renameResult: "same" }],
|
|
133
|
+
});
|
|
134
|
+
context.client.setWorkspaceEditIo({
|
|
135
|
+
writeFile() {
|
|
136
|
+
throw new InjectedWorkspaceIoError();
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
await context.client.openFile(context.source);
|
|
140
|
+
|
|
141
|
+
const result = await context.client.rename(context.source, 1, 6, "after");
|
|
142
|
+
|
|
143
|
+
expect(result.apply.success).toBe(false);
|
|
144
|
+
expect(result.apply.errors.join("\n")).toContain("I/O failure during text");
|
|
145
|
+
expect(readFileSync(context.source, "utf-8")).toBe("const before = 1;\n");
|
|
146
|
+
const response = readEvents(context.events).find(
|
|
147
|
+
(event) => event.type === "clientResponse" && event.method === "workspace/applyEdit",
|
|
148
|
+
);
|
|
149
|
+
expect(response?.result).toEqual({
|
|
150
|
+
applied: false,
|
|
151
|
+
failureReason: expect.stringContaining("I/O failure during text"),
|
|
152
|
+
failedChange: 0,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
class InjectedWorkspaceIoError extends Error {
|
|
158
|
+
override readonly name = "InjectedWorkspaceIoError";
|
|
159
|
+
|
|
160
|
+
constructor() {
|
|
161
|
+
super("injected workspace I/O failure");
|
|
162
|
+
}
|
|
163
|
+
}
|