From 78c303990638118c48e5d11c05df533f19e896af Mon Sep 17 00:00:00 2001 From: Yao Jingxi <23722032@bjtu.edu.cn> Date: Fri, 13 Nov 2026 23:30:36 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(core)!:=20=E5=88=A0=E9=99=A4=20Java/Ma?= =?UTF-8?q?ven/JDT=20=E7=89=B9=E5=88=A4=E4=B8=8E=E5=A5=91=E7=BA=A6?= =?UTF-8?q?=E4=B8=93=E5=B1=9E=E6=AD=BB=E5=91=BD=E4=BB=A4=EF=BC=8Crun/start?= =?UTF-8?q?=20=E4=B8=B4=E6=97=B6=E6=96=87=E4=BB=B6=E6=94=AF=E6=8C=81=20bas?= =?UTF-8?q?e64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core 对 Java 等语言零依赖、零特判,语言和工具专属知识全部由插件注入。 - 删除 execution.planLaunchCommand(JDK @argfile 规划)及 execution/launch_plan.rs - 删除 LSP 内置语言目录:lsp/languages/catalog.rs、resources/lsp/language-providers.json、 FFI lithe_core_lsp_provider_catalog_json 与头文件声明 - 删除 tree-sitter、tree-sitter-java 依赖 - 删除 lithe-ide-host 目录里的 Java/Maven 工具 - 删除只存在于契约、fixture、测试中的 23 个命令名(java.*、lsp.jdt*、maven.*、 mybatis.index、spring.index、runConfig.selectJava、build.languageImport、 agent.attachments.retentionPlan)及其 fixture - 删除 maven-launch-context-v1、maven-portable-configuration-v1、run-configuration-v1 schema - run/start 的 temporaryFiles 增加 {name, base64} 字节文件,宿主原样写入 - 模板 ${field..major} 取第一个数字分量,不再特判 1.x - 旧版工具链 pin 的条目改为不透明字符串记录,不再点名 javaHome - 契约文档和测试 fixture 去掉 Java/Maven/JDT 专属内容 Refs 1lck/Lithe-IDEA#1856, 1lck/Lithe-IDEA#1857, 1lck/Lithe-IDEA#1858, 1lck/Lithe-IDEA#1853 --- rust/Cargo.lock | 38 - rust/contracts/application-boundary.md | 372 +----- rust/contracts/ide-api/v1.md | 44 +- .../maven-launch-context-v1.schema.json | 21 - ...aven-portable-configuration-v1.schema.json | 22 - .../run-configuration-v1.schema.json | 48 - .../run-configuration-v2.schema.json | 43 +- rust/contracts/rust-core-api.md | 1157 ++--------------- rust/contracts/toolchain-local-v1.schema.json | 29 +- .../toolchain-requirements-v1.schema.json | 4 +- .../shared-defaults.json | 2 +- .../build-project-options/validation.json | 26 +- rust/fixtures/debug/hot-code-replace-v1.json | 2 +- .../editor/basic-file-highlighting-v1.json | 40 - .../execution/framework-identity.json | 114 -- rust/fixtures/execution/gradle.json | 92 -- .../maven-java-main-source-sets-v1.json | 28 - .../execution/maven-runtime-versions.json | 10 - rust/fixtures/execution/maven.json | 245 ---- .../standalone-java-compile-run-v1.json | 40 - rust/fixtures/ide-api/calls.json | 49 +- rust/fixtures/ide-api/host-tool-exposure.json | 18 - rust/fixtures/java/basic.json | 21 - rust/fixtures/java/run-markers-v1.json | 216 --- rust/fixtures/lsp/java-build-report-v1.json | 31 - rust/fixtures/lsp/java-completion-v1.json | 23 - .../lsp/java-launch-preparation-v1.json | 80 -- rust/fixtures/lsp/java-navigation-v1.json | 58 - .../lsp/java-project-runtimes-v1.json | 78 -- .../lsp/java-workspace-policy-v1.json | 79 -- rust/fixtures/lsp/jdt-cache-retention-v1.json | 52 - rust/fixtures/lsp/jdt-direct-launch-v1.json | 34 - rust/fixtures/lsp/jdt-readiness-v1.json | 74 -- .../lsp/jdt-workspace-fingerprint-v1.json | 35 - rust/fixtures/lsp/jdt-workspace-key-v1.json | 17 - .../fixtures/lsp/maven-profile-events-v1.json | 11 - rust/fixtures/lsp/project-preparation-v1.json | 12 - rust/fixtures/maven/basic.json | 49 - rust/fixtures/maven/dependency-tree-v2.json | 140 -- rust/fixtures/maven/diagnostics.json | 28 - rust/fixtures/maven/launch-plan-v1.json | 88 -- rust/fixtures/maven/platform-contract-v1.json | 30 - rust/fixtures/maven/source-roots-v1.json | 57 - rust/fixtures/maven/test-results-v1.json | 304 ----- .../fixtures/maven/testcase-durations-v1.json | 72 - rust/fixtures/modules/built-in-v1.json | 1 - rust/fixtures/mybatis/basic.json | 25 - .../lithe-spring-boot-git-graph/.classpath | 57 - .../lithe-spring-boot-git-graph/.project | 34 - .../org.eclipse.core.resources.prefs | 4 - .../.settings/org.eclipse.jdt.apt.core.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 10 - .../.settings/org.eclipse.m2e.core.prefs | 4 - .../lithe-spring-boot-git-graph/README.md | 17 - .../history/OrderController-feature.java | 14 - .../history/UserService-feature.java | 18 - .../lithe-spring-boot-git-graph/pom.xml | 46 - .../com/example/demo/DemoApplication.java | 11 - .../com/example/demo/user/UserController.java | 27 - .../com/example/demo/user/UserRepository.java | 17 - .../com/example/demo/user/UserService.java | 18 - .../src/main/resources/application.yml | 5 - .../automatic-java-selection.json | 284 ---- rust/fixtures/run-configuration/basic.json | 23 - .../run-configuration/build-provider.json | 4 +- .../configuration-toolchain-selection.json | 446 ------- .../run-configuration/editor-save.json | 20 +- .../run-configuration/hybrid-service-web.json | 58 + .../run-configuration/hybrid-spring-vue.json | 43 - .../run-configuration/invalid-version.json | 7 - .../maven-module-ownership.json | 91 -- .../run-configuration/multi-module.json | 39 - rust/fixtures/run-configuration/no-entry.json | 7 - .../project-environment.json | 22 - .../project-toolchain-selection.json | 328 ----- .../provider-prepared-java.json | 17 - .../provider-project-ownership.json | 156 --- .../run-configuration/single-module.json | 23 - .../subproject-toolchains.json | 45 - .../run-configuration/toolchain-mismatch.json | 15 - rust/fixtures/spring/basic.json | 45 - rust/lithe-core/Cargo.toml | 2 - rust/lithe-core/include/lithe_core.h | 1 - .../resources/lsp/language-providers.json | 500 ------- rust/lithe-core/src/build/run_start.rs | 63 +- rust/lithe-core/src/debug/engine.rs | 4 +- rust/lithe-core/src/debug/types.rs | 2 +- rust/lithe-core/src/execution/launch_plan.rs | 451 ------- rust/lithe-core/src/execution/mod.rs | 6 - rust/lithe-core/src/lsp/interface/client.rs | 10 +- rust/lithe-core/src/lsp/interface/engine.rs | 16 +- rust/lithe-core/src/lsp/interface/process.rs | 8 +- rust/lithe-core/src/lsp/languages/catalog.rs | 392 ------ rust/lithe-core/src/lsp/languages/mod.rs | 3 - rust/lithe-core/src/lsp/mod.rs | 1 - rust/lithe-core/src/lsp/tests.rs | 252 ---- rust/lithe-core/src/plugins/declarative.rs | 4 +- .../src/plugins/web_content_tests.rs | 4 +- .../src/project_generators/archive.rs | 4 +- .../lithe-core/src/project_generators/form.rs | 33 +- .../src/project_generators/manifest.rs | 4 +- rust/lithe-core/src/project_generators/mod.rs | 4 +- .../src/project_generators/template.rs | 38 +- rust/lithe-core/src/protocol/command.rs | 3 - rust/lithe-core/src/runtime/dispatcher.rs | 16 - rust/lithe-core/src/runtime/ffi.rs | 27 - .../src/tests/build_project_options.rs | 4 +- rust/lithe-core/src/tests/plugin_run_debug.rs | 64 + .../lithe-core/src/tests/run_configuration.rs | 156 +-- rust/lithe-core/src/tests/toolchain_pins.rs | 11 +- rust/lithe-core/src/toolchain/pins.rs | 10 +- rust/lithe-ide-host/src/catalog.rs | 84 +- rust/lithe-ide-host/src/lib.rs | 23 +- shared/plugins/contract/v1/README.md | 117 +- .../contract/v1/plugin-manifest.schema.json | 15 +- .../plugins/contract/v1/toolchains/README.md | 4 +- .../plugins/guides/build-tool-integration.md | 39 +- shared/plugins/reference/api-catalog.md | 4 +- shared/plugins/reference/domains/backend.md | 4 +- .../reference/domains/build-systems.md | 12 +- shared/plugins/reference/domains/languages.md | 42 +- shared/plugins/reference/domains/manifest.md | 8 +- .../plugins/reference/protocol-changelog.md | 7 + 123 files changed, 670 insertions(+), 7798 deletions(-) delete mode 100644 rust/contracts/maven-launch-context-v1.schema.json delete mode 100644 rust/contracts/maven-portable-configuration-v1.schema.json delete mode 100644 rust/contracts/run-configuration-v1.schema.json delete mode 100644 rust/fixtures/editor/basic-file-highlighting-v1.json delete mode 100644 rust/fixtures/execution/framework-identity.json delete mode 100644 rust/fixtures/execution/gradle.json delete mode 100644 rust/fixtures/execution/maven-java-main-source-sets-v1.json delete mode 100644 rust/fixtures/execution/maven-runtime-versions.json delete mode 100644 rust/fixtures/execution/maven.json delete mode 100644 rust/fixtures/execution/standalone-java-compile-run-v1.json delete mode 100644 rust/fixtures/java/basic.json delete mode 100644 rust/fixtures/java/run-markers-v1.json delete mode 100644 rust/fixtures/lsp/java-build-report-v1.json delete mode 100644 rust/fixtures/lsp/java-completion-v1.json delete mode 100644 rust/fixtures/lsp/java-launch-preparation-v1.json delete mode 100644 rust/fixtures/lsp/java-navigation-v1.json delete mode 100644 rust/fixtures/lsp/java-project-runtimes-v1.json delete mode 100644 rust/fixtures/lsp/java-workspace-policy-v1.json delete mode 100644 rust/fixtures/lsp/jdt-cache-retention-v1.json delete mode 100644 rust/fixtures/lsp/jdt-direct-launch-v1.json delete mode 100644 rust/fixtures/lsp/jdt-readiness-v1.json delete mode 100644 rust/fixtures/lsp/jdt-workspace-fingerprint-v1.json delete mode 100644 rust/fixtures/lsp/jdt-workspace-key-v1.json delete mode 100644 rust/fixtures/lsp/maven-profile-events-v1.json delete mode 100644 rust/fixtures/lsp/project-preparation-v1.json delete mode 100644 rust/fixtures/maven/basic.json delete mode 100644 rust/fixtures/maven/dependency-tree-v2.json delete mode 100644 rust/fixtures/maven/diagnostics.json delete mode 100644 rust/fixtures/maven/launch-plan-v1.json delete mode 100644 rust/fixtures/maven/platform-contract-v1.json delete mode 100644 rust/fixtures/maven/source-roots-v1.json delete mode 100644 rust/fixtures/maven/test-results-v1.json delete mode 100644 rust/fixtures/maven/testcase-durations-v1.json delete mode 100644 rust/fixtures/mybatis/basic.json delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/.classpath delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/.project delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.core.resources.prefs delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.apt.core.prefs delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.core.prefs delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.m2e.core.prefs delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/README.md delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/history/OrderController-feature.java delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/history/UserService-feature.java delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/pom.xml delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/DemoApplication.java delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserController.java delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserRepository.java delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserService.java delete mode 100644 rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/resources/application.yml delete mode 100644 rust/fixtures/run-configuration/automatic-java-selection.json delete mode 100644 rust/fixtures/run-configuration/basic.json delete mode 100644 rust/fixtures/run-configuration/configuration-toolchain-selection.json create mode 100644 rust/fixtures/run-configuration/hybrid-service-web.json delete mode 100644 rust/fixtures/run-configuration/hybrid-spring-vue.json delete mode 100644 rust/fixtures/run-configuration/invalid-version.json delete mode 100644 rust/fixtures/run-configuration/maven-module-ownership.json delete mode 100644 rust/fixtures/run-configuration/multi-module.json delete mode 100644 rust/fixtures/run-configuration/no-entry.json delete mode 100644 rust/fixtures/run-configuration/project-environment.json delete mode 100644 rust/fixtures/run-configuration/project-toolchain-selection.json delete mode 100644 rust/fixtures/run-configuration/provider-prepared-java.json delete mode 100644 rust/fixtures/run-configuration/provider-project-ownership.json delete mode 100644 rust/fixtures/run-configuration/single-module.json delete mode 100644 rust/fixtures/run-configuration/subproject-toolchains.json delete mode 100644 rust/fixtures/run-configuration/toolchain-mismatch.json delete mode 100644 rust/fixtures/spring/basic.json delete mode 100644 rust/lithe-core/resources/lsp/language-providers.json delete mode 100644 rust/lithe-core/src/execution/launch_plan.rs delete mode 100644 rust/lithe-core/src/lsp/languages/catalog.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 961c7b5a..a69e7b78 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -978,8 +978,6 @@ dependencies = [ "similar", "toml", "toml_edit", - "tree-sitter", - "tree-sitter-java", "unicode-normalization", "url", "zip", @@ -1947,12 +1945,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" - [[package]] name = "string_cache" version = "0.9.0" @@ -2276,36 +2268,6 @@ dependencies = [ "windows", ] -[[package]] -name = "tree-sitter" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" -dependencies = [ - "cc", - "regex", - "regex-syntax", - "serde_json", - "streaming-iterator", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-java" -version = "0.23.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa6cbcdc8c679b214e616fd3300da67da0e492e066df01bcf5a5921a71e90d6" -dependencies = [ - "cc", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-language" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" - [[package]] name = "try-lock" version = "0.2.5" diff --git a/rust/contracts/application-boundary.md b/rust/contracts/application-boundary.md index fd8a0d00..5d41ea90 100644 --- a/rust/contracts/application-boundary.md +++ b/rust/contracts/application-boundary.md @@ -40,130 +40,11 @@ Core's stateless `search.actions` returns ordered candidate offsets; the host ke ownership of action availability, execution and module activation. See [the action search fixture](../fixtures/search/actions-v1.json). -Global Agent instructions edit each upstream Agent's native user file, independently -of the current project and provider. Opening settings is read-only. Codex uses -`CODEX_HOME` (otherwise the user home `.codex`) and its first non-empty -`AGENTS.override.md` / `AGENTS.md`; Claude uses `CLAUDE_CONFIG_DIR` (otherwise -the user home `.claude`) and `CLAUDE.md`. The host resolves these locations; -UI supplies no arbitrary read/write destination. Absolute native paths in snapshots -are display and optimistic-write guards, not portable identifiers. A save checks -the snapshot's path and revision, replaces the file atomically, and reads the -effective source back. Failures retain the draft; reloading explicitly discards it -only after a successful read. Drafts stay separate by Agent. Updated instructions -load in a new native conversation; existing sessions are not rewritten or stopped. -Project instructions retain the upstream Agent's precedence rules. See -`fixtures/agent/agent-instructions-v1.json`. - -A user can also author one shared instruction text in platform-owned application -support storage and opt each supported Agent into a delimited block in its native -file. Synchronization preserves every byte outside that block. Turning a switch -off removes only that block. Enabled, unknown and out-of-date states come from -native snapshots, not a stored preference. Source saves and each target replacement -are atomic individually, not a cross-file transaction: per-Agent conflicts remain -visible and retryable while the saved shared draft and successful targets survive. -No startup/background synchronization or extra prompt injection occurs. A malformed -or duplicate marker is never repaired by overwriting it automatically. See -`fixtures/agent/shared-instructions-v1.json`. - Shared Monaco highlighting is owned by the current view/model pair. A queued callback for a closed or replaced model must finish without running against the new model or locking surviving documents. Primary, split and diff views share this lifecycle protection; actual symbol computation remains upstream-owned. -Agent prompt completion may include Agent-reported token counters; their accounting -scope belongs to the provider and is separate from context occupancy and subscription -quota. Platforms may measure locally observed turns with a monotonic clock, including -tools and permission waits, and freeze elapsed time at completion, failure or disconnect. -Normal Agent turns and permission decisions have no total-duration deadline. Quiet -activity notices are advisory and retain the busy turn; stop remains acknowledged -with bounded process cleanup. Missing usage and unmeasured replayed history remain -unknown. See the -[Agent host contract](rust-core-api.md) and `fixtures/agent/acp-events-v1.json`. - -Agent history lists can expose matching summaries as pages arrive while the remaining scan stays loading. Partial results prove presence only; final snapshots reconcile removals, and later failures retain visible rows. History surfaces share connection-local recent snapshots and in-flight requests; manual refresh remains immediate. Workspace/Agent isolation, stale-token rejection and upstream history ownership apply to both partial and final results. - -History provider filtering defaults to all providers and uses only upstream-reported -`modelProvider` identities. Choices span active and archived records; missing metadata -has an explicit unknown choice. A provider selection composes with title/ID search, -favorites and local removal, including archive results. It is transient presentation -state, not a routing or account setting, and never narrows the upstream history scan -or infers provenance from the active model. Bulk actions remain limited to visible -eligible records. Empty filtered results do not establish that no history exists. - -An Agent that advertises steering can accept supplementary user input while its -logical turn is active. Input delivery has a separate pending/received/failed -outcome and does not replace turn ownership, permissions, timing, or Stop. -Only an explicit unconsumed-input acknowledgement permits a normal continuation -prompt; slash commands are explicitly left unconsumed and deferred to this owned -prompt path, whose response also completes command-only input. Pending -continuations remain cancellable while staged configuration is awaiting its -acknowledgement, without cancelling the configuration itself. Unknown delivery -is never retried automatically. Platform UI enables -this behavior from the negotiated capability, not the Agent's display name. - -File context actions target a configured Agent and a root conversation within the -captured workspace. The quick action names the current Agent; other destinations -are grouped by Agent and identify existing sessions or a new conversation. Opening -the menu uses cached session summaries and does not start background Agents. -Attaching appends deduplicated native file references to that destination's draft, -preserves its text, and never sends, steers or cancels a turn. Draft text and files -remain isolated by Agent/session, including new-session creation and failed history -loads. Stale projects, removed Agents and unavailable sessions must not redirect the -attachment. Directories are not recursively expanded. References read saved disk -contents; dirty editor buffers are retained and prompt the user to save before sending. - -Agent activity separates reported plans, native subagents and file edits. Ordinary -tools are execution evidence, never inferred plan steps or child agents. Native -children expose their upstream task, state and delivered output, scoped to their -root conversation. Completion counts only explicit success; unknown outcomes -remain disconnected. Child permissions use the owning root's existing controls. -A platform enables this monitor only when it consumes negotiated child-session -events; execution and history storage remain owned by the upstream Agent. - -The opened workspace, supplied by the product, defines the default meaning of -"current project". A containing Git repository and inherited development rules -do not replace that scope. The shared Host adds the same explicit workspace -context to ordinary prompts and supplementary messages, including prompts after -history restoration. File contents and applicable rules remain Agent-owned; -explicit user requests can select a different scope. Native slash commands retain -their original text and ordered attachments, including deferred supplementary -commands; attachments do not cause workspace context to be appended. Host-added context is omitted from user-message replay -presented by Lithe and from generated live/history-list titles, without rewriting -upstream history or filtering model replies. Titles left empty by this filtering -are missing values, so presentation retains its existing title fallbacks. - -The Host enables the native Codex checklist through process-local -`tools.update_plan.enabled` configuration for both API and subscription launches. -It does not edit CLI settings or switch collaboration mode. Plan creation and -updates remain Agent-owned. - -Native child permissions preserve their requester separately from the root turn. -An ended child or ancestor cancels only that subtree's pending permissions; -presentation consumes the same lifecycle update to remove its prompts and refresh -attention/timing. Root and sibling permissions and the root prompt stay active. - -Agent activity presentation uses the selected session's reported plan, native -subagents and file diffs. Compact summaries may omit empty categories; ordinary -tools do not create placeholder tasks or child sessions. File-summary line totals -require complete reported counts for all included, completed changes; missing or -overflowing counts remain unknown rather than becoming partial or estimated totals. -Review acknowledgements are local to the exact reported version; later edits remain reviewable and start after the acknowledged prefix. -The same open session retains its acknowledgements through disconnects and history -reloads; closing its tab releases them. Replay must still match the exact version -or prefix, so changed upstream evidence is never hidden by a stale acknowledgement. -Reported excerpts and missing original content are not full-file snapshots. -Native rollback is an explicit user action: preserve dirty editor buffers, -reject paths outside the workspace and ambiguous or incomplete evidence, retain -unrelated text, and use guarded native writes preserving encoding. Creation -requires explicit evidence before recoverable removal. Reject replacement terminal -symlinks instead of following them. Guard removal with the expected byte identity -and the native save lock; verify the actual moved object, restore it on conflict -without overwriting a recreated path, and report any manual recovery location. -Unsupported adapters must reject removal rather than use unchecked Trash. -Restoring a deletion -requires the path to remain absent. Batch failures retain unprocessed changes. -These presentation actions do not add Agent Host commands or persist chat history. - | Feature | Shared input/output | Platform-owned implementation | | --- | --- | --- | | Workspace | visible snapshot, relative paths, file metadata, deterministic ordering | workspace root selection, native dialogs, and watchers | @@ -172,11 +53,9 @@ These presentation actions do not add Agent Host commands or persist chat histor | Git | workspace commit plans with optional immutable ChangeList path scopes, dependency ordering, guarded steps and partial-success retry, changes, commits, branches, diffs, reviewed history actions and recovery, worktree listing and safe management, worktree-aware PR publication context, validation, and mutation results | Git executable discovery, credentials, process environment, opening checkout paths, local ChangeList selection and preference persistence | | GitHub | remote parsing, trusted request plans, normalized branch comparisons and pull requests/reviews/comments, deterministic ordering, and stable errors | OAuth configuration, HTTPS, browser opening, and operating-system credential storage | | [AI commit messages](ai-commit.md) | provider configuration parsing, commit rules, bounded diff evidence, request plans, and response text | local configuration discovery, credentials, HTTP, cancellation, and draft UI | -| Agent conversation (ACP) | supported-agent catalog, Node.js/npm detection, adapter install with the user's npm and numbers-only live download progress, CLI provenance and owner-preserving updates, per-agent API-key and model delivery over ACP stdio (Codex gateway authentication; Claude public session options), explicit Codex subscription authentication via the installed CLI, bounded official App Server quota reads with account checks, ACP v1 connection per workspace and agent, agent-owned session history (list/load), session config options, user-selected file references as ACP resource links, streamed tool evidence, permission decisions, acknowledged cancellation with bounded recovery, and process-tree lifecycle in the shared Rust host | provider and agent settings, API-key storage, credential-independent local default-model reading through AI configuration ports, data directory, workspace selection, module enablement, conversation UI and compact subscription quota presentation, workspace/Agent-scoped local history annotations (favorites, title overrides and recoverable hidden rows), and user-selected Markdown export destinations | | [IDE capability API / MCP](ide-api/v1.md) | canonical tool schemas, narrowing host selection, permission/argument validation, stable execution IDs, bounded output cursors and errors | implemented workflow inventory, explicit project authorization, live environment/Build/Run application actions, helper discovery and writable connection storage | -| Runtime | Java/Maven requirements, normalized candidates, and effective toolchain references | JDK/Maven probing and executable paths | +| Runtime | tool requirements, normalized candidates, and effective toolchain references | tool probing and executable paths | | Language tooling | provider catalog, local fallback results, complete LSP process/session runtime, capabilities, diagnostics, UTF-16 edits, and normalized feature results | executable/environment discovery and UI provider routing | -| Java/Maven/Spring | deterministic Maven-root selection, project structure, modules and profiles, bounded dependency-tree normalization; compiler diagnostic parsing; Java source structure, symbols, code vision, run-configuration detection, Spring configuration/bean/endpoint indexing, and JDTLS/Java Debug adapter policy | JDK/Maven discovery, local dependency-repository selection, Java/Maven child processes, and sockets | | Run/Debug | versioned configuration documents, three-layer resolution, diagnostics, platform-neutral launch plans, DAP framing/state, reverse terminal requests, breakpoint relocation, stepping filters, threads, stacks, variables, and events | project and preference persistence, native edit reporting, adapter discovery, PTY/ConPTY debuggee launch, child processes, sockets, native termination, and UI | | Terminal | input bytes, output bytes, lifecycle; [shell selection semantics](terminal-profiles.md) | PTY/ConPTY, shell discovery, native profile preferences, and environment | | Workbench background | versioned source (`none`, bundled slot `01`–`10`, or `custom`) and opacity | UI, image rendering, bundled-resource packaging, local-image access permission and persistence | @@ -218,11 +97,11 @@ marker. Existing Dev data is never merged or overwritten; subsequent launches do not fall back to Stable. Changing the channel alone does not switch profiles. Updater/channel suppression state, custom log destinations, browser caches, indexes, locks and runtime connections are excluded. macOS also copies its local -AI secret file, Maven preferences and marketplace source configuration, along with +AI secret file and marketplace source configuration, along with persisted database configuration/history/schedules and custom background image access records. These preference keys also count as existing Dev data. OS-vault credentials remain separate and may require signing in again. Windows snapshots -only native settings/Maven JSON and closed WebView localStorage, before creating +only native settings JSON and closed WebView localStorage, before creating any WebView; it never copies a live browser profile. A busy source or failed copy stops initialization with a retry message and no success marker. Published files and preferences resume from a staged snapshot after an interrupted commit. Stable @@ -338,11 +217,11 @@ contract must preserve these invariants: - Native plugin factories receive a read-only host context. Host services use stable IDs and shared protocols; plugin code cannot import a platform composition root or the application executable. -- AI Assistance, Agent Conversation, Terminal, Git, Search, Local History, Debug, and Java/Maven - execution are built-in lifecycle modules. They are not marketplace plugins. -- Java language tooling remains part of the built-in product. Every other - language provider is represented by an independently configurable bundled - language-support plugin. On macOS, Go and PHP are community declarative +- AI Assistance, Terminal, Git, Search, Local History, and Debug are built-in + lifecycle modules. They are not marketplace plugins. +- Every language provider, build tool and ecosystem runner is represented by an + independently configurable language-support plugin; Core names none of them. + On macOS, Go and PHP are community declarative plugins from Lithe Store (`io.github.lithe-idea.go-support`, `io.github.lithe-idea.php-support`); the retired official IDs `dev.lithe.plugin.go-support` and `dev.lithe.plugin.php-support` keep their @@ -427,64 +306,10 @@ exact launched root, never a process selected by name. Consumers must validate `operationID` before retaining it and invalidate it on stop, completion, reset, or replacement. Hosts without a local PID leave it absent. Test performance uses native CPU/RSS of that root and its descendants (including forked workers), with -RSS explicitly labeled as resident memory rather than JVM heap. Debug keeps its -existing exact-JVM JDK heap sampling; these are separate metric sources. - -Java workspace activation is a shared Core decision over visible relative -paths. Any workspace containing a non-ignored `.java` source starts one JDT LS -session asynchronously, even without Maven or Gradle metadata. That session is -owned by the workspace and remains alive until the workspace closes or the user -explicitly restarts it. `ready` means JDT LS has completed project import, not -merely that the process answered `initialize`. - -JDT LS runs only on the Temurin JDK 21 bundled with Lithe. This runtime is -independent of project Run/Debug JDK selection and has no user-configurable -path. A missing or invalid bundle is a packaging failure. The application shows -preparing, ready, failure, and timeout notifications; a navigation command while -preparing ends after the notice and is never replayed later. - -macOS and Windows adapters discover the selected JDT LS installation's Equinox -launcher JAR, platform configuration directory, Lombok agent, Java Debug -Server, and bundled Java executable. Java Test-capable adapters additionally -submit ordered extension bundles; Rust Core owns their ordering and -de-duplication, the JVM flags, and direct `java`/`java.exe` startup with array -arguments. The macOS TestNG runner remains a packaged native resource used only -when a TestNG session starts. Packaged JDT LS therefore has no runtime dependency -on shell wrappers, PowerShell, or the user's `PATH`. Legacy wrappers are an -external-plan compatibility fallback and are not the packaged execution path. - -Java test discovery remains a language-service workflow rather than a UI or -Debug Core parser. When the Tests tool window is opened or refreshed, the -language facade offers every Java source to Core's typed `javaTestItems` -operation; Core asks the Java Test extension for the class and method tree and -platforms project stable fully qualified identifiers into the native list. -Neither file names nor locally recognized annotations may prefilter this request, -so inherited tests and custom composed annotations remain visible. Closing the tool window, changing workspace, or reloading the -Java runtime cancels the owning discovery operation; late results cannot replace -the current workspace's tree. Discovery does not create a Debug session, result -socket, adapter connection, or target JVM. - -Starting one JUnit or TestNG file, class, or method creates a short-lived native -loopback result listener on demand. JDT LS owns project/test metadata, Rust Core -owns deterministic DAP launch argument projection, and the Debug module owns the -adapter session. Repeated launch, stop, project close, runtime reload, and launch -failure all cancel the active operation and release the listener. The selected -Run configuration remains the source of project-scoped Java runtime selection; -JDT LS remains authoritative for the test runner classpath, working directory, -and test-specific VM and program arguments. - -Platforms observe JDT LS version and non-recursive build-file metadata, while -Rust Core alone validates and reduces those observations to the opaque workspace -fingerprint. macOS and Windows adapters must not duplicate its ordering, -de-duplication, or serialization rules. +RSS explicitly labeled as resident memory rather than a runtime's managed heap. Editor changes use versioned incremental document synchronization and do not -restart the session or clear its index. Workspace watchers coalesce Maven and -Gradle changes before one project refresh and publish normalized watched-file -events. Platform adapters mark selected JDT LS caches as recently used, ask -Rust Core which inactive caches exceed the 30-day retention period, and delete -only those validated directories. **Java: Rebuild Index** remains the explicit -recovery path for the current workspace key. +restart the language-server session or clear its index. Language feature clients route through a provider interface rather than depending directly on an LSP session. Process-free providers remain available @@ -536,23 +361,16 @@ neither a terminal nor a debuggee process exists until an adapter requests one. Debugger disconnect ownership is portable. A session started with `launch` owns its local debuggee and sends `terminateDebuggee: true` when stopping. A -session started with `attach` does not own the remote JVM and sends +session started with `attach` does not own the remote process and sends `terminateDebuggee: false`; closing the native transport must therefore detach without killing the remote process. A session stopped before launch or attach also uses the non-terminating policy. -For JDT LS, the standard initialize handshake and project-import readiness use -separate Core-owned deadlines. Project import fails only after 45 seconds -without changed progress or the 10-minute absolute safety cap; platform clients -must not impose a shorter readiness deadline. The terminal timeout code is -`serviceReadyTimeout`, and its details preserve the last import, download, and -cache snapshot for both products. - ### Explicit debug adapter selection `DebugLaunchConfiguration.adapterProviderID` is optional host routing metadata. -Plugin registrations use the existing Registry identity `pluginID/adapterID`; -the host Java adapter retains `java`. The field is not a DAP argument, a process +Plugin registrations use the existing Registry identity `pluginID/adapterID`. +The field is not a DAP argument, a process command, or a plugin-private function call. An omitted identity uses the existing file selection. An explicit identity, including an empty or withdrawn one, must never fall back to another adapter matching the file. @@ -581,8 +399,8 @@ executable permissions before binding target facts through `debug.adapters.start Build-tree Debug and saved-provider Run Debug save documents first and revalidate workspace, source, configuration, model and plugin identity before handoff. Debug owns the resulting session; stopping preparation or accepting late replies cannot -start a session after invalidation. Java/JDT and cross-plugin preparation are not -covered by this native increment. +start a session after invalidation. Cross-plugin preparation is not covered by +this native increment. ## Error Codes @@ -653,8 +471,8 @@ replace those channels or swallow real source changes mixed with build output. Run configuration behavior is exposed through the `runConfig.*` commands. Platform clients coordinate inspection, generation, resolution, typed document edits, and launch planning, but must not implement a second JSON merger, -toolchain matcher, ID generator, argument parser, or Java/Maven argument -builder. Opening a project inspects existing files without writing; generation +toolchain matcher, ID generator, or argument parser. Ecosystem-specific +arguments are built by the plugin that declared the run. Opening a project inspects existing files without writing; generation is an explicit user action. Shared project overrides stay in `.lithe/run/configurations.json`. Machine-local overrides may live in `.lithe/run/local.json` or in a host-owned document supplied as @@ -669,92 +487,6 @@ than inferred from the provider namespace. An automatically discovered runtime path is session-effective: validation and launch share it, but persistence still requires an explicit user selection. -Maven tool-window execution uses `maven.launchPlan`; platform views do not -assemble Maven arguments. Portable profile and Skip Tests defaults conform to -[`maven-portable-configuration-v1.schema.json`](maven-portable-configuration-v1.schema.json). -The transient Core request conforms to -[`maven-launch-context-v1.schema.json`](maven-launch-context-v1.schema.json). -External `settings.xml`, local repository, Maven executable, and Maven JDK -paths remain in a machine-local store. They may be supplied transiently to Core -for planning and fingerprinting, but Core never opens `settings.xml` or -serializes those paths into the portable project context. - -Expanding a module's Dependencies node starts an on-demand query using -`maven.dependencyPlan`; Core owns the fixed plugin arguments and normalizes the -captured text through `maven.dependencies`. Each platform owns a separate, -bounded Maven process for this query so dependency loading cannot replace build -output or stop an ordinary Maven task. Results are cached by module until the -project or Maven configuration changes. The UI exposes loading, ready, failed, -and cancelled states, rejects stale results, and links every dependency to the -owning module's `pom.xml`. Dependency failure never blocks project loading or -Java editing. - -The Java language-server startup consumes that same context. Core exposes the -selected `settings.xml` to JDT LS as -`java.configuration.maven.userSettings`, then applies the sorted Profile set -to the reactor and every recursively declared Maven module after JDT LS -reports `ServiceReady`. The Java session reaches `ready` at that verified -signal; Profile updates then run as a bounded background task with at most -eight in-flight projects. Each project reports its own result, and a rejected -or timed-out update preserves the usable Java session while exposing a partial -failure that the host can retry. - -Maven-backed framework, test, and module launch planning consumes the current -project Maven context. A Run Configuration's explicit Profiles and toolchain -paths take precedence; explicit `cwd` and `extensions.maven.skipTests` values -also take precedence, including `skipTests: false`. Unset values inherit the -project settings. The shared Core applies the final Maven argument order; -tool-window, framework, test, and module launches add `-am` when reactor -dependencies must be built. - -Project-owned Java Main and a Maven Spring Boot service with a resolved Java -entry source use a different boundary: the Java language service selects the -source target, Java Debug Server builds the owning project and resolves its -runtime classpath/module path, Core projects those structured paths into a -direct JDK launch, and the Run host starts that one JVM. Maven still defines the -project model, but Run never sends either target through a reactor-wide Java -launch goal. A user-authored or legacy Spring Boot service without a resolved Java entry source retains -its Maven-goal compatibility path. Run and Debug share the Java preparation path -so module selection, generated sources, test-source mains, and dependency paths -do not drift. - -Both platforms use `java.prepareLaunch` for source-specific entry selection, -the serialized JDT build, and runtime path resolution. One operation ID owns all -three phases and their cancellation; a cancelled or timed-out attempt cannot -advance on a late upstream reply. Hosts save dirty workspace documents before -preparation and abort on any save failure. JDT remains the authority for project -membership and paths; Core retains only the current attempt's intermediate -results. Native adapters still own JDK discovery and process execution. - -An unsuccessful Java launch build is evidence, not an unconditional host veto. -For `javaBuildCompilationErrors` and `javaBuildFailed`, the language boundary -still resolves the target's runtime paths and returns them with Core's build -report. Run and Debug pause the original attempt and offer Run Anyway, Always -Continue for this workspace, and Cancel; a reported index rebuild recovery also -offers Java: Rebuild Index. Continuing resumes the same attempt and never -issues a second build. Cancellation, timeout, transport failure, and an unknown -build status do not carry a code verdict and remain non-overridable. Build -elapsed time is displayed as evidence only, never used as a trust threshold. -Windows opens the Run tool window when a decision is required, including when -Debug was initiated from the Maven tool window, so no launch waits on an -unmounted prompt. - -Java test actions use the same Maven process lifecycle for a complete JUnit 4 -or JUnit 5 test class and for an individual method. The selector is validated -before launch and is passed through `maven.launchPlan`; no platform assembles a -shell command. Surefire/Failsafe text is normalized through `maven.testResults` -into passed, failed, skipped, and total counts plus ordered failure details. -When a stack frame resolves inside the workspace, the failure links to its -one-based source line. The active test operation owns cancellation and stop; -late output or parsed results cannot replace a newer run. The last valid class -or method selection remains available for an explicit rerun, while cancellation -clears only the active result. - -Maven module menus use Core's resolved `extensions.maven.reactorPath` and -module identity before preferring a default Run configuration. An effective -working-directory override is not project ownership. File-dependent entries -such as Current File are not module launch candidates. - On Windows, Debug cleanup owns a Run execution ID in addition to its reusable output slot. The host checks that ID atomically when stopping the process so a late adapter shutdown cannot terminate a replacement Run in the same slot. @@ -772,25 +504,6 @@ application DOM as executable markup. Rendering and resizable layout are owned by the platform. The behavior fixture is [`svg-preview-v1.json`](../fixtures/editor/svg-preview-v1.json). -### Java project preparation presentation - -Core projects the existing Java session, Maven profile task, and JDT build gate -into `projectPreparation` runtime events. Both products render this in their -status bar and Run panel, with language service settings and diagnostic access. -`starting`, `importing`, `configuring`, and `building` describe actual work; -`ready` describes preparation completion, not successful compilation or a valid -launch configuration. Ordinary indexing is not a run prerequisite. - -Preparation is scoped to the Java workspace session. Only JDT-backed launch -paths honor `blocksRun`; other language and Maven-goal launchers keep their own -prerequisites. Partial profile failures remain visible without blocking unrelated -modules: the target build still validates its own readiness. Consumers reject old -session updates and clear state on explicit stop/workspace replacement. -Core's bounded preparation wait is the single launch gate; platform services and -Run controls do not race it with a second snapshot check. Preparation remains -visible while a click queues behind Core, and a stale visible `ready` state -cannot disagree with a separate host-owned preparation veto. - ### Optional PHP support PHP plugin installation is explicit. The base application ships neither a PHP @@ -846,47 +559,6 @@ text. Register bundled contributions in `frontend/editor`, not in a separate platform-specific tokenizer. Text fixtures are shared under `rust/fixtures/editor/text-content-v1.json`. -### Updating a running Java service - -Run/Debug presentation owns an explicit update action bound to an execution ID, -not the latest Run configuration or focused editor. Platform workflows save the -workspace, reuse the live JDT session to verify the original runtime paths, and -compile with the existing Core build coordinator. Build failures cannot be -bypassed for updates. Core owns Java Debug Server response normalization; hosts -own deadlines, stale-result rejection, UI progress and explicit restart actions. - -Run offers compilation for executions whose launch classpath includes Spring -Boot DevTools. Compilation completion does not establish restart or readiness; -the UI directs users to service output, and respects a project-configured trigger -file. Debug uses HotSwap with DevTools automatic restart disabled at JVM launch. -Remote attach and non-JDT launches have no update action in this first version. -No installed resources or new runtime caches are written: output remains in -JDT-owned workspace build paths and the existing platform-owned JDT state. - -### Agent 响应状态展示 - -- 状态与已观察到的 ACP 进度一致:准备会话、等待响应、收到推理、收到回复、本轮工具执行、等待授权、明确重试和正在停止。静默时长不能证明正在推理或重试,已回放的历史工具不属于当前轮次。 -- 固定 codex-acp 的 `session_info_update._meta.codex.error.willRetry == true` 和非空 `turnId` 是 macOS 重试显示的证据;该通知可能没有 `title`。共享样例 `codexRetry` 由官方 ACP SDK 往返验证,Host 继续透传元数据,不新增请求/事件种类或网络重试策略。 -- 状态隔离到会话,新进度清除重试显示,完成/失败/断连清理本轮状态,正在停止时不因迟到进度回退;不得显示未经脱敏的原始错误或从文本猜测重试次数。请求仍等待上游完成/取消确认,既有十分钟 prompt 上限和十秒取消上限不变。 -- 这次交付 macOS 展示;Windows 对话 UI 的消费与真实验收仍待完成。展示状态只在内存中,不写入 bundle、安装目录或上游历史。 - -### Claude Desktop 机制卡片 - -- Host 识别 Claude Desktop 注入的 MCP 工具(`ccd_*`、`visualize`、`terminal`、`Claude_Browser`、`computer-use`、`scheduled-tasks` 与按 UUID 命名的 claude.ai 连接器),为每个调用给出可读的英文 `title` 和 `kind`;不认识的 MCP 服务原样透传。 -- 有交互含义的调用在 `tool_call` / `tool_call_update` 的 `_meta.lithe.claudeDesktop` 附加卡片,字段 `card` 决定类型:`chapter {title, summary?}`、`nextStep {actions: [{label, message}] ≤4}`、`task {title, summary?, prompt?, state: proposed|withdrawn|started|handedOff}`、`pullRequest {action: bound|unbound|monitored|autoMerge, url, number?, autoFix?, enabled?}`、`directory {action: changed|requested, path}`、`widget {title, code ≤256 KiB 字符}`。文本字段截断到 16 KiB 字符;超限的 `widget.code` 置空。 -- `nextStep` 是 Host 另发的已完成调用,id 为原 `wrap_up` 调用 id 加 `#next`;任务撤回或启动的结果到达后,Host 以原卡片调用 id 补发整张卡片。后到的卡片整体替换先前的卡片;不带卡片的更新不清除卡片。 -- 客户端只在调用状态为 `completed` 时显示卡片;未知 `card` 类型、缺字段或非 http(s) 的 PR 链接退回普通工具行。卡片动作只填写输入框,不代替用户发送。可视化内容必须在禁止网络加载、非持久存储、禁止导航的隔离网页中渲染。 -- 共享样例 `rust/fixtures/agent/claude-desktop-cards-v1.json` 按调用顺序记录 Host 收到的更新(`updates`)和呈现给客户端的结果(`presented`),覆盖六种卡片、只换标题的行和原样透传;Host 测试逐例重放它,客户端按它解码卡片。 - - -Agent skill management groups files by Global/Project and retains their Agent -provenance. Codex-reported capabilities and Claude's partial directory inventory -are distinct; neither file presence nor a management-list row proves that the -current conversation has used a skill. Explicit create/import operations write -only to native user/project directories and preserve existing skills. Explicit toggles write native settings: Codex by discovered path via its official App Server API, Claude by name via skillOverrides in user or project-local settings. Claude states are configured visibility, not effective managed/session policy. Never persist a separate Lithe enabled flag, move packages to simulate disablement, or claim that a change revokes already loaded session content. File -contents remain untrusted document data. Settings discovery and file management -must not create conversations, send prompts, or interrupt existing Agent work. - Build-provider application preparation and execution have separate owners. The workspace Build session validates a current target and prepares it; the native Run host owns the application process, output and stop. Explicit persistence @@ -913,7 +585,6 @@ Automatic Provider run discovery uses the current public declaration/target pair independently of language imports. The host projects generic selectors and upstream labels; Core owns stable Run IDs, layering and snapshot freshness. Neither layer interprets framework IDs or reconstructs commands/toolchains from those selectors. -The existing Java ownership projection remains separate during the remaining migration. Portable Provider user choices live in Run v2 `extensions.build-run`, separately from `extensions.build` selector identity. Hosts preserve exact runtime argument @@ -927,12 +598,3 @@ the prefix does not confer authority. Core deduplicates only the generated count of the effective saved selector and validates it before use. Prepared team/local Run layers can be resolved without filesystem publication; native adapters own cancellation, source checks, metadata-preserving conditional writes, rollback and readback. - -### Project Java runtime and language-server runtime - -Platforms supply the selected project JDK with `javaRuntimes[].isDefault`, reusing -Run's project configuration and requirement-aware selection. The JDT host JVM may -remain the bundled supported JVM. Core projects the selected project home to JDT's -`java.configuration.runtimes[].default`; it must not infer the project VM from the -server executable. An invalid explicit selection must not mark an unrelated discovered candidate as the default; -Windows reports the invalid setting before launch. diff --git a/rust/contracts/ide-api/v1.md b/rust/contracts/ide-api/v1.md index 5166ad8d..4d3de7c1 100644 --- a/rust/contracts/ide-api/v1.md +++ b/rust/contracts/ide-api/v1.md @@ -101,16 +101,10 @@ configuration values, never portable workspace identifiers. | Tool | Arguments | Permission | Result | | --- | --- | --- | --- | | `lithe_project_inspect` | none | read | workspace identity, name, permissions and available readiness state | -| `lithe_environment_inspect` | none | read | project-local `settings`, `discovered`, and `effective` toolchains | -| `lithe_environment_configure` | optional `javaHomePath`, `mavenExecutablePath`, `mavenJavaHomePath`, `settingsPath`, `localRepositoryPath` | configure | persisted/effective settings or explicit failure; omitted settings are preserved | -| `lithe_maven_inspect` | none | read | project/modules, profiles, skip-tests and reload/error state | -| `lithe_maven_configure` | optional `profiles`, `skipTests` | configure | saved profiles and reload state | -| `lithe_maven_reload` | none | execute | outcome of existing project/Java synchronization | | `lithe_build_inspect` | none | read | detected plugin instances, declarations and current model identities | | `lithe_build_reload` | `instanceID` | execute | model reload requested; inspect until model state is ready | | `lithe_build_configure` | `instanceID`, `optionID`, `value` (string, boolean, string list or null) | configure | checked persisted option and reload state; null resets the declared option | | `lithe_build_execute` | `instanceID`, `taskID`, optional `projectID`, `targetID`, `modelTaskID`, `userArguments` | execute | `operationID` for the same Build execution shown by the IDE | -| `lithe_maven_execute` | `goals`, optional `modulePath` | execute | Windows current Maven execution; unavailable on macOS | | `lithe_run_list` | none | read | project configurations with IDs, names, providers and readiness | | `lithe_run_start` | `configurationID` | execute | `operationID` after launch; failure if launch was refused | | `lithe_run_restart` | `operationID` | execute | replacement execution's `operationID` | @@ -118,30 +112,23 @@ configuration values, never portable workspace identifiers. | `lithe_operation_output` | `operationID`, optional opaque `cursor` | read | state, nullable exitCode, bounded output and cursor | | `lithe_operation_stop` | `operationID` | execute | stop request for that exact execution | -Empty configuration paths restore automatic selection. macOS does not serve -`lithe_environment_inspect`, `lithe_environment_configure` or any `lithe_maven_*` -tool: toolchains and build tools belong to plugins there, so an agent reads and -sets their options through `lithe_build_inspect` / `lithe_build_configure` on the -plugin's Build instance, and native XML uses its explicit configuration -workflow. Windows still serves the environment tools, including `settingsPath` -and `localRepositoryPath`, which require a loaded Maven project. An open unsaved -settings draft or an in-progress save returns `BUSY`; the Agent cannot reset -that draft or save unrelated edits. Source read failures remain failures and -never become automatic tool selection. +The catalog has no language- or tool-specific capability. Toolchains and build +tools belong to plugins, so an agent reads and sets their options through +`lithe_build_inspect` / `lithe_build_configure` on the plugin's Build instance, +and a tool-native configuration file uses its explicit configuration workflow. Configuration writes use the existing platform stores; they are not a transaction across multiple settings documents. An error can follow a partial save, so inspect settings before retrying. Credentials, full launch environment values and command arguments are not included in run configuration -listing. Toolchain discovery uses existing installed tools; it does not install -or download a JDK or Maven. Maven execution may download dependencies or execute -plugins, and is therefore an execute capability. +listing. Build execution may download dependencies or execute project code, and +is therefore an execute capability. On macOS, build execution uses the enabled plugin's detected instance and task identities. Read them with `lithe_build_inspect`; model project, target and task selectors require a loaded, current model and are resolved again for each call. A model task also requires its declaring template and project. Arguments remain an array of tokens all the way to the Provider; the host does not reinterpret -Maven goals, construct a command string or use the old Maven execution service. +them or construct a command string. `lithe_build_inspect` also returns each option's ID, kind, effective value, source, choices and allowCustom, plus native-configuration availability. `lithe_build_configure` validates one value with the same current instance/model used by the native option @@ -155,15 +142,12 @@ stale Provider work and invalidate native drafts, models and language import. The same effective Build options, configured tool resolver and workspace Provider serve UI and Agent calls. An active Build execution returns `BUSY`. -macOS exposes the four plugin Build tools and removes all four Maven-specific -tools from discovery and admission. Windows connects Build inspect, reload, configure and -execute to its existing workspace-owned Provider controller, alongside the four -Maven tools whose current consumers are still being migrated. Detection reads +Both products expose the four plugin Build tools. Windows connects Build inspect, +reload, configure and execute to its existing workspace-owned Provider controller. Detection reads only admitted declarations and saved options. Product Build capability admission remains closed; exposing an implemented API does not admit a plugin requiring unimplemented capabilities. Windows configure shares the explicit native UI save path, persists machine-local workspace overrides, checks team/local source revisions and re-detects after publication. Null restores team/declaration inheritance; it does not edit a tool-native configuration file. -An unexposed call returns `NOT_SUPPORTED` before native execution. The complete -Maven project, settings and Run migration remains pending outside these Agent paths. +An unexposed call returns `NOT_SUPPORTED` before native execution. Windows requires a complete, current model before executing a task. Reload uses the existing model preparation and returns immediately; inspect reports its state. @@ -178,7 +162,7 @@ prevents new starts; already accepted work remains owned by the product workspac whose close/plugin lifecycle cancels it and waits for cleanup. Run operates on saved project configurations, not the currently focused editor -file. Existing Java build decisions are still shown in the IDE; the API never +file. Pending build decisions are still shown in the IDE; the API never silently answers them. Output is retained only as long as its existing IDE output slot: replacing a run, removing its tab, or closing the workspace may expire an operation. Callers receive `OPERATION_EXPIRED`, never another run's output. @@ -224,9 +208,9 @@ surface; it does not introduce a new plugin marketplace, sandbox or UI extension - `cargo test --manifest-path rust/Cargo.toml -p lithe-ide-host` - `cargo build --manifest-path rust/Cargo.toml -p lithe-ide-host --features mcp --bin lithe-mcp` - `node --test scripts/ide-mcp/test-ide-mcp.mjs` -- Windows: the host-api, Maven, run and project-environment frontend tests, plus a - native packaged run using the generated client configuration. +- Windows: the host-api and run frontend tests, plus a native packaged run using + the generated client configuration. - macOS: `IdeCapabilitiesTests`, followed by a packaged native run. -- Both platforms: configure JDK/Maven, reload, execute Maven, start a configuration, +- Both platforms: configure a plugin Build option, reload, execute a Build task, start a configuration, read incremental output, restart, reject the old ID, stop, revoke, and close the project. Confirm installed resource manifests/hashes remain unchanged. diff --git a/rust/contracts/maven-launch-context-v1.schema.json b/rust/contracts/maven-launch-context-v1.schema.json deleted file mode 100644 index 74d99573..00000000 --- a/rust/contracts/maven-launch-context-v1.schema.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://lithe.dev/contracts/maven-launch-context-v1.schema.json", - "title": "Lithe Maven Launch Context v1", - "type": "object", - "additionalProperties": false, - "required": ["version", "reactorPath", "profiles", "skipTests"], - "properties": { - "version": { "const": 1 }, - "reactorPath": { "type": "string", "minLength": 1 }, - "profiles": { - "type": "array", - "items": { "type": "string", "minLength": 1 } - }, - "settingsPath": { "type": ["string", "null"] }, - "localRepositoryPath": { "type": ["string", "null"] }, - "skipTests": { "type": "boolean" }, - "mavenExecutablePath": { "type": ["string", "null"] }, - "javaHomePath": { "type": ["string", "null"] } - } -} diff --git a/rust/contracts/maven-portable-configuration-v1.schema.json b/rust/contracts/maven-portable-configuration-v1.schema.json deleted file mode 100644 index 13c94584..00000000 --- a/rust/contracts/maven-portable-configuration-v1.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://lithe.dev/contracts/maven-portable-configuration-v1.schema.json", - "title": "Lithe Maven Portable Configuration v1", - "type": "object", - "additionalProperties": false, - "required": ["version", "selectedProfiles", "customProfiles", "skipTests"], - "properties": { - "version": { "const": 1 }, - "selectedProfiles": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "uniqueItems": true - }, - "customProfiles": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "uniqueItems": true - }, - "skipTests": { "type": "boolean" } - } -} diff --git a/rust/contracts/run-configuration-v1.schema.json b/rust/contracts/run-configuration-v1.schema.json deleted file mode 100644 index 6c09a68d..00000000 --- a/rust/contracts/run-configuration-v1.schema.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://lithe.dev/contracts/run-configuration-v1.schema.json", - "title": "Lithe Run Configuration Document v1", - "type": "object", - "required": ["version", "configurations"], - "properties": { - "version": { "const": 1 }, - "generator": { - "type": "object", - "required": ["fingerprint"], - "properties": { - "fingerprint": { "type": "string", "pattern": "^sha256:" }, - "inputs": { - "type": "object", - "propertyNames": { "type": "string", "minLength": 1 }, - "additionalProperties": { "type": "string", "pattern": "^sha256:" } - } - }, - "additionalProperties": false - }, - "configurations": { - "type": "array", - "items": { "$ref": "#/$defs/configuration" } - } - }, - "$defs": { - "configuration": { - "type": "object", - "required": ["id"], - "properties": { - "id": { "type": "string", "minLength": 1 }, - "name": { "type": "string", "minLength": 1 }, - "type": { "enum": ["java.current-file", "spring-boot.maven", "maven.module"] }, - "module": { "type": "string" }, - "mainClass": { "type": "string" }, - "toolchains": { "type": "object", "additionalProperties": { "type": "string" } }, - "workingDirectory": { "type": "string", "pattern": "^(?!/)(?!.*(^|/)\\.\\.(?:/|$)).*$" }, - "jvmArguments": { "type": "array", "items": { "type": "string" } }, - "programArguments": { "type": "array", "items": { "type": "string" } }, - "mavenProfiles": { "type": "array", "items": { "type": "string" } }, - "mavenSkipTests": { "type": "boolean" }, - "disabled": { "type": "boolean" } - }, - "additionalProperties": false - } - } -} diff --git a/rust/contracts/run-configuration-v2.schema.json b/rust/contracts/run-configuration-v2.schema.json index 057f1e45..e33c6077 100644 --- a/rust/contracts/run-configuration-v2.schema.json +++ b/rust/contracts/run-configuration-v2.schema.json @@ -24,41 +24,11 @@ }, "toolchain": { "type": "object", - "description": "Machine-local project toolchain defaults. Stored only in the local layer; non-empty per-configuration paths override matching defaults.", + "description": "Machine-local project toolchain state. Stored only in the local layer. Core reads and replaces only `preferences`; other fields belong to whichever host or plugin wrote them and are preserved unchanged.", "properties": { - "preferences": { "$ref": "toolchain-consumer-preferences-v1.schema.json" }, - "java": { - "type": "object", - "properties": { - "homePath": { "type": "string" } - }, - "additionalProperties": false - }, - "maven": { - "type": "object", - "properties": { - "executablePath": { "type": "string" }, - "javaHomePath": { "type": "string" } - }, - "additionalProperties": false - }, - "pathOverrides": { - "type": "object", - "description": "Machine-local subproject selections, keyed by normalized workspace-relative root. Empty fields inherit project defaults. Hosts preserve the map when saving individual run configurations.", - "maxProperties": 256, - "propertyNames": { "type": "string", "minLength": 1, "maxLength": 1024, "pattern": "^(?!/)(?!.*//)(?!.*/$)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))[^\\\\:\\x00-\\x1f\\x7f]+$" }, - "additionalProperties": { - "type": "object", - "additionalProperties": false, - "properties": { - "javaHomePath": { "type": "string", "maxLength": 4096 }, - "mavenExecutablePath": { "type": "string", "maxLength": 4096 }, - "mavenJavaHomePath": { "type": "string", "maxLength": 4096 } - } - } - } + "preferences": { "$ref": "toolchain-consumer-preferences-v1.schema.json" } }, - "additionalProperties": false + "additionalProperties": true }, "configurations": { "type": "array", @@ -235,13 +205,6 @@ "extensions": { "type": "object", "propertyNames": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }, - "properties": { - "java": { "type": "object", "properties": { - "build": { "$ref": "#/$defs/buildProjectReference" }, - "module": { "type": "string" }, - "mainClass": { "type": ["string", "null"] } - } } - }, "additionalProperties": { "type": "object" } }, "disabled": { "type": "boolean" }, diff --git a/rust/contracts/rust-core-api.md b/rust/contracts/rust-core-api.md index 0d33607b..37636689 100644 --- a/rust/contracts/rust-core-api.md +++ b/rust/contracts/rust-core-api.md @@ -219,7 +219,6 @@ const char *lithe_core_version(void); char *lithe_core_execute_json(const char *request); char *lithe_core_execute_json_with_events(const char *request, void (*callback)(const char *, void *), void *context); int32_t lithe_core_git_askpass(const char *prompt); -char *lithe_core_lsp_provider_catalog_json(const char *workspace_root); int32_t lithe_core_cancel(const char *operation_id); void lithe_core_free_string(char *value); void lithe_core_install_log_waker(void (*callback)(void *), void *context); @@ -229,49 +228,10 @@ int32_t lithe_core_remove_log_waker(void *context); The macOS package uses the small C bridge in `macos/Sources/LitheRustCore/`. The canonical C declarations are in `rust/lithe-core/include/lithe_core.h`. Native clients can link the same `staticlib` or `cdylib`; Rust hosts call -`lithe_core::execute_json` and `lithe_core::cancel_operation` directly. Hosts -call `lithe_core::execution::plan_launch_command` (or the -`execution.planLaunchCommand` JSON command) before spawning a Java process. The -request fields are `executable` (required, non-empty launcher path), `arguments` -(string array, excluding the executable), `javaFeatureVersion` (optional -integer), `javaReleaseContents` (optional string array of host-read `release` -file texts, nearest first), and `commandLineLimit` (optional byte budget; default -30719, which is 32767 - 2048). An explicit `javaFeatureVersion` takes precedence -over the contents. Core reads the first entry containing `JAVA_VERSION=`; an -unrecognized value is unknown and does not fall back to a farther entry. The -planner changes a launch only when the executable file name is `java`, `java.exe`, -`javaw` or `javaw.exe`, the estimated command line exceeds the limit, the JDK -feature version is known and at least 9, and a path-list option (`-cp`, -`-classpath`, `--class-path`, `-p` or `--module-path`, in separate or `=` form) -precedes the application target. Planning stops at the application target (main -class, `-jar`, or `-m`/`--module`); program arguments stay verbatim. The response -`data` is `{ arguments, argumentFile }`. `arguments` is the request list unchanged -when no file is needed. Otherwise the element at `argumentFile.argumentIndex` is an -empty placeholder that the host replaces with `@`, and -`argumentFile` is `{ text, argumentIndex }`; it is `null` when the launch is -unchanged. `lithe_core::execution::java_feature_version_from_release` reads the -feature version from one release file text. -Core owns the Unicode argument-file text and quoting. The Windows host encodes -that text losslessly using the launcher's actual system code page, independently -of the JDK feature version: JEP 400 does not make native launcher arguments UTF-8. -An unrepresentable path is reported as an actionable failure, never substituted. -The host also escapes backslash bytes introduced by multibyte encoding inside -quoted values, since the native argument-file parser processes bytes. -Every execution owns an exclusively created temporary file; partial writes and -spawn failures clean it up, while successful launches retain it until that exact -process exits. A replacement execution never shares its predecessor's file. -The `plan_classpath_jar_launch` fallback for known JDK versions older than 9 is not -yet provided: there is no Rust API or JSON command for it. Those launches stay -direct, so an oversized Windows JDK 8 command line is not shortened yet. The macOS -process argument limit (`ARG_MAX`) is far above the Windows cap, so a direct JDK 8 -launch already succeeds there. +`lithe_core::execute_json` and `lithe_core::cancel_operation` directly. Strings returned by the core are UTF-8 JSON allocated by Rust. The caller must release response strings with `lithe_core_free_string`. -Rust native adapters may call `execution::java_feature_version_from_release` to read the -JDK feature version from bounded host-read `release` metadata, then reuse Core's -runtime candidate ordering. Passive Build discovery never falls back to running -Java when that metadata is missing or invalid; explicit tool execution owns probes. ### Localized action search `search.actions` accepts `{query, candidates: [{texts: [string]}]}` and returns @@ -380,79 +340,6 @@ subsystem: `mcp.parseConfiguration`, `mcp.parseRegistry`, `mcp.featuredRegistry` `rust/fixtures/mcp` and `rust/contracts/mcp-registry-page.json`. The IDE-facing MCP server (`lithe-mcp` plus `rust/fixtures/ide-api/mcp-protocol.json`) is unaffected and stays in `lithe-ide-host`. -### Maven test results - -`maven.testResults` accepts `{ "root": string, "output": string }` and parses -the bounded text emitted by Maven Surefire or Failsafe after a JUnit 4/5 class -or method run. `root` must be an existing workspace directory and `output` is -limited to 500,000 characters. The response is: - -```json -{ - "testsRun": 4, - "failures": 1, - "errors": 1, - "skipped": 1, - "passed": 1, - "success": false, - "failureDetails": [ - { - "name": "additionIsCorrect(com.example.CalculatorTest)", - "kind": "failure", - "message": "expected <4> but was <5>", - "path": "src/test/java/com/example/CalculatorTest.java", - "line": 42, - "column": null - } - ] -} -``` - -`kind` is `failure` or `error`; `path` is a workspace-relative source path -when a stack frame or failure footer can be resolved unambiguously. Relative -source lookup requires a complete workspace index, prefers a unique full -package-path suffix at a path boundary, and falls back to a unique filename -only when no package-path candidate exists. Ambiguous matches or an incomplete -scan (including the 10,000-directory limit) leave the location `null`. -All locations use one-based lines with nullable columns. `passed` is derived from the summary and never -negative. A final `Results` summary is preferred; when Maven only prints -per-class summaries, the counts are aggregated. Failure details retain Maven's output order and are bounded to -10,000 entries. A parser or size violation returns the standard -`parse_failed` error. - -The request may also carry `reports: { module, sourcePath, classes, -notBeforeMillis }` naming the run's workspace-relative Maven module directory -(`null` or `.` for the root), the binary names of the selected test classes, and -the run's start time in Unix milliseconds. When `module` is absent and -`sourcePath` names a workspace-relative test file, the module is the nearest -directory above that file holding a `pom.xml`. An empty or absent `classes` -reads every report in the module written at or after `notBeforeMillis`. Core then reads the Surefire/Failsafe XML reports -the run wrote and returns `testCases`, otherwise an empty list: - -```json -{ "className": "com.example.CalculatorTest", "method": "additionIsCorrect", - "status": "failed", "message": "expected <4> but was <5>", "invocations": 1, - "durationMillis": 23 } -``` - -Optional `durationMillis` comes only from each XML testcase's `time` seconds attribute. Repeated/parameterized invocations sum their times in whole milliseconds; if any invocation has a missing, negative, non-finite, unrepresentable time or the sum overflows the host integer range, the field is omitted. A reported zero is valid. Hosts hide missing method durations and must not substitute a console/suite summary or another class's method time. `build.decodeTestResults` preserves the same duration in its canonical testcase result. - -Reports are searched in the module's configured Surefire/Failsafe -`reportsDirectory` values plus `/surefire-reports` and -`failsafe-reports`. Only `TEST-.xml` and nested `TEST-$*.xml` -files modified at or after `notBeforeMillis` are read, so an earlier run's -reports never produce outcomes. `method` drops parameter lists and invocation -indexes; invocations that report a display name instead of a method are -omitted. `status` is `passed`, `failed`, `error`, or `skipped`; when a method -has several invocations, the most severe wins (error, failed, passed, skipped) -and `message` comes from the first invocation with that status. Results are -ordered by `className`, then `method`. Core reads at most 512 reports of up to -32 MiB each and returns at most 10,000 methods; malformed reports are skipped. -A module outside the workspace or a class name that is not a Java binary name -returns `invalid_request`. A class with no case means "no recorded outcome", -never "passed". Platform stores must associate the response with the -launch operation and discard it after cancellation, replacement, or workspace -change. Workspace paths in responses are relative and use `/` separators. Line numbers are one-based. `git.status.repositoryRoot` may be an absolute path when the @@ -534,7 +421,7 @@ the source as `originalPath`; platform mutations that act on the Git entry pass both paths back to Core. The core rejects absolute paths and `..` traversal for file commands. Native file dialogs, file watching, PTY/ConPTY, -Java processes, and runtime discovery remain platform adapters. +and runtime discovery remain platform adapters. `git.history.recentReferences` contains at most five existing local branches in most-recently-used order. The current branch is first. Core derives checkout @@ -1319,7 +1206,8 @@ a filter (omit it to show every commit), `displayMode` is `compact` (default) or `expanded`, and `currentBranch` asks for the ancestry of that local branch. Hosts call it once per loaded window, never per commit, and recompute the whole window when a page is appended or the history refreshes; lanes depend on every loaded edge, and a head -fragment's color ID is the Java `String.hashCode` of its principal reference name, so a +fragment's color ID is the wrapping 32-bit hash `h = 31 * h + unit` over the UTF-16 +units of its principal reference name (the value IDEA's `GraphColorManagerImpl` uses), so a named branch keeps its color across pages. The response is `{ "version", "rows", "laneCount", "recommendedLaneCount", "hasMissingParents", "currentBranchHashes" }`. Each visible row carries `commitHash`, integer `lane` and `laneCount`, a signed @@ -1714,9 +1602,8 @@ visibility. Names share index invalidation and reported file updates; changes inside unwatched generated directories require workspace/index refresh. No index files or resources are written to disk. macOS Search Everywhere requests pages of 50 matches per source; legacy synchronous consumers retain their requested caps. -Java type and method results still use the existing syntax index and include a -one-based line, `symbolName`, and source line in `preview`; language-provider -workspace-symbol integration and external-library search are not implied. +Name search does not read language syntax: language-provider workspace-symbol +integration and external-library search are not implied. The shared name/range examples are in `rust/fixtures/search/name-matching.json`. `workspace.replacePreview` accepts `root`, `query`, `replacement`, the same @@ -1903,7 +1790,7 @@ The `debug.*` commands are the shared Debug Adapter Protocol boundary. Rust owns DAP framing, request sequences, response correlation, initialization and execution state, deterministic breakpoint sets, and normalized thread, stack, scope, variable, evaluation, output, stop, continue, and termination events. -Platforms own adapter discovery, JDT LS activation, sockets or process pipes, +Platforms own adapter discovery, sockets or process pipes, native process termination, persistence, and UI rendering. `debug.validateAdapterEndpoint` accepts exactly `{ host, port }`. `host` is a @@ -2227,10 +2114,10 @@ native clients must not treat its `source.path` as a workspace file. Exception information is available only while paused and normalizes the exception type, description, break mode, optional -stack trace, evaluation name, and nested exception details. The Java adapter -currently supplies the type, description, and break mode but no expandable -exception object reference, so native clients continue to inspect ordinary -frame scopes for local state. +stack trace, evaluation name, and nested exception details. An adapter that +supplies the type, description, and break mode without an expandable exception +object reference leaves native clients to inspect ordinary frame scopes for +local state. Terminal operation events are exactly one of `operationCompleted` with a typed result or `operationFailed` with the adapter command and safe message. Other ordered events are `stateChanged`, `initialized`, `output`, `stopped`, @@ -2253,7 +2140,7 @@ absent, Core derives DAP `terminateDebuggee` from the session's request kind: use `false`. A provided value applies to `launch` sessions only, so a user can keep a local debuggee running. An `attach` session never terminates the external process, even when `terminateDebuggee: true` is sent. This prevents a remote -detach from killing a JVM the IDE does not own. +detach from killing a process the IDE does not own. The compatibility cases are in `rust/fixtures/debug/disconnect-policy-v1.json`. The platform keeps the socket or process alive long enough to flush the frame, then closes it and @@ -2310,21 +2197,14 @@ the Project view's natural file-name order. Severity is the LSP number; missing or out-of-range values are unknown (`0`) and rank last. Cases: `rust/fixtures/lsp/problems-query-v1.json`. -The LSP provider catalog is returned by `lithe_core_lsp_provider_catalog_json`. -Each provider descriptor may include `languageServerLaunch` with ordered -`executableNames` and `arguments`; Swift adapters use this metadata when they -need to discover a real language-server executable; the selected launch plan is -then submitted to the Rust-owned runtime. Built-in descriptors are merged by provider ID with the optional -`.lithe/lsp/language-providers.json` workspace document. See -[`.agents/notes/implemented/architecture/2026-09-13-language-tooling-and-lsp-runtime-ownership.md`](https://github.com/1lck/Lithe-IDEA/blob/preview/.agents/notes/implemented/architecture/2026-09-13-language-tooling-and-lsp-runtime-ownership.md) -for routing, discovery, lifecycle, and compatibility rules. +Core ships no built-in language-provider catalog. Every language server comes +from a plugin's `languageServers` declaration, which Core validates and plans. The `lsp.*Server`, `lsp.*Document`, `lsp.request`, `lsp.pollEvents`, and `lsp.waitEvents` commands are the semantic LSP runtime boundary. `lsp.startServer` accepts the provider ID, selected executable/arguments/environment, root URI, working -directory, initialization options, optional runtime executable, -`jdtlsLaunchResources`, cache directory, `workspaceFingerprint`, and the +directory, initialization options, and the optional `pluginId` of the plugin that owns the process, its optional `workspaceConfiguration` (an object, plugin-owned sessions only), its optional `hostServices` (the launch plan's `backend.hostServices`; @@ -2333,117 +2213,8 @@ its optional `hostOptions` (plugin-owned sessions only; `editor:{actionIcons, actionSourceBytes, providerTypes}` declares the icons, collector source bound and provider types the host implements, reported through `lithe/host/getCapabilities` to sessions granted the matching editor methods), plus -initialize, post-initialize readiness, request, Java project build -(`javaBuildTimeoutMilliseconds`), and shutdown deadlines. -Java callers may also provide the versioned `mavenContext` accepted by -`maven.launchPlan`. Core validates its reactor and recursively declared modules, -publishes the user-level settings through -`java.configuration.maven.userSettings` and the selected installation's -`conf/settings.xml` through `java.configuration.maven.globalSettings`, and, -after `ServiceReady`, sends one -`java.project.updateSettings` command per Maven project with -`org.eclipse.m2e.core.selectedProfiles`. Maven Java, test, and generated source -roots are normalized to workspace-relative `java.project.sourcePaths` during -the same configuration flow, so JDT LS receives the selected reactor's source -model without platform-specific POM parsing. Both settings documents are passed as content-addressed copies inside the -session's JDT LS state directory (`/.lithe/maven/`). The user-level copy -comes from `settingsPath`, else Maven's default `~/.m2/settings.xml`, else an -empty document when only `localRepositoryPath` is set, and carries that local -repository override. JDT LS detects settings changes by comparing paths, so a -content change must always produce a new path. An unreadable document is passed -by its original path with a session warning instead of failing startup. -Copies remain readable until JDT workspace cache eviction or index rebuilding; -notification delivery is not an acknowledgement that the server read them. - -Gradle callers may also provide `javaGradle = { wrapperPresent, offline?, userHome? }`, where -`wrapperPresent` reports whether the root ships -`gradle/wrapper/gradle-wrapper.properties`. When present, Core states -`java.import.gradle.enabled` in `settings.java` and, if that descriptor exists, -`java.import.gradle.wrapper.enabled`. Both leaves equal JDT LS's own defaults, -so the section declares the import behavior Lithe relies on instead of changing -it; Buildship itself decides which distribution to use, honoring the wrapper only -when the project ships that descriptor. Settings that require reading the build -script (`java.home`, `version`) are never guessed. `offline` and `userHome` -are the user's own build options, as `build.languageImport` maps them from the -plugin that declares the Gradle build system; Core sends -`java.import.gradle.offline.enabled` and `java.import.gradle.user.home` only -when they are set, and the session keeps them until it restarts. A workspace -whose root declares no Gradle build file sends no `javaGradle` and therefore -receives no `java.import` settings. - -`lsp.updateMavenConfiguration` accepts `{ sessionId, mavenContext, -reloadProjects? }` for a running Java session started with a `mavenContext`, -and returns `{ settingsChanged, projectsReloaded, profilesUpdating }`. When the -settings copies differ from the ones JDT LS holds, Core sends -`workspace/didChangeConfiguration` and JDT LS force-updates every Maven project -itself. When they are unchanged and `reloadProjects` is `true`, Core sends -`java/projectConfigurationsUpdate` for the reactor's project URIs, which -re-resolves dependencies even though no `pom.xml` changed. Changed profiles -restart the profile task once the session is ready. Before the `initialized` -handshake, the new configuration replaces the one the pending settings -notification sends. Explicit reloads received before `ServiceReady` are coalesced -and sent once projects are ready, even when settings are unchanged. Response -booleans describe actions sent immediately, not queued work. A newer profile -selection is applied after the preceding batch terminates, including failed -batches; timed-out requests must all drain before that follow-up can start. -The same failed selection is not automatically retried. -A stopped or failed session returns `invalidRequest`. -Resolution problems are not part of the response; JDT LS reports them as -`pom.xml` diagnostics. - -Maven profile application is a -bounded background task: at most eight project commands are in flight, remaining -projects are queued, and each project reports `running`, `succeeded`, or -`failed` with optional error details. Project results use a redacted stable -`projectUri` identifier; they never expose the user's absolute workspace path. -The runtime event also carries the aggregate Maven task status (`running`, -`succeeded`, `partiallySucceeded`, `failed`, `timedOut`, or `cancelled`) so hosts -do not need to infer task completion from log text. A project failure or task timeout does not -terminate an otherwise usable JDT LS session; the host receives a partial-failure -event and may retry. The session reaches `ready` after JDT LS `ServiceReady`. -Core only accepts retries for a ready Java session. A timeout sends `$/cancelRequest` -but retains each in-flight slot until its terminal response arrives. Retry is -rejected while the previous batch is still stopping; if JDT LS never responds, -the user must restart the Java session. Late responses release those slots -without changing the timed-out results. -Hosts reset project results on the structured `mavenProfileTask: "running"` -event and consume `mavenProfileProject` updates directly, scoped to the current -session. Java import completion and Maven task completion use separate UI -notifications so service readiness cannot overwrite a Maven failure. -The session continues to expose profile progress independently. `initializeTimeoutMilliseconds` -only bounds the standard LSP handshake. For a provider such as JDT LS that has -a later readiness signal, -the profile task records a deterministic digest of Maven settings, selected -profiles, project URIs, and source paths; an unchanged successful digest skips -reapplying the same settings, while an explicit retry invalidates that digest. -Hosts may consume lifecycle events for the shared `serverConnected`, -`projectImporting`, `profileApplying`, and `fullyReady` phases. -`serviceReadyIdleTimeoutMilliseconds` retains its wire name but now sets the -quiet-progress **warning** threshold (45 seconds by default). A quiet interval -emits one warning with the last progress snapshot; changed progress rearms the -warning, while duplicate progress does not. JDT progress is not a heartbeat: -silence must not fail the session or terminate its process. -`serviceReadyAbsoluteTimeoutMilliseconds` remains the sole post-initialize -readiness deadline (10 minutes by default), regardless of progress activity. `jdtlsLaunchResources`, when present, -contains `launcherJarPath`, `configurationDirectory`, `lombokAgentPath`, the -legacy optional `javaDebugBundlePath`, and ordered -`javaExtensionBundlePaths`. It is valid only for the Java provider and requires -`runtimeExecutablePath`. Rust loads the legacy Debug bundle first when present, -then appends the extension bundle paths with stable de-duplication. Rust -then uses `runtimeExecutablePath` as the process executable and constructs the -complete deterministic JDT LS JVM argument list. `configurationDirectory` names -the packaged, read-only configuration; Rust never passes it to Equinox, which -writes framework state into its `-configuration` directory. Rust copies the -directory's `config.ini` into -`cacheDirectory/jdtls-configuration//configuration`, -rewrites a missing or damaged copy, and passes that directory instead. A -`cacheDirectory` that resolves inside the JDT LS installation, including -through a symbolic link, fails with `invalid_request` before anything is written; -a missing `config.ini` fails with `process_start_failed`. Areas of other digests -unused for the JDT cache retention period are removed after the area is -prepared, and a removal failure is logged without failing the start. When the structured object is -absent, the selected `executablePath` and legacy wrapper arguments remain the -compatibility path. Rust owns the returned +initialize, request, and shutdown deadlines. +Rust owns the returned session's child process, stdin/stdout/stderr, framing buffer, JSON-RPC request IDs, document versions, pending deadlines, capabilities, diagnostics, and graceful/forced termination. @@ -2456,115 +2227,6 @@ remains incomplete. The original startup error is preserved. Native owners using the in-process registration callback retain their registered identity even when startup returns an error and retry `lsp.destroyServer` themselves. -JDT LS remains `initializing` until `language/status: ServiceReady`. During this -phase Rust reduces changed `$/progress` notifications into throttled JSON log -details containing the current phase, percentage, project, observed project -count, artifact name, repository host, downloaded/total bytes, calculated -throughput, elapsed/idle durations, and cache disposition. Progress parsing is -observability-only and never substitutes for `ServiceReady`. Absolute readiness failures use `serviceReadyTimeout` at stage `serviceReady` -and retain the final diagnostic snapshot in `underlyingMessage` with -`timeoutKind: "absolute"`. Classification still describes observed silence or -transfer activity; it does not prove a JVM deadlock. Quiet warnings preserve the -initializing/preparation state. Explicit server errors and exits still fail -immediately. Shared timing examples live in -`rust/fixtures/lsp/jdt-readiness-v1.json`. - -Platform adapters own filesystem discovery and validate that packaged JDT LS -contains the Equinox launcher, platform configuration directory, Lombok agent, -Java Debug Server, and bundled Java. Java Test-capable hosts additionally -validate their extension bundles and runner. They do not construct JVM commands. -Packaged macOS and Windows plans always use structured direct launch, so runtime -startup has no shell, PowerShell, or user-`PATH` dependency. Wrapper launch -remains optional only for external or older plans. - -For JDT LS, platform adapters observe which root Maven/Gradle descriptors -exist, names of direct Maven module directories, the Gradle wrapper's -`distributionUrl` when the workspace has one, and the selected JDT LS version. They submit those raw observations to `java.jdtWorkspaceFingerprint`; -Rust Core validates, sorts, de-duplicates, and constructs the sole portable -fingerprint representation. Core then hashes the normalized workspace identity -followed by a null separator and that opaque fingerprint to select -`cacheDirectory/jdtls/`. -Omitting the fingerprint preserves the legacy path-only key for older clients. -Changing structure selects a new directory without deleting the old one, so a -later switch back can reuse it. Editing a descriptor is not a structural change: -JDT LS compares each POM and Gradle file's timestamp with the state it saved and -updates only the changed projects on restart, and running sessions receive the -edit through `lsp.workspaceFilesChanged`. Keying the directory on descriptor -timestamps would discard the whole index after any touch of the file. The Gradle -version is the exception: JDT LS does not check the wrapper descriptor, so the -wrapper's `distributionUrl` takes part in the fingerprint and a version change -selects a fresh directory. - -Core starts JDT LS with -`-Djava.import.generatesMetadataFilesAtProjectRoot=false`, so the Eclipse -project files JDT LS maintains (`.project`, `.classpath`, `.factorypath`, and -`.settings/*.prefs`) live in that state directory instead of the user's -modules. Before launch, Core removes such files that earlier versions left in a -Maven or Gradle module directory when the enclosing Git repository does not -track them; tracked files and workspaces outside Git are left alone because JDT -LS keeps honoring files that already exist at a module root. Tracking is decided -by the repository that owns each module, located through its `.git` entry, with -one batched `git ls-files` query per repository; modules outside Git start no Git -process. When any file is removed, Core also deletes the current state directory -so the launch imports the modules afresh instead of reusing a model that points -at the removed files, and records the removed workspace-relative paths in the -session's `info` log event. - -`java.jdtWorkspaceFingerprint` accepts -`{ buildFiles, directMavenModules, gradleWrapperDistribution?, jdtlsVersion }`. -Each build-file observation contains only a workspace-relative `path`; -`gradleWrapperDistribution`, when sent, must not be blank. It returns `{ workspaceFingerprint }`; platforms must not recreate -or parse this opaque string. Compatibility cases are in -`rust/fixtures/lsp/jdt-workspace-fingerprint-v1.json`. - -`lsp.jdtWorkspaceKey` accepts `{ workspaceRoot, workspaceFingerprint? }` and -returns `{ workspaceKey }` through the same normalization and SHA-256 algorithm -used by `lsp.startServer`. Platform cache-maintenance actions use it to remove -only the current workspace/fingerprint directory; they do not clear sibling -workspaces or older structural states. - -`java.runMarkers` accepts `{ mainMethods, testItems, testCases }`: the -`methods` of one file's `javaMainMethods` answer, the `items` of its -`javaTestItems` answer (empty when the platform cannot run tests for the file), -and canonical build-provider report cases (`suite`, `case`, `status`). The older -`className`/`method` field names remain accepted for Windows compatibility. -It returns `{ markers }` ordered by -zero-based `line`, then `kind` (`main`, `testClass`, `testMethod`). `endLine` -is the last line of the declaration as reported upstream: the end of the body for -test classes and methods, and the name line for `main`. Each marker -has a `label` for menus (`App.main()`, `OrderTest`, `OrderTest.creates`), the -launch target (`mainClass`/`projectName`, or `testClass`/`testMethod` and the -Java Test `testItemId`), and `status`: `none`, `passed`, `failed` (failure or -error), or `skipped`. Test cases match a method by class and method name, with -nested-class `$` and `.` treated alike. A class is `failed` when any recorded -method under it (including nested classes) failed and `passed` when at least -one passed and none failed. Items without a source range, such as inherited -test methods, get no marker. The command is pure and never reads the file -system. `rust/fixtures/java/run-markers-v1.json` is the compatibility fixture. - -`java.workspacePolicy` accepts `workspacePaths` and `changedPaths` as -workspace-relative paths. It starts Java tooling when a non-ignored `.java` -source exists and the workspace shows evidence of being a Java project: a build -descriptor (`pom.xml`, `build.gradle[.kts]`, `settings.gradle[.kts]`, a Maven or -Gradle wrapper) no more than two directories below the root, or a `.java` source -no more than two directories below the root for projects that have no build -system. A Java sample or fixture checked into a repository of another ecosystem -therefore does not activate Java tooling; hosts still start a language server on -demand when the user opens a `.java` file. The command chooses one deterministic -representative source and classifies changes as `ignored`, `source`, -`buildConfiguration`, or `other`. The compatibility examples are in -`rust/fixtures/lsp/java-workspace-policy-v1.json`. - -`java.jdtCacheRetention` accepts platform-observed cache directory metadata as -`{ nowUnixSeconds, activeWorkspaceKey?, entries }`. Each entry contains a -lowercase 64-character SHA-256 `workspaceKey` and -`lastModifiedUnixSeconds`. Core ignores invalid candidate names, de-duplicates -observations using the newest timestamp, never selects the active key, and -returns deterministically sorted `expiredWorkspaceKeys` older than the fixed -30-day retention period. Platform adapters own the last-used marker, directory -enumeration, revalidation, deletion, and error logging; Core performs no cache -filesystem I/O. See `rust/fixtures/lsp/jdt-cache-retention-v1.json`. - `lsp.documentIdentity` accepts `{ workspaceRoot, input, platform }` with the same `posix | windows | auto` path syntax. It returns `{ document: { path, relativePath } | null }`. `input` may be a file URI, native absolute path, or workspace-relative path. @@ -2586,8 +2248,8 @@ separator, and percent-encodes reserved characters and UTF-8. Invalid paths return `invalid_request`. See `rust/fixtures/lsp/document-uri-v1.json`. The `rootUri` of `lsp.startServer` and the `uri` fields of `lsp.syncDocument`, -`lsp.saveDocument`, `lsp.closeDocument`, `lsp.request`, `java.navigationMarkers`, -`java.resolveNavigation`, and each `lsp.workspaceFilesChanged` change accept +`lsp.saveDocument`, `lsp.closeDocument`, `lsp.request`, and each +`lsp.workspaceFilesChanged` change accept either an existing URI string or `{ path, platform }`. The related diagnostic location URIs in `lsp.request` accept the same shape. Core converts path inputs inside the existing request; virtual URI strings and opaque server data are not @@ -2625,8 +2287,8 @@ operation. The host flushes current editor text before the save, including edits made while the native write was in flight. Before launch, hosts retry save notifications for clean open buffers and propagate transport failures instead of treating disk persistence alone as launch readiness. -This is distinct from `didChange`: JDT reloads its on-disk working copy on save, -which makes a subsequent Java launch build observe saved dependency edits. +This is distinct from `didChange`: a server that keeps on-disk working copies +reloads them on save, so a later build observes the saved edits. Shared cases: `rust/fixtures/lsp/document-save-v1.json`. `lsp.workspaceFilesChanged` accepts a session ID and ordered file URI changes @@ -2635,24 +2297,6 @@ whose `kind` is `created`, `changed`, or `deleted`. It emits one versioned `lsp.syncDocument`; adapters must not duplicate those edits as watcher events. -`java.navigationMarkers` accepts -`{ sessionId, operationId?, uri, documentVersion? }` and completes with -`{ documentVersion, markers }`. Rust combines JDT LS implementation CodeLens, -`textDocument/implementation`, and `java/findLinks` results with parser-selected -Java declaration candidates. Work is capped at 64 semantic tasks, individual -task failures preserve other verified markers, stale document versions cancel -the batch, and the latest completed version is cached per URI. Markers are -sorted by line, UTF-16 column, and relation. A marker contains `direction` -(`up` or `down`) and `relation` (`interface` or `inheritance`); zero-target -declarations are omitted. The cross-platform examples are in -`rust/fixtures/lsp/java-navigation-v1.json`. - -`java.resolveNavigation` accepts the marker position, direction, relation, and -document version. Downward markers use `textDocument/implementation`; upward -markers use JDT LS `java/findLinks` with `superImplementation`. Its terminal -result is `{ documentVersion, locations }`, using the same normalized physical -and virtual-location representation as ordinary navigation. - `lsp.buildRequest` accepts `{ sessionId, operationId?, method, params, timeoutMilliseconds? }` and returns `{ operationId }`. `method` and `params` are the `provider` field of a `build.modelPlan`, `build.dependencyPlan` or @@ -2879,7 +2523,7 @@ implementation, rename, formatting, range formatting, document symbols, code act inlay hints, full-document semantic tokens, folding ranges, document links (`documentLinks`, result `{ links: [{ range, target, tooltip }] }`; `target` is null until a server supplies it, and entries without a range are dropped), workspace -symbols (`workspaceSymbols`, see below), code lens, provider virtual documents, `javaEntrypoints`, `javaTestItems`, and `javaMainMethods`. +symbols (`workspaceSymbols`, see below), code lens, and provider virtual documents. For code actions, `diagnostics` contains the original `LspClientDiagnostic` objects returned by Core: nullable integer `severity`, `tags`, and `relatedInformation` remain intact, and every range uses zero-based `line` / @@ -2887,102 +2531,11 @@ objects returned by Core: nullable integer `severity`, `tags`, and not a diagnostic request model. Non-LSP markers may request actions with a range and an empty diagnostic context. The round-trip fixture is `rust/fixtures/lsp/diagnostic-code-actions-v1.json`. -`javaEntrypoints` invokes Java Debug Server's -`vscode.java.resolveMainClass` for the session workspace and returns schema -version 1 with deterministic workspace-relative `{ sourcePath, mainClass, -projectName? }` entries plus diagnostics for unusable upstream records. -Discovery queries all entries in the owned JDT session, then filters source -paths against the opened workspace and its resolved location. This includes -plain folders represented by invisible projects in JDT's writable cache; -unrelated workspace sources remain excluded. Discovery -waits behind pending Maven profile updates and JDT project-configuration jobs, -using the same configuration barrier as the following Java build. Up to 128 -waiting discovery operations are allowed per session. Their deadlines include -configuration waiting and the eventual upstream query, bounded by the session's -Java build timeout. Cancellation before dispatch completes that operation without -writing a JSON-RPC discovery request or cancellation; shutdown/failure settles -every waiter. A completed profile update must precede discovery of source roots -introduced by that profile. - -`javaTestItems` requires a file URI, invokes Java Test's -`vscode.java.test.findTestTypesAndMethods`, and returns schema version 1 with a -typed class/method tree. Each item carries the upstream identity, label, fully -qualified name, project, test kind/level, optional JDT handler and sort text, -optional zero-based UTF-16 range, and children. A `null` upstream result means -the file has no tests (Java Test leaves its root's children unset) and yields an -empty item list; any other non-list top-level result is an -`invalidServerResult`, never an empty semantic answer. -`javaMainMethods` requires a file URI, invokes Java Debug Server's -`vscode.java.resolveMainMethod`, and returns schema version 1 with -`{ methods: [{ range, mainClass, projectName? }], diagnostics }`. `range` is the -zero-based UTF-16 range of the method name, and methods are ordered by source -position. `mainClass` matches the `javaEntrypoints` entry generated for the same -file, so editors can launch the workspace configuration for that entry. A `null` -upstream result means no launchable method; any other non-list result is an -`invalidServerResult`. Core does not infer Java entry points or tests from -source syntax. -For the Java provider, an `executeCommand` whose command is -`vscode.java.buildWorkspace` is coordinated by Core instead of being written -immediately. Core writes it only when no earlier build is awaiting its JDT -response, the Maven profile task is not `running`, and no JDT work-done -progress job named `Update project …`, `Updating project configurations`, -`Applying the selected build files…`, or `Updating workspace folders` is open; -a job without progress for 120 seconds stops blocking. Identical queued build -commands share one JDT request, while a running build is never shared. Each -caller is bounded by `javaBuildTimeoutMilliseconds` from `lsp.startServer` -(default 600000), measured from submission; its `requestTimeout` error has stage -`javaBuild` and an `underlyingMessage` naming the reached phase -(`building`, `waitingForPreviousBuild`, `waitingForMavenProfiles`, or -`waitingForProjectConfiguration`). Cancellation or timeout sends -`$/cancelRequest` only when no running or queued caller still needs a build, -and the build keeps its slot until JDT answers. A successful build completes -with the unchanged `{ value: 1 }` result. Other JDT `BuildWorkspaceStatus` -values complete with stage `javaBuild` and code `javaBuildCompilationErrors` -(`WITH_ERROR`), `javaBuildFailed` (`FAILED`), `javaBuildCancelled` -(`CANCELLED`), or `invalidServerResult`. `javaBuildCompilationErrors` and -`javaBuildFailed` additionally carry `javaBuildReport` with `markerScope` -(`launchTarget` or `workspace`), `builderFailedEarlier`, -`elapsedMilliseconds`, and `recovery` (`none` or `rebuildJavaIndex`). The marker -scope is inferred from the dispatched command: `launchTarget` means the request -named a project, not that Core observed Java Debug Server's final project -selection. Elapsed time is evidence only and must not become a heuristic gate. -The versioned examples are in -`rust/fixtures/lsp/java-build-report-v1.json`. - -Hosts treat these two terminal build outcomes as evidence rather than an -irrevocable launch veto. After resolving the usable runtime paths, they may let -the user continue that same launch attempt without issuing another build. A -cancelled, timed-out, rejected, or unrecognized build has no usable verdict and -must be retried instead of overridden. Hosts present Core's message and report -instead of inferring a cause. Core logs `Java project build is waiting` (with `reason`), -`Java project build started`, and `Java project build finished` (with -`outcome`, `errorCode`, `elapsedMilliseconds`, and `waiterCount`). -Background build retries and deadline cancellations are written by a separate -session-owned worker. The deadline monitor never writes to stdin. A background -write that exceeds `requestTimeoutMilliseconds` fails the session with -`transportFailed` at stage `outboundMaintenance` and terminates the server to -release the stalled pipe; the Java build deadline still bounds queue and build time. -`java.prepareLaunch` accepts `{ sessionId, operationId?, sourcePath, mainClass? }`. -`sourcePath` is the absolute saved source path at the native boundary. -Core owns one operation across JDT entry-point discovery, the existing serialized -build coordinator, and classpath resolution. The source must be inside the session -workspace; an optional configured class disambiguates multiple entries in that -exact file (including JDT's module prefix). Core does not infer entry points or paths. -Discovery and classpath each use the session request deadline; the build retains -its independent queue-inclusive deadline. The host fallback deadline covers all -three phases plus event-delivery grace. - -Only the final `requestCompleted` event is published to the host, with either -`{ kind: "ready", target: { mainClass, projectName?, modulePaths, classPaths } }` -or `{ kind: "buildFailed", target, failure: { code, message, report } }`. -Only the two terminal build failures above can produce the latter. -Cancellation, timeout, invalid replies and transport failures remain operation -errors. Cancelling the operation cancels its current phase; a late build answer -retains the existing coordinator semantics and never starts classpath resolution. -Runtime path groups must contain nonblank strings, with at least one path overall. -Hosts save documents before submitting and own the final launch decision, JDK -discovery and native process. Run plan/toolchain selection remain in existing Core -operations. See `rust/fixtures/lsp/java-launch-preparation-v1.json`. + +Deadline cancellations are written by a separate session-owned worker. The +deadline monitor never writes to stdin. A background write that exceeds +`requestTimeoutMilliseconds` fails the session with `transportFailed` at stage +`outboundMaintenance` and terminates the server to release the stalled pipe. The `semanticTokens` operation uses the open document URI and normal version, timeout, and cancellation rules. Its result is @@ -3087,138 +2640,22 @@ newLineCount, additions, deletions }`. `rows` are side-by-side `added`, each changed block listing its removals before its additions. See `rust/fixtures/diff/compare-texts-v1.json`. -`maven.scan` accepts `{ "root": string, "paths"?: string[] }` and returns -`null` when neither the root nor the supplied visible workspace-relative paths -contain a readable `pom.xml`. Candidates are tried in shallowest-first order, -with `/`-normalized lexical paths breaking ties, until one parses successfully; -a malformed candidate does not hide a valid nested project. A project response -contains its workspace `relativePath`, -`groupId`, `artifactId`, `version`, `packaging`, recursive `modules`, `profiles`, -and `hasWrapper`. The root project and every recursive module also contain a -`sourceRoots` list. Each entry has a module-relative `/`-normalized `path` and -a `kind` of `mainJava`, `mainResources`, `testJava`, `testResources`, -`generatedMain`, or `generatedTest`. Standard Maven roots are returned before -their directories exist; explicit `` source/resource directories and -`maven-compiler-plugin` generated-source directories or -`build-helper-maven-plugin` source lists replace the corresponding defaults, -whether configured directly on the plugin or within an execution. -`${project.build.directory}` resolves from `` and defaults to -`target` only when that element is absent; unresolved or invalid explicit -values do not silently fall back. -Absolute, unresolved-property, and parent-traversal paths are omitted so one -module cannot claim another module's source root. Entries are de-duplicated and -ordered by the documented kind order, then path. Aggregator-only `pom` modules -have no default roots. Module paths are relative to the selected Maven root and -use `/` separators. Malformed XML returns `parse_failed`. Source-root examples -are in `rust/fixtures/maven/source-roots-v1.json`. - -`maven.launchPlan` accepts a workspace `root`, a versioned `context`, an -optional reactor-relative `module`, and an ordered `goals` array whose first -entry is a lifecycle or custom goal. Later entries may be ordinary Maven CLI -arguments such as `-Dname=value` or `-q`. They remain separate process arguments -and are never interpreted by a shell. Context version 1 contains the -workspace-relative `reactorPath`, -selected `profiles`, optional platform-local `settingsPath` and -`localRepositoryPath`, `skipTests`, and optional Maven/JDK paths used only for -the configuration fingerprint. The response contains the `project-maven` -toolchain reference, an argument array, the workspace-relative reactor working -directory, and a deterministic SHA-256 configuration fingerprint. Profiles are -sorted and de-duplicated. Module plans from `maven.launchPlan` and Maven-backed -Run and Debug plans use `-pl -am`. Settings use `-s`; a local -repository override uses `-Dmaven.repo.local=`; skipped tests use -`-DskipTests`. Explicit Run `cwd`, Profiles, and `extensions.maven.skipTests` -values override the project context, including `skipTests: false`. The core -never reads `settings.xml` and never copies its path into a portable project -document. Maven itself continues to read `.mvn/maven.config`; the plan does not -expand or duplicate that file's arguments. Fixtures are in -`rust/fixtures/maven/launch-plan-v1.json`. - -`maven.dependencyPlan` accepts the same workspace `root`, versioned `context`, -optional reactor-relative `module`, and a required absolute `outputFile`. It -returns a launch plan for the fixed `maven-dependency-plugin:3.8.1:tree` goal -that writes the verbose text tree to `outputFile` in UTF-8 with standard tree -tokens, disabled color, and an English locale. Exactly one project runs because -every project in the session would overwrite the same file: module queries use -`-pl ` without `-am`, and reactor-root queries use `-N`. The read-only -query does not build reactor dependencies. Platform adapters own the child -process, apply a bounded timeout, and keep it independent from an ordinary Maven -build session. They also own `outputFile`: each invocation receives a fresh -path in a platform scratch directory, and the platform removes it after the -result, cancellation, timeout, or failure. The process's console output is log -text only and is never parsed as dependency data. - -`maven.dependencies` accepts `{ "modulePath": string, "outputFile": string }` -after the plan's process exits successfully and returns the normalized module -path plus a recursively nested `dependencies` array. Each node contains -`modulePath`, `groupId`, `artifactId`, `version`, `type`, nullable `classifier`, -`scope`, `resolution`, nullable `selectedVersion`, nullable -`premanagedVersion` and `premanagedScope` (values before dependency -management), nullable `originalScope` (declared scope before mediation widened -it), nullable `ignoredScope` (a wider scope mediation did not apply), and -`children`. Resolution is `resolved`, `omittedDuplicate`, or `omittedConflict`; -`selectedVersion` is the winning version of an omitted conflict. Every level is -sorted deterministically. - -The first line of the file must name the module and every later line must be a -node with only the annotations the pinned plugin writes; any other content, -invalid UTF-8, a line over 4 KiB, more than 10,000 nodes, 64 levels, or a file -over the byte limit derived from those bounds returns `parse_failed` instead of -a partial tree. A missing file returns `process_failed`. The compatibility -fixture is `rust/fixtures/maven/dependency-tree-v2.json`. - -`maven.diagnostics` accepts `{ "root": string, "output": string }` and returns -`{ "issues": [] }`. Diagnostic paths may be absolute or workspace-relative; -the response preserves the path text, uses one-based line and column values, -and normalizes severity to `error` or `warning`. Duplicate issue lines are -removed deterministically. - -`runConfig.selectJava` uses the toolchain requirements of the workspace's latest -`runConfig.generate` in this process; before any generation it derives them from -the project's own declarations (`pom.xml`, `.java-version`, the Maven wrapper). -Its request contains optional -`root`, `candidates` (`id`, probed `version`, numeric source `priority`), and -`fallbackId`. IDs are opaque machine-local identities, never persisted or opened -by this operation. Lower priority wins; equal-priority candidates use descending -numeric Java versions (including legacy `1.8`), then ascending ID. - -When `project-jdk.minimumVersion` exists, selection first filters using the same -Java version comparison as run-configuration diagnostics. Among the compatible -candidates, the one whose Java feature version equals the declared one wins, else -the oldest newer feature version; source priority only breaks ties within a feature -version. A Java 8 project therefore runs on an installed JDK 8 even when `JAVA_HOME` -points at JDK 21, matching IDEA and OpenIDEA. The response is -`{ id, warning }`: the compatible candidate, or the supplied usable fallback -with an actionable warning if none qualifies. Missing requirements retain the -platform's unconstrained choice; a malformed or unsupported `requirements` -snapshot returns the parse/version error. Explicit configured paths bypass automatic -selection. The operation never generates requirements or probes executables. -The cross-platform examples are in -`rust/fixtures/run-configuration/automatic-java-selection.json`. - -Windows `run_resolve_toolchains` includes an optional `warning` on resolved -JDKs, including inherited Maven JDKs. This warning remains visible even when no -run configuration exists to carry a scoped `toolchainVersionMismatch` diagnostic. - `runConfig.projectToolchain` reads `{root,path,localDocument?,configurationId?}` and returns -`{toolchain:{javaHomePath,mavenExecutablePath,mavenJavaHomePath,runtimeExecutablePaths}}`, even without +`{toolchain:{runtimeExecutablePaths},toolPreferences?,definedTools?}`, even without generated Run configurations. `path` is the current Provider's normalized, -workspace-relative project/reactor root (`.` for the workspace). The nearest -`toolchain.pathOverrides` entry owns all three fields; its empty/missing fields -inherit workspace defaults, not another ancestor override. Matching uses path -segments, so `app` never owns `app2`. Malformed stored paths/documents fail -without changing source bytes. The command does not discover, probe or run any -tool, inspect project descriptors, or write files. `runtimeExecutablePaths` -comes from the existing `.lithe/toolchains/local.json`; platforms resolve the selected -paths through their existing toolchain adapters. Shared examples are in -`rust/fixtures/run-configuration/project-toolchain-selection.json`. -When `configurationId` is supplied, explicit Java/Maven paths from that current -Run configuration override the canonical project/reactor selection. Core reuses -the existing team/local configuration merge without projecting global defaults -as explicit configuration choices. Empty configuration fields inherit the -canonical selection; missing, disabled, mismatched-root or malformed choices fail -instead of falling back. No generated layer is required for an explicitly saved -Provider reference. Shared cases are in -`rust/fixtures/run-configuration/configuration-toolchain-selection.json`. +workspace-relative project root (`.` for the workspace). `runtimeExecutablePaths` +maps each runtime ID in the existing `.lithe/toolchains/local.json` to its saved, +non-empty `executable`; platforms resolve the selected paths through their +existing toolchain adapters. `toolPreferences` echoes the local document-level +`toolchain.preferences`. When `configurationId` is supplied, Core reuses the +existing team/local configuration merge without projecting the global defaults +as explicit configuration choices. The configuration must be enabled and, when +it carries an `extensions.build` reference, belong to the provider instance +rooted at `path`; a `build.provider` configuration then reports its active +`extensions.build-run.tools` choices as `definedTools`. Missing, disabled, +mismatched-root or malformed choices fail instead of falling back, and malformed +stored paths or documents fail without changing source bytes. The command does +not discover, probe or run any tool, inspect project descriptors, or write files. `runConfig.inspect` also returns the local document-level `toolchain`, including when no generated configuration exists (`status: "missing"`). Settings, @@ -3227,14 +2664,14 @@ toolchain-only callers, and initial run-panel presentation may send project sources. Omission preserves full inspection. The Windows run panel publishes readable configurations first, then performs full inspection and reports freshness failures without discarding those configurations. -The input fingerprint covers what generation reads: build and tool files and -the sources of generated Java entries by content (`sha256:` in -`generator.inputs`), and every other Java source by path only (`path`), so -editing a class body is not a staleness. When the stored inputs no longer +The input fingerprint covers what generation reads: the manifests the built-in +detectors consume (package and lock files, Compose files, Procfiles, task-runner +files and runtime version files) by content (`sha256:` in +`generator.inputs`), plus the detector revision. When the stored inputs no longer reproduce the stored fingerprint, the document came from another generator revision and the diagnostic says so instead of listing modified files. -Automatic run discovery, fingerprint traversal, Node manifest discovery and the -Windows Java source list use Core's shared scan policy. Known caches/build outputs +Automatic run discovery, fingerprint traversal and Node manifest discovery use +Core's shared scan policy. Known caches/build outputs are pruned before descending; other hidden source directories remain visible and symlinks are not followed. Failure to read the workspace root is an error. An unreadable optional child or fingerprint input is recorded in @@ -3243,109 +2680,48 @@ hashes beneath those unknown paths. Inspection returns `incompleteProjectScan` instead of claiming fingerprint freshness or inferred removals when either the current scan or saved baseline is incomplete. The persisted diagnostic also appears with `checkFingerprint: false`; a successful complete regeneration clears it. -This does not replace declared build-system modules or JDT's entrypoint facts. -The bounded Java source list stops opening further directories once its source -limit is reached, retains only collected Java paths, and returns them sorted. -Full fingerprint scans remain uncapped by this list limit and keep reporting -unreadable inputs independently. - -`runConfig.inspect` also accepts optional schema-versioned `javaEntrypoints` -from the current `lsp.request` result, independent of `checkFingerprint`. Core -then compares JDT's `(sourcePath, mainClass)` pairs, with a `module/` prefix -removed and nested checkouts excluded, against the generated Java entries and -reports a difference as a `staleFingerprint` diagnostic. Platforms send it -after a project load once the Java service has prepared the project, never -start that service for this check, and skip it while a regeneration already -waits for the service. Project environment saves use the existing local `runConfig.updateOptions` toolchain payload with an empty `configurationId`; service overrides are untouched. The `runConfig.*` commands implement the versioned project protocol described by the JSON Schemas in this directory. `runConfig.inspect` accepts `root` and -never writes files. `runConfig.generate` accepts `root`, relative Java `paths`, -relative `modulePaths`, optional schema-versioned `javaEntrypoints` from the -current `lsp.request` result, and optional `projectOwnership`. The latter is a -host projection of current enabled Provider projects: `{ path, -name?, build?: { pluginID, buildSystemID, root, projectID }, minimumJavaVersion?, contentRoots: [{ path, kind }] }`, using workspace-relative -paths and the model's `source`/`testSource`/generated-source roles. Core validates -project/root containment, unique project paths, roles and bounds (2,000 projects, -10,000 content roots). When supplied, including `[]`, these facts own Java -project membership and source sets: `modulePaths` and ancestor POM inference -do not supplement them. `build` is an exact current model identity, not a build -semantic tag. Generated Java entries store it under `extensions.java.build`, -with Java-owned `module` and `mainClass`; they contain no Maven extension or -Maven tool binding. Its root stays independent of editable cwd and document -layers cannot replace generated build ownership. An omitted snapshot retains the current -Windows generator's legacy ownership path. A supplied ownership snapshot disables -legacy Maven framework detection. Framework selection is not an ownership field; -run discovery uses the independent generic snapshot described below. -`minimumJavaVersion` is an optional normalized Java feature version (1–999), -owned by the upstream compiler model, including before entrypoint discovery -returns any sources. When the snapshot is supplied, Core uses -the highest known project floor and never parses POM compiler properties; a -platform Java version file can raise but cannot lower that floor. Missing means -unknown, not permission to infer. Build-tool versions and wrapper paths are not -ownership fields. With provider ownership Core emits no Maven requirement and -never reads wrapper properties or scripts to supplement it. Installed tool -definitions/providers own these choices. Native Java preparation routes by the -exact enabled package, declaration, instance and project identity. Legacy -consumers that omit provider ownership retain their old Maven launch path until -they migrate; those fields are not accepted in the new ownership projection. See `fixtures/run-configuration/provider-project-ownership.json`. -The generated document records optional `generator.providerFingerprint`, a -SHA-256 identity of the validated projection, with projects and roots sorted. -It covers project labels, ownership, source roles and requirement -facts, but never environment values or native configuration secrets. -`runConfig.inspect` accepts the same optional `projectOwnership` and emits -`staleProviderModel` when current facts differ or a legacy generated document -has no provider identity. `checkFingerprint: false` skips file hashing only; -this model comparison remains active. Explicit `[]` represents disabled/missing -providers, while omission preserves existing Windows callers. A return to the -same semantic facts matches again; asynchronous host publication still needs -its independent monotonic operation revision to reject ABA replies. +optional `checkFingerprint`, `localDocument` and `providerRuns`, and never +writes files. `runConfig.generate` accepts `root`, optional `providerRuns` and +an optional `cacheDirectory`, and returns `{ generated, toolchainRequirements, +entryCount }`. `runConfig.generate` and `runConfig.inspect` also accept optional `providerRuns`: a complete list of `{projectPath,name,reference}`. The native host matches current model targets against enabled public run declarations; `reference` has exactly the -`extensions.build` selector shape below, with empty model `userArguments`. This -snapshot is independent of Java entrypoints and language imports. Core validates +`extensions.build` selector shape below, with empty model `userArguments`. Core validates at most 10,000 unique selectors, provider-root/project containment and bounded labels, then creates `build.provider` entries without interpreting target IDs, tool names, frameworks, commands or debug adapters. An explicit `[]` withdraws generated runs; -omission preserves older callers. It also disables the legacy framework detector. +omission preserves older callers. IDs hash plugin/build/root/run/project/target identity, so changed labels or execution kind retain user overrides. Current declarations still validate the execution kind before preparation. `generator.providerRunFingerprint` hashes the complete sorted snapshot; changed or missing metadata yields `staleProviderModel`, including when file fingerprinting is disabled. Withdrawn entries cannot be restored from cached configurations; unmatched overrides stay in their user-owned layer and are diagnosed. -See `fixtures/run-configuration/provider-project-ownership.json`. macOS supplies this +See `fixtures/run-configuration/build-provider-stable-identities.json`. macOS supplies this snapshot; Windows retains its existing generator until its Provider host is migrated. -Java configurations with the same class and module name in independent reactors -remain distinct; a common reactor retains its existing configuration IDs. - When Java tooling is still preparing, omission -retains the previous generated entrypoint facts instead of running a local -scanner or replacing them with an empty list. Those previous facts come from -this process's last generation, else from the optional `cacheDirectory` (an -application cache root where generation records its result under -`run-configurations/`), else read-only from a `.lithe/run/generated.json` that -older versions wrote. It returns the generated configuration and toolchain -requirement documents and keeps them in memory as the workspace's detected -layer for `inspect`, `resolve`, and `selectJava`; nothing is written into the -workspace. `inspect` returns `requiresGeneration: true` while this process has +Generation keeps the generated configuration and toolchain requirement +documents in memory as the workspace's detected layer for `inspect` and +`resolve`; nothing is written into the workspace. With `cacheDirectory` (an +application cache root outside the workspace) it also records the result under +`run-configurations/`, so retained entries keep their IDs and the Run list is +complete before providers answer again; failing to read or write that cache +changes nothing else. The previous generation, used only to retain entries +beneath unreadable paths, is this process's last generation, else the cached +result, else read-only a `.lithe/run/generated.json` that older versions wrote. +`inspect` returns `requiresGeneration: true` while this process has no detected layer. Hosts identify the workspace when this flag is true or the status is `missing`; a saved provider task can make `status` be `ready` before -detection, and must not suppress discovery of the remaining entries. Without -`projectOwnership`, legacy Maven root -discovery checks `pom.xml` along each supplied path's ancestor chain, so a - -reactor nested below the opened workspace does not depend on the platform -including build descriptors in `paths`. Maven ownership is resolved per Java -entry: standalone sources keep the JDK launch path, while entries from -independent nested reactors retain their own reactor working directory and -module selector. Generated fingerprints include both project inputs and the -detector revision; either changing marks the detected layer stale and requires -regeneration. +detection, and must not suppress discovery of the remaining entries. Generated +fingerprints include both project inputs and the detector revision; either +changing marks the detected layer stale and requires regeneration. `runConfig.decideTitleAction { action, trigger, currentFileChosen, settled, configuration? }` decides what a press on the title bar's Run or Debug button does. `action` is @@ -3392,20 +2768,6 @@ states, the effective global `toolchain`, and the machine-local `localToolchains` document. Toolchain diagnostics carry the affected run configuration ID when a requirement is consumed by one or more configurations; requirements with no configuration consumer do not emit a blocking diagnostic. -For detected Maven configurations, resolved `extensions.maven.reactorPath` -contains the workspace-relative reactor from the generated layer, independently -of an overridden effective `cwd`. Core derives this read-only ownership value -when resolving existing generated documents as well; regeneration is not -required. Overrides cannot move a configuration to another reactor. Current -File and configurations without detected Maven ownership omit this field. -Resolution checks that `extensions.maven.module` exists relative to this -reactor, or relative to `root` when the field is absent, and never relative to -an overridden `cwd`: setting a module's own directory as the working directory -keeps the configuration available. -Module menus first match reactor and module, then apply the default preference; -they must not infer ownership from an overridden working directory. The shared -`run-configuration/maven-module-ownership.json` fixture covers independent -reactors, cwd overrides, and the ordinary Java main / Current File capabilities. Each configuration carries a `category` of `project` or `infrastructure`. Docker Compose detections are `infrastructure`: a Compose file in an application @@ -3421,32 +2783,24 @@ selection filtering are pending there. Display names that repeat are qualified by Core, because hosts show the name alone: the first candidate that separates every entry in the group wins, trying -the Maven module, then the working directory, then the source manifest. Three +the working directory (when it is not the workspace root), then the source +manifest. Three Compose files each declaring `compose up` become `compose up (script/docker)` and so on, while a name that occurs once is never decorated. Ids are unaffected. -Java entry points are read from the Java syntax tree rather than matched as -text, so a `static void main` or `@SpringBootApplication` inside a string -literal or comment — common in test fixtures and documentation samples — does -not become a run configuration. A declared `main` under `src/test` remains a -valid entry and keeps the test classpath; see -`rust/fixtures/execution/maven-java-main-source-sets-v1.json`. - A process detector declares a runtime binding only when that command genuinely consumes the runtime. npm, pnpm, and Yarn scripts consume `project-node`; Bun scripts keep their independent `bun` command and do not acquire a Node -requirement. Go, Python, Cargo, and Gradle remain command-based until every host +requirement. Go, Python, and Cargo remain command-based until every host provides the corresponding configurable runtime registry, so their PATH-based launch behavior is not blocked by an unavailable platform selector. During resolution, Core also reconciles npm, pnpm, and Yarn commands from older v2 generated documents with the same `project-node` binding. This compatibility normalization is based on the effective command, does not mutate the stored document, and keeps legacy hybrid projects scoped without requiring regeneration. -A document-level `toolchain` -object in the local layer (e.g. -`{ "java": { "homePath": ... }, "maven": { "executablePath": ..., "javaHomePath": ... } }`) -provides defaults for every configuration's `extensions.java.*`. A non-empty -per-configuration toolchain path overrides the corresponding project default. +The document-level `toolchain` object in the local layer holds generic +`preferences`; other fields there belong to whichever host or plugin wrote them, +and Core preserves them unchanged. Provider applications can be explicitly saved with `runConfig.createUserConfiguration` using `{root,scope:"local",name,type:"buildProvider",build:{pluginID,buildSystemID, @@ -3524,37 +2878,20 @@ before process launch, and an invalidated configuration or operation rejects lat `runConfig.updateOptions` and `runConfig.createUserConfiguration` are pure document transformations. They validate scope, paths, supported types, stable -IDs, main classes, modules, and argument parsing, then return UTF-8 JSON in the +IDs, provider run options, and argument parsing, then return UTF-8 JSON in the `document` field. The platform adapter selects the target project or local file and performs the atomic write. These commands never write files. An empty `workingDirectory` removes the layer's `cwd` override. A non-empty value must name an existing directory inside `root`, given relative to it or as an absolute path, and is stored project-relative. Values are literal paths; editor variables such as `${workspaceFolder}` are rejected rather than stored, because -resolution disables and omits a configuration whose `cwd` does not exist. Optional -`mavenSkipTests` writes `extensions.maven.skipTests`; omission removes the -override so the project Maven context is inherited, while explicit `false` -continues to run tests even when the project default skips them. -For project-scoped option updates, selected toolchain paths must resolve inside -`root` and are persisted with `/`-separated project-relative paths. Local-scoped -updates may carry host absolute paths. `runConfig.updateOptions` and -`runConfig.inspect` accept the same optional `localDocument` override. -When `updateOptions` carries a `toolchain` object (`javaHomePath`, -`mavenExecutablePath`, `mavenJavaHomePath`), it writes the document-level -global toolchain into the local layer instead of patching a configuration; -project scope rejects this payload because toolchain paths are machine-local. - -The project toolchain may also carry `pathOverrides`, a map of normalized -workspace-relative subproject roots to `{ javaHomePath?, mavenExecutablePath?, -mavenJavaHomePath? }`. The map has at most 256 entries; roots reject absolute, -parent, dot, empty-segment, drive, backslash and control-character forms. Empty -fields inherit project defaults. This map lives only in `.lithe/run/local.json` -and is returned by inspection/resolution; hosts preserve it when saving a run -configuration or project defaults. An absent map identifies a document predating -subproject migration; a saved empty map is an explicit choice. Old platform -preferences are copied and read back before cleanup, with already saved choices -remaining authoritative. See `rust/fixtures/run-configuration/subproject-toolchains.json`. - +resolution disables and omits a configuration whose `cwd` does not exist. +`runConfig.updateOptions` and `runConfig.inspect` accept the same optional +`localDocument` override. When `updateOptions` carries a `toolchain` object +(`{ runtimeExecutablePaths?, preferences? }`), it replaces only the +document-level `toolchain.preferences` in the local layer instead of patching a +configuration, preserving every other document-level toolchain field; project +scope rejects this payload because toolchain choices are machine-local. `execution.resolveSearchPath` accepts already-split `existing` directories and `candidates` carrying `priority`, opaque `id`, and `path`; lower priority sorts @@ -3570,8 +2907,8 @@ expansion or execution. Empty arguments remain explicit. The shared cases are in `rust/fixtures/run-configuration/argument-text.json`. `runConfig.saveEditorChanges` accepts the normal option-edit payload plus the -required `toolchain` object. In addition to Java and Maven paths, that object -may contain `runtimeExecutablePaths`, keyed by stable generic toolchain ID. It +required `toolchain` object. That object may contain `preferences` and +`runtimeExecutablePaths`, keyed by stable generic toolchain ID. It applies the global toolchain and configuration override edits together, returning `localDocument`, either a `projectDocument` string or `null`, and either a `toolchainDocument` string or `null`. The latter updates @@ -3579,9 +2916,7 @@ either a `toolchainDocument` string or `null`. The latter updates an entry when its supplied executable path is empty. Local scope combines the run-option edits in the local run document. Project scope returns the local defaults and team options as separate fully prepared documents; the platform -adapter writes all returned documents as one transaction with rollback. Empty -per-configuration toolchain paths remove the -corresponding override keys while preserving unrelated extension fields. +adapter writes all returned documents as one transaction with rollback. Platform clients report the editor save as successful only after the written documents resolve again. Failures identify whether preparation, document writing, or post-save reload failed; a reload failure keeps the last usable UI @@ -3597,177 +2932,20 @@ selected Node installation. They do not fall back to a PATH shim from another installation, and Windows executable extensions take precedence over extensionless shell scripts. -Automatic Spring service generation requires both an applied Maven plugin and -exactly one JDT-confirmed, Spring-classified non-test entry in the same module. -An inherited plugin alone cannot promote a library to a runnable service. -Nested-module entries do not promote their parent. Ambiguous, utility and test -mains remain independently selectable Java entries. Module assignment consumes -the confirmed source set even when the caller supplies no editor paths. -Absent discovery may reuse previously confirmed entries; an explicit empty result -removes automatic services. Existing service IDs and user-authored Maven-goal -compatibility configurations retain their established meaning. - -`runConfig.createLaunchPlan` accepts `root`, `configurationId`, optional -`currentFile` and `classPath`, optional `debugPort`, and optional -`localDocument`. Maven-backed Run and Debug callers may also supply the same -versioned `mavenContext` accepted by `maven.launchPlan`. Explicit profiles in -the resolved Run Configuration replace the context profiles; otherwise the -project profiles are inherited. Explicit `extensions.maven.skipTests` and -`cwd` values also replace the context values. Core applies the shared settings, -module, Skip Tests, and reactor-working-directory rules to the generated -framework arguments, including `-am` for selected reactor modules. A -project-owned `java.main` caller, or a `spring-boot.maven` caller whose generated -configuration records a Java entry source, instead supplies `javaLaunch` with -the exact JDT LS-selected `mainClass` plus structured `classPaths` and -`modulePaths`. An optional `javaLaunch.buildPrepared: true` is a host receipt -that the current build provider resource task completed before JDT launch -resolution. Core then omits its legacy Maven resource pre-launch step and does -not inspect POMs for that step. The host must recheck workspace/configuration -generation and cancellation before handing off the launch; it cannot reuse a -receipt from another operation. Missing/false retains compatibility for current -Windows callers. See `fixtures/run-configuration/provider-prepared-java.json`. Core then produces a direct `project-jdk` launch; it never -projects those Java entry points into a reactor-wide Maven launch goal. A Spring -Boot configuration without a resolved Java entry source retains its Maven-goal -compatibility path. It -returns a toolchain -reference, argument array, project-relative working directory, and structured -environment references. It does not return a shell command or platform -executable path. All project paths use `/`, reject absolute paths and `..` -traversal, and remain relative to `root`. - -The plan may also carry three optional envelope fields. `preLaunchSteps` is an -ordered array of `{ executable, arguments, classpath?, workingDirectory? }` steps -the host runs to completion, in order, before the main process; a non-zero exit -aborts the run and surfaces that step's diagnostics. Each step's `executable` -reuses the plan's -`{ toolchain }` shape plus an optional `tool` selector (`"javac"` resolves the -sibling compiler in the toolchain's `bin` directory; absent means the default -launcher). `classpath` is a structured array of project-relative or host-absolute -entries the host joins with the platform path separator (`:` on POSIX, `;` on -Windows) and prepends as `["-cp", joined]` to the relevant argument list; core -never joins classpath entries because the separator is platform-specific. -`modulepath` follows the same rule and is emitted as `--module-path` by the -host. A JDT main identity in `module/name.Type` form is projected as -`-m module/name.Type` for the direct Java launcher. Empty fields are omitted, -so existing single-process Maven, Gradle, and Node -plans are unchanged. A step shares the plan-level `environment` and, unless it -declares its own project-relative `workingDirectory`, the plan-level -`workingDirectory`. A step that declares one resolves its toolchain from that -directory and runs there, so a resource step still finds the project wrapper -next to its reactor POM when the application working directory is a user -override. - -Direct Maven-project Java launches also carry a `project-maven` resource step: -`resources:resources`, plus `resources:testResources` for test-source entrypoints. -The step inherits Maven profiles, settings, repository and module/dependency -selection. Its absolute `-f` POM argument anchors the generated reactor even when -the application working directory is overridden, and its own `workingDirectory` -names that reactor as the step's run directory and toolchain resolution root. A -child POM not declared in -that reactor is processed as its own Maven project, without `-pl`. Resource -filtering and custom resource directories remain Maven-owned; no Java compilation -or application launch goal is added. Hosts must finish these steps successfully -before starting the JVM. Both hosts scope pre-launch execution to the -window/session/execution and stop the owned process tree on Stop, window close or -replacement. They apply the same ten-minute deadline with bounded pipe draining -and cleanup, and a step that reaches its deadline fails the run with -`Pre-launch step timed out after 600 seconds.`. macOS runs the steps before both -the application launch and a Run-panel service session, and cancels a session's -running step when that session stops, restarts, or is dropped by reconciliation. -Independent DAP launches that do -not consume this plan are not covered by this resource-step contract. - -A Maven-project `java.main` launch must first ask JDT LS/Java Debug -Server to resolve the exact source target, build its owning project, and return -the runtime classpath/module path. Missing project launch metadata is a launch -error rather than permission to fall back to `mvn exec:java`; reactor-wide Exec -would attempt the same main class in parent and dependency modules. - -A `java.main` configuration without a -Maven toolchain launches through `project-jdk` and the configuration's Java -source path only when that source has no Maven ancestor. An older configuration -that omitted the Maven binding is rejected with an instruction to regenerate. -Standalone Java (`java.main` without Maven, and `java.current-file`) compiles -before running: core emits a `javac` pre-launch step writing `.class` files to -`.lithe/run/classes/`, puts that directory on the run -`classpath`, and launches by the qualified class name rather than the source -file. This keeps one-click Run working on JDK 8, which lacks the JEP 330 -single-file source launcher (`java File.java`) that only exists on JDK 11+. The -`java.current-file` main class is derived from the file's `package` and declared -class; when the host supplies a project `classPath`, the compiled output leads -the run classpath and the project classes feed the compile step. - -`java.codeVision` accepts a workspace root, a target Java path, and Java source -paths. It returns declaration locations and usage counts; Git blame attribution -is joined by the UI from the shared Git result. `java.className` accepts Java -source text and a file simple name and returns the fully qualified runtime class -name. - -`java.sourceDefinition` accepts `source`, `declarationName`, an optional -`memberName`, an optional `declarationsOnly`, and an optional `reference` -(`line`, `utf16Column`), returning zero-based `line` and UTF-16 `utf16Column` or -`null` when no declaration is found. A member is looked up as a syntax-tree -method, field, or enum-constant declaration that the `declarationName` type -declares directly. With `reference`, the member is instead resolved from that -position: the identifier must be a plain name or a `this.` access, the innermost -type is searched first and then the enclosing ones, and the result is `null` when -a type on the way may inherit the name or a local may shadow a field. -Without `declarationsOnly`, text that merely resembles a declaration, such as a -call, is accepted next, and then the `declarationName` type; with it, a missing -member returns `null`. - -`java.structure` accepts Java `source` and optional `declarationSources`. It -returns `foldRegions`, `inlayHints`, and `syntaxHighlights`. -Fold and inlay line numbers are zero-based because these values are editor -offsets; UTF-16 columns and hidden ranges match the native text editor coordinate -system. Syntax highlights contain document-relative `utf16Start`, -`utf16Length`, and a role from the shared editor syntax-theme contract. They -are sorted and non-overlapping, so native renderers can apply semantic colors -without maintaining another Java parser. The parser is platform-independent -and does not start a Java process or contact JDT. - -`spring.index` accepts `root`, workspace-relative `paths`, optional trusted -absolute `metadataRepositories` (and the legacy singular `metadataRepository`), -optional `textOverrides` keyed by relative path, and -`refreshDependencyMetadata`. The command reads Spring configuration -metadata from workspace JSON files and dependency JARs, indexes application -configuration documents and Java source, and returns deterministically ordered -`properties`, `values`, `propertyReferences`, `diagnostics`, `beans`, -`injections`, and `endpoints` collections. Locations use relative paths and -one-based lines and columns. - -`properties` include type, documentation, default value, and an optional Java -declaration. `values` include profile/override state and an optional declaration -target. `propertyReferences` represent Java `@Value` uses. Bean resolution -accounts for component names, `@Bean` aliases, interfaces, `@Qualifier`, -`@Resource`, `@Primary`, field injection, and constructor injection. Endpoint -entries expand multiple controller/method paths and retain the exact declared -HTTP method set. - -Dependency metadata is cached in the Rust process. Project-open indexing sets -`refreshDependencyMetadata` to `true`; debounced unsaved-buffer indexing leaves -it `false`, so editing Java or configuration files does not repeatedly traverse -and open the local dependency repository. The repository path is selected by -the platform composition layer and is never persisted in shared results. - -`mybatis.index` accepts `root`, workspace-relative `paths`, and optional -`textOverrides` keyed by relative path. It pairs Java mapper types with XML -`` documents and returns only statements that have both a -Java method and a matching XML `select`/`insert`/`update`/`delete` `id`. -Results are deterministically ordered by namespace, statement id, XML path, -and line. Locations use relative paths and one-based lines and columns. -`javaLine`/`javaColumn` point at the method name; `javaEndColumn` is the -exclusive UTF-16 column after that name. `javaEndLine` is the signature -terminator. `xmlLine`/`xmlColumn`/`xmlEndColumn` bound the statement `id` -value the same way. Hosts intercept go-to-definition only when the caret -is inside those name ranges; return types and parameters keep LSP -navigation. Java methods are collected from `tree-sitter-java` syntax -nodes, so nested generics, split signatures, and commented-out methods -are not mistaken for declarations. XML comments are ignored. Methods with -a method body, including `default` methods, are omitted from the Java -side of the index. Paths are indexed only when they are regular `.java` -or mapper `.xml` files no larger than 2 MiB; `pom.xml` and other -extensions are skipped before content is read. +`runConfig.createLaunchPlan` accepts `root`, `configurationId` and optional +`localDocument`. It resolves the configuration through the same layers and +rejects a missing or disabled one. Core assembles only process-based plans: a +configuration with a bare program-name `command` returns +`executable: { command }`, which the host resolves on PATH; one that transfers +executable ownership to a runtime toolchain (`toolchains.runtime`) returns +`executable: { toolchain }`, which the host resolves from its toolchain +registry. Any other configuration, including a `build.provider` reference, is +`invalid_request`: ecosystem launch semantics stay with the plugin that +declared the run. The plan also returns the string argument array, the +project-relative `workingDirectory`, an empty `environment` reserved for +toolchain-derived values, and the configuration's literal `env`. It does not +return a shell command or platform executable path. All project paths use `/`, +reject absolute paths and `..` traversal, and remain relative to `root`. `diagnostics.redactText` accepts `text` and returns `redacted` with credentials, tokens, and home-directory paths replaced by stable placeholders @@ -4501,8 +3679,9 @@ entry?, runtimeArguments, arguments }` and returns `{ status: "ok", launch: params }`, where `params` are those of `lithe/host/run/start` as the backend sent them. It returns `{ status: "ok", start }` with `start` being `{ label, executable: { kind: "toolchain" | "packaged", value }, arguments, -workingDirectory, environment, temporaryFiles: [{ path, contents }], debug?, -rerun? }`. The tool must be listed in the manifest's `requiresTools`, and the +workingDirectory, environment, temporaryFiles: [{ path, contents } | { path, +base64 }], debug?, rerun? }`. A text file is written as UTF-8; a `base64` file +holds bytes the host decodes and writes unchanged. The tool must be listed in the manifest's `requiresTools`, and the working directory must be a relative path of an existing directory inside `workspaceRoot`, which Core checks read-only the way it does for run preparation. `${temporaryFile:}` in arguments and environment becomes a file path under @@ -4575,12 +3754,12 @@ failure is terminal. There is no separate `configuredExecutable` result. See the plugin contract's **Shared executable candidates** section for ordering and strict selection. -Host-managed runtime probes use `java -version` (including Java 8), -`node --version`, and `go version`. Hosts execute the returned arguments and -pass combined stdout and stderr as `build.checkToolVersion.request.output`: -Java normally prints its version to stderr. Core normalizes legacy Java -`1.8.0_…` output to major version 8 before comparing the declared minimum, -and ignores `Picked up …` environment-option notices preceding the version. +Host-managed version probes run the arguments of the tool definition's +`version` declaration. Hosts execute them and pass combined stdout and stderr +as `build.checkToolVersion.request.output`, because some tools print their +version to stderr. The definition's `version.pattern` alone captures the +version, including any tool-specific numbering such as a legacy `1.` prefix; +Core never branches on a tool name. Paths in requests are workspace-relative with `/` unless named absolute. `root` is the instance root from detection. Selected entities are @@ -4615,7 +3794,6 @@ and the directories are absolute host paths used only as substitution text. | `build.decodeDiagnostics` | `{ workspaceRoot, root, output }` | `diagnostics`: `[{ path, line, column?, severity, message }]`, deduplicated, inside the workspace only | | `build.decodeDependencies` | `{ workspaceRoot, root, project?, output?, outputFile?, result? }` | `dependencies`: `{ project, dependencies: [{ name, group?, version?, scope?, properties?, children }], truncated }` | | `build.syncFingerprint` | `{ workspaceRoot, root, projects, options? }` | `sync`: `{ fingerprint, watch }` | -| `build.languageImport` | `{ workspaceRoot, root, options?, executablePath? }` | `languageImport`: `null` without a `languageImport` declaration; `{ kind: "maven", mavenContext: { version, reactorPath, profiles, settingsPath?, localRepositoryPath?, mavenExecutablePath? } }` for `lsp.startServer.mavenContext` and `lsp.updateMavenConfiguration`; or `{ kind: "gradle", javaGradle: { wrapperPresent, offline, userHome? } }` for `lsp.startServer.javaGradle`, where `wrapperPresent` reports `gradle/wrapper/gradle-wrapper.properties` under `root` | Invocation candidates also include `{ kind: "toolchain", toolchainID, root }`. Hosts resolve them through the same canonical project/subproject @@ -4636,6 +3814,12 @@ inside `workspaceRoot` (and the given `outputFile`); none of the commands writes anything or starts a process. `shared/plugins/contract/v1/fixtures/build-systems/build-plans.json` holds the golden plans and decoded outputs. +A `junitXml` testcase's optional `durationMillis` comes only from its `time` +seconds attribute, rounded to whole milliseconds. Repeated invocations of one case +sum their times; when any invocation lacks a valid non-negative time, or the sum +exceeds 2^53 - 1, the field is omitted. A reported zero is valid. Hosts hide a +missing duration and never substitute a suite or console summary. + ### VCS providers The `vcs.*` commands implement the `vcsProviders` capability of @@ -4909,32 +4093,6 @@ than being inserted as literal source text. The initialize handshake advertises incomplete items. Core forwards `labelDetails` and omits null `data` so `completionItem/resolve` can still produce import `use`/`import` edits. -The shared JDT adapter sends `java.completion.guessMethodArguments: "off"` -through initialization, configuration notifications and section queries on both -platforms. JDT LS therefore leaves constructor and method argument lists empty -while preserving the snippet cursor inside the parentheses. Other completion -settings declared in the provider's initialization options are retained for the -session in initialize, didChangeConfiguration, and parent/leaf section pulls -(including favoriteStaticMembers and filteredTypes). This does not disable snippet -support or strip parameter text from arbitrary completion items; the shared -configuration fixture is `rust/fixtures/lsp/java-completion-v1.json`. - -### Java preparation snapshot - -Java `projectPreparation` runtime events carry a `result` object with `phase` -(`starting`, `importing`, `configuring`, `building`, `ready`, `stopped`), `status` -(`idle`, `loading`, `ready`, `failed`) and boolean `blocksRun`. -`lsp.pollEvents` and `lsp.waitEvents` also return `projectPreparation` (the current -snapshot or null) alongside `events`, so restored consumers need not replay the -queue. Event session identity and sequence retain their existing semantics. - -The snapshot reuses the existing service-ready signal, profile-task results and -configuration/build coordinator. Generic indexing never blocks Run. Profile -failure is visible but does not globally block unrelated targets; callers still -build the selected target before launching. A successful preparation does not -promise compilation success. Shared examples live in -`rust/fixtures/lsp/project-preparation-v1.json`. - ### Workspace commit preconditions `git.commitState` accepts `{ root }` and returns `{ head, branch, indexEntries, @@ -5283,26 +4441,26 @@ application action. Closing the connection releases the callback. Windows delive window-scoped Tauri events rather than an ordered Channel; a missing notification must not block subsequent requests. -### Java service hot replacement +### Hot code replacement -`debug.inspect` accepts the Java-provider extension `kind: "redefineClasses"` -with a caller-owned `operationId`. Unlike the inspection kinds, this operation -**mutates the running debuggee**; it requires a running or paused Java session, -but no selected thread. Platforms save documents and complete a successful JDT -`vscode.java.buildWorkspace` before submitting it. They must retain the original -launch target and compare its runtime paths with JDT before compiling; changed -paths require a restart instead of applying to a different output directory. +`debug.inspect` accepts `kind: "redefineClasses"` with a caller-owned +`operationId` only for a session whose adapter declares +`features.hotCodeReplace` in its plugin declaration; any other session is +`invalid_request`. Unlike the inspection kinds, this operation **mutates the +running debuggee**; it requires a running or paused session, but no selected +thread. Core sends the declared `request` command and relays the declared +`event` messages to the Debug Console as progress. Building the changed code +before submitting the operation belongs to the plugin that owns the language; +Core never compiles, and never interprets the replaced units. The terminal result is `{ kind: "redefineClasses", changedClasses: [...] }`, -sorted and deduplicated. Empty means no classes were replaced. Java Debug Server's -`errorMessage` inside a successful DAP response becomes `operationFailed` with -`adapterRejected`; the debug session remains usable. Malformed replacement -results also fail only the operation. Replacement does not imply continue. -The fixture is `rust/fixtures/debug/hot-code-replace-v1.json`. - -Java debug launches append `-Dspring.devtools.restart.enabled=false` (also for -Core-planned direct JDT JDWP launches) so DevTools cannot restart the classloader during -HotSwap. Attach to independently launched JVMs does not change their options. +read from the declared `changedField`, sorted and deduplicated; without a +declared field it is empty. A non-empty message in the declared `errorField` of +a successful DAP response fails the operation; the debug session remains +usable. Malformed replacement results also fail only the operation. +Replacement does not imply continue. The fixture is +`rust/fixtures/debug/hot-code-replace-v1.json`. + The Windows host maps `redefineClasses` to this operation and `cancelOperation` to `debug.cancelOperation` with a `timedOut` reason for its bounded result wait. @@ -5428,19 +4586,6 @@ presentation feature; views never call the C ABI. Windows can reuse validation when it later adds a native renderer; Core support does not make Windows advertise native plugin content. -### Project JDK supplied to JDT LS - -`lsp.startServer.javaRuntimes` entries contain `homePath`, `version`, and optional -`isDefault` (default `false`). Platforms use their existing project toolchain selection, -including explicit JDK homes outside discovery roots. The server process JVM remains -independent. Core registers one JDK per Java major; the first valid selected candidate -wins its major and alone receives `default: true` in `java.configuration.runtimes`, -both at initialization and in configuration responses. Empty homes and unparseable versions -are ignored during projection; platform adapters validate explicit choices before launch. -Legacy callers that omit `isDefault` retain their previous behavior. -Fixture: `rust/fixtures/lsp/java-project-runtimes-v1.json`. - - ### Workspace indexing policy `workspace.configureIndexing` accepts `root`, `manualExcludedDirectories`, @@ -5526,8 +4671,7 @@ in force. Hosts must display this state instead of treating it as empty rules. Tree listings deliberately remain available. Snapshot inventories, filename and content indexes, symbols and replacement preview candidates obey the policy. Generated filename search cannot bypass it. Language-server working-copy sync is -blocked for excluded paths. Java additionally receives import exclusions; this does -not promise resource-scan exclusion for imported or build-declared modules. Rust and Go receive native configuration overlays that preserve user settings; +blocked for excluded paths. Rust and Go receive native configuration overlays that preserve user settings; other providers may continue background scanning and hosts must display that limitation. `workspace.snapshot.incompleteDirectories` is present when optional child directories @@ -5624,8 +4768,8 @@ scope covers nothing and an empty prefix is `InvalidRequest`. It returns others stay in place, then the run's covered results follow. Incoming results outside the scope are discarded, and for a repeated id the later entry wins. Core compares strings only: hosts keep the result payloads, and the provider -adapter builds the scope (for example turning Java nested classes `Outer$Inner` -into the prefix `Outer$`). Cases: `rust/fixtures/build/test-outcome-merge-v1.json`. +adapter builds the scope (for example turning a parent result `suite` into the +prefix `suite/`). Cases: `rust/fixtures/build/test-outcome-merge-v1.json`. ### Variable and disassembly paging @@ -5726,17 +4870,12 @@ does not transition session state. Codes have one meaning: | code | stage | meaning | | --- | --- | --- | -| `mavenSettingsUnchanged` | `mavenSettings` | Maven settings could not be normalized and were forwarded unchanged | -| `configurationCleanupFailed` | `configurationCleanup` | Unused language-server configuration areas could not be removed | -| `mavenProfileStartFailed` | `mavenProfileStart` | The pending Maven profile update could not start | - -The existing Core log sources additionally classify `upstreamStderr` (stderr -observation), `stderrReadFailed` (stderr I/O), `navigationLimited` (batch cap), -`navigationMarkerFailed` (marker verification), `workspaceImportStalled` -(no import progress), `mavenProfileUpdateFailed` (project update failure), -`shutdownTimeout` (forced termination after shutdown deadline), and -`mavenProfileTimeout` (profile application deadline). Each code names exactly -that observation; arbitrary upstream messages retain `upstreamDiagnostic`. +| `upstreamStderr` | `stderr` | The language server wrote to stderr | +| `stderrReadFailed` | `stderr` | Reading the language server's stderr failed | +| `shutdownTimeout` | `shutdown` | The shutdown deadline passed and Core forced termination | +| `processCleanupFailed` | `cleanup` | The language-server process could not be cleaned up | + +Each code names exactly that observation. Hosts persist these controlled identities, never the raw detail, URL, output or argv. @@ -5782,7 +4921,7 @@ of the validated catalog routing field. Hosts must use `fields` and `channel` from Core, never rebuild the route from `providerId` or `pluginId`. Log runtime events retain `type:"log"`, sequence, providerId, sessionId, -level, message, detail, error, and Maven state fields. They add `channel`, +level, message, detail and error. They add `channel`, `pluginId?`, `role`, `event` (catalog ID), `catalogLevel` (canonical uppercase v2 level), and `fields` (normalized scalar disk fields). `type` is the actual JSON discriminator; there is no `kind` wire key. Catalog metadata determines canonical v2 severity; the old `level` remains a v1 @@ -6098,22 +5237,6 @@ the failure again. {"ts":"2026-11-03T08:00:01.123Z","level":"WARN","event":"vcs.git.command.failed","channel":"vcs/git","op":"op-7f3a","fields":{"command":"fetch","arguments":"fetch --prune origin","repository":"/src/app","source":"user","duration_ms":311,"exit_code":128,"stderr":"fatal: could not read from remote repository."}} ``` -`agent.attachments.retentionPlan` decides which pasted Agent images the platform may -delete; it reads and writes no files. The payload is `{nowMs, entries, acceptedReferences, -ledgerPresent, caseInsensitive}`: `entries` lists the platform's pasted-image cache as `{path, modifiedMs, -abandoned}` (`modifiedMs` is Unix milliseconds and may be omitted; `abandoned` marks a -paste that failed or was discarded before any conversation accepted it), -`acceptedReferences` is the platform ledger of paths a conversation accepted, and -`caseInsensitive` (Windows) compares paths without case; separators are always normalized. -`ledgerPresent` reports only whether the ledger file exists, never whether it is empty: when it -is missing, every non-abandoned cached image counts as accepted (the cache predates the -ledger) and is returned in `seed`, which the platform writes atomically (temporary file, then -rename) before deleting anything. The result is `{delete, keep, seed}`, the first two lists of `{path, reason}` in entry order. Reasons: -`referenced` (kept, even when flagged abandoned), `recent` (kept, unreferenced and at most -seven days old or of unknown age), `abandoned` and `expired` (deleted). The cache and the -ledger live in the platform cache directory, never in the app bundle or install directory. -The fixture is `rust/fixtures/agent/attachment-retention-v1.json`. - ### Atomic local toolchain pin editing `toolchains.editPins` accepts `{document,directory,edits:[{tool,pin}]}` and returns diff --git a/rust/contracts/toolchain-local-v1.schema.json b/rust/contracts/toolchain-local-v1.schema.json index c39dd18a..15ce1360 100644 --- a/rust/contracts/toolchain-local-v1.schema.json +++ b/rust/contracts/toolchain-local-v1.schema.json @@ -7,15 +7,32 @@ "properties": { "version": { "const": 1 }, "toolchains": { + "description": "Legacy entries written by earlier releases. Core keeps them unchanged and never interprets their keys.", "type": "object", + "maxProperties": 1024, "additionalProperties": { "type": "object", - "properties": { - "home": { "type": "string", "minLength": 1 }, - "executable": { "type": "string", "minLength": 1 }, - "javaHome": { "type": "string", "minLength": 1 } - }, - "additionalProperties": false + "maxProperties": 16, + "additionalProperties": { "type": "string", "maxLength": 4096 } + } + }, + "pins": { + "description": "Workspace-relative directory ('.' for the root) to tool ID to the installation pinned there.", + "type": "object", + "maxProperties": 1024, + "additionalProperties": { + "type": "object", + "maxProperties": 64, + "additionalProperties": { + "type": "object", + "required": ["home", "version", "provider"], + "properties": { + "home": { "type": "string", "minLength": 1, "maxLength": 4096 }, + "version": { "type": "string", "maxLength": 128 }, + "provider": { "type": "string", "maxLength": 256 } + }, + "additionalProperties": false + } } } }, diff --git a/rust/contracts/toolchain-requirements-v1.schema.json b/rust/contracts/toolchain-requirements-v1.schema.json index 5eedb378..0a316112 100644 --- a/rust/contracts/toolchain-requirements-v1.schema.json +++ b/rust/contracts/toolchain-requirements-v1.schema.json @@ -19,9 +19,7 @@ "type": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" }, "minimumVersion": { "type": "string", "minLength": 1 }, "preferredVendor": { "type": "string", "minLength": 1 }, - "wrapper": { "type": "string", "pattern": "^(?!/)(?!.*(^|/)\\.\\.(?:/|$)).*$" }, - "version": { "type": "string", "minLength": 1 }, - "java": { "type": "string", "minLength": 1 } + "version": { "type": "string", "minLength": 1 } }, "additionalProperties": false } diff --git a/rust/fixtures/build-project-options/shared-defaults.json b/rust/fixtures/build-project-options/shared-defaults.json index fc63a3ca..557e3944 100644 --- a/rust/fixtures/build-project-options/shared-defaults.json +++ b/rust/fixtures/build-project-options/shared-defaults.json @@ -1,7 +1,7 @@ { "version": 1, "systems": { - "maven": { + "make": { "options": {}, "instanceDefaults": { "profiles": [ diff --git a/rust/fixtures/build-project-options/validation.json b/rust/fixtures/build-project-options/validation.json index b300e01a..57a51ae8 100644 --- a/rust/fixtures/build-project-options/validation.json +++ b/rust/fixtures/build-project-options/validation.json @@ -3,23 +3,23 @@ "cases": [ { "name": "empty document", "valid": true, "document": {"version": 1, "systems": {}} }, { "name": "numeric schema const", "valid": true, "document": {"version": 1.0, "systems": {}} }, - { "name": "per root empty selection and inactive custom choice", "valid": true, "document": {"version": 1, "systems": {"maven": {"instanceDefaults": {"profiles": ["ci"], "offline": false}, "instances": {"services/项目": {"profiles": []}, ".": {"settingsFile": "selected.xml"}}, "savedChoices": {"profiles": ["ci", "manual"]}}}} }, + { "name": "per root empty selection and inactive custom choice", "valid": true, "document": {"version": 1, "systems": {"make": {"instanceDefaults": {"profiles": ["ci"], "offline": false}, "instances": {"services/项目": {"profiles": []}, ".": {"settingsFile": "selected.xml"}}, "savedChoices": {"profiles": ["ci", "manual"]}}}} }, { "name": "unknown system retained", "valid": true, "document": {"version": 1, "systems": {"future-tool": {"options": {"custom.option": "saved"}}}} }, { "name": "boolean version", "valid": false, "document": {"version": true, "systems": {}} }, { "name": "unsupported version", "valid": false, "document": {"version": 2, "systems": {}} }, { "name": "unknown document field", "valid": false, "document": {"version": 1, "systems": {}, "extra": true} }, - { "name": "unknown system field", "valid": false, "document": {"version": 1, "systems": {"maven": {"extra": true}}} }, - { "name": "numeric option", "valid": false, "document": {"version": 1, "systems": {"maven": {"options": {"threads": 2}}}} }, - { "name": "null option", "valid": false, "document": {"version": 1, "systems": {"maven": {"options": {"profiles": null}}}} }, - { "name": "mixed list", "valid": false, "document": {"version": 1, "systems": {"maven": {"options": {"profiles": ["ci", false]}}}} }, + { "name": "unknown system field", "valid": false, "document": {"version": 1, "systems": {"make": {"extra": true}}} }, + { "name": "numeric option", "valid": false, "document": {"version": 1, "systems": {"make": {"options": {"threads": 2}}}} }, + { "name": "null option", "valid": false, "document": {"version": 1, "systems": {"make": {"options": {"profiles": null}}}} }, + { "name": "mixed list", "valid": false, "document": {"version": 1, "systems": {"make": {"options": {"profiles": ["ci", false]}}}} }, { "name": "empty system id", "valid": false, "document": {"version": 1, "systems": {"": {}}} }, - { "name": "invalid system id", "valid": false, "document": {"version": 1, "systems": {"maven.plugin": {}}} }, - { "name": "invalid option id", "valid": false, "document": {"version": 1, "systems": {"maven": {"options": {"profile/value": []}}}} }, - { "name": "root escape", "valid": false, "document": {"version": 1, "systems": {"maven": {"instances": {"../outside": {}}}}} }, - { "name": "absolute root", "valid": false, "document": {"version": 1, "systems": {"maven": {"instances": {"/outside": {}}}}} }, - { "name": "windows root", "valid": false, "document": {"version": 1, "systems": {"maven": {"instances": {"C:/outside": {}}}}} }, - { "name": "empty segment", "valid": false, "document": {"version": 1, "systems": {"maven": {"instances": {"a//b": {}}}}} }, - { "name": "backslash root", "valid": false, "document": {"version": 1, "systems": {"maven": {"instances": {"a\\b": {}}}}} }, - { "name": "malformed saved choices", "valid": false, "document": {"version": 1, "systems": {"maven": {"savedChoices": {"profiles": [false]}}}} } + { "name": "invalid system id", "valid": false, "document": {"version": 1, "systems": {"make.plugin": {}}} }, + { "name": "invalid option id", "valid": false, "document": {"version": 1, "systems": {"make": {"options": {"profile/value": []}}}} }, + { "name": "root escape", "valid": false, "document": {"version": 1, "systems": {"make": {"instances": {"../outside": {}}}}} }, + { "name": "absolute root", "valid": false, "document": {"version": 1, "systems": {"make": {"instances": {"/outside": {}}}}} }, + { "name": "windows root", "valid": false, "document": {"version": 1, "systems": {"make": {"instances": {"C:/outside": {}}}}} }, + { "name": "empty segment", "valid": false, "document": {"version": 1, "systems": {"make": {"instances": {"a//b": {}}}}} }, + { "name": "backslash root", "valid": false, "document": {"version": 1, "systems": {"make": {"instances": {"a\\b": {}}}}} }, + { "name": "malformed saved choices", "valid": false, "document": {"version": 1, "systems": {"make": {"savedChoices": {"profiles": [false]}}}} } ] } diff --git a/rust/fixtures/debug/hot-code-replace-v1.json b/rust/fixtures/debug/hot-code-replace-v1.json index fa36df2d..3931dff2 100644 --- a/rust/fixtures/debug/hot-code-replace-v1.json +++ b/rust/fixtures/debug/hot-code-replace-v1.json @@ -3,7 +3,7 @@ "cases": [ {"name":"replacement", "body":{"changedClasses":["b.B","a.A","b.B"]}, "result":{"kind":"redefineClasses","changedClasses":["a.A","b.B"]}}, {"name":"no changes", "body":{"changedClasses":[]}, "result":{"kind":"redefineClasses","changedClasses":[]}}, - {"name":"DAP success with JVM failure", "body":{"changedClasses":[],"errorMessage":"Schema change not supported"}, "failure":true}, + {"name":"DAP success with a runtime failure", "body":{"changedClasses":[],"errorMessage":"Schema change not supported"}, "failure":true}, {"name":"missing result", "body":{}, "failure":true}, {"name":"invalid class name", "body":{"changedClasses":[3]}, "failure":true} ] diff --git a/rust/fixtures/editor/basic-file-highlighting-v1.json b/rust/fixtures/editor/basic-file-highlighting-v1.json deleted file mode 100644 index ebd121ee..00000000 --- a/rust/fixtures/editor/basic-file-highlighting-v1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "version": 1, - "associations": [ - { "filename": "Makefile", "language": "makefile" }, - { "filename": "makefile", "language": "makefile" }, - { "filename": "GNUmakefile", "language": "makefile" }, - { "filename": "Makefile.local", "language": "makefile" }, - { "filename": "rules.mk", "language": "makefile" }, - { "filename": "rules.MAK", "language": "makefile" }, - { "filename": "CMakeLists.txt", "language": "cmake" }, - { "filename": "toolchain.cmake", "language": "cmake" }, - { "filename": "script.groovy", "language": "groovy" }, - { "filename": "build.gradle", "language": "groovy" }, - { "filename": "Jenkinsfile", "language": "groovy" }, - { "filename": "build.gradle.kts", "language": "kotlin" }, - { "filename": "deploy.ps1", "language": "powershell" }, - { "filename": "module.psm1", "language": "powershell" }, - { "filename": "module.psd1", "language": "powershell" }, - { "filename": "build.bat", "language": "batch" }, - { "filename": "build.cmd", "language": "batch" }, - { "filename": "setup.zsh", "language": "bash" }, - { "filename": ".bashrc", "language": "bash" }, - { "filename": ".zshrc", "language": "bash" }, - { "filename": ".bash_profile", "language": "bash" }, - { "filename": ".zprofile", "language": "bash" }, - { "filename": ".profile", "language": "bash" }, - { "filename": ".env", "language": "dotenv" }, - { "filename": ".env.production", "language": "dotenv" }, - { "filename": "Containerfile", "language": "dockerfile" }, - { "filename": "Dockerfile.dev", "language": "dockerfile" }, - { "filename": "Containerfile.dev", "language": "dockerfile" } - ], - "syntax": [ - { "filename": "Makefile", "language": "makefile", "text": "# build\nSOURCES := $(wildcard src/*.c)\nall: $(SOURCES)\n\t@echo $@", "tokens": [{ "line": 1, "text": "#", "role": "comment" }, { "line": 2, "text": "wildcard", "role": "function" }] }, - { "filename": "CMakeLists.txt", "language": "cmake", "text": "# configure\nproject(Example)\nset(NAME \"example\")", "tokens": [{ "line": 1, "text": "#", "role": "comment" }, { "line": 2, "text": "project", "role": "function" }, { "line": 3, "text": "example", "role": "string" }] }, - { "filename": "build.gradle", "language": "groovy", "text": "// build\ndef name = \"example\"\nplugins { id 'java' }", "tokens": [{ "line": 1, "text": "//", "role": "comment" }, { "line": 2, "text": "def", "role": "keyword" }, { "line": 2, "text": "example", "role": "string" }] }, - { "filename": "deploy.ps1", "language": "powershell", "text": "# deploy\nWrite-Host \"example\"", "tokens": [{ "line": 1, "text": "#", "role": "comment" }, { "line": 2, "text": "example", "role": "string" }] }, - { "filename": "build.cmd", "language": "batch", "text": "REM build\necho \"example\"", "tokens": [{ "line": 1, "text": "REM", "role": "comment" }] } - ] -} diff --git a/rust/fixtures/execution/framework-identity.json b/rust/fixtures/execution/framework-identity.json deleted file mode 100644 index 15d0cf05..00000000 --- a/rust/fixtures/execution/framework-identity.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "name": "Framework identity and service classification for detected configurations", - "notes": [ - "A detected configuration carries the framework name in its provider extension so a client can label the service without re-reading the manifest.", - "Frameworks are matched most-specific-first, because a meta-framework declares the framework it is built on: a Nuxt app also depends on Vue.", - "A script is a service when its body names a program that serves, not when its name looks like `dev` or `start`. A name-only guess is reported as `heuristic` so a detector holding a real declaration wins deduplication.", - "Deduplication identity is (cwd, lowercased name); the provider is excluded, so the same service seen by two detectors merges to the higher confidence." - ], - "requests": [ - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "web/package.json": "{\"dependencies\":{\"nuxt\":\"^3.11.0\",\"vue\":\"^3.4.0\"},\"scripts\":{\"dev\":\"nuxt dev\",\"build\":\"nuxt build\"}}", - "site/package.json": "{\"dependencies\":{\"next\":\"^14.2.0\",\"react\":\"^18.3.0\"},\"scripts\":{\"dev\":\"next dev\"}}", - "admin/package.json": "{\"dependencies\":{\"vue\":\"^3.4.0\"},\"devDependencies\":{\"vite\":\"^5.0.0\"},\"scripts\":{\"dev\":\"vite\",\"check\":\"tsc -p .\"}}" - }, - "expected": { - "configurations": [ - { - "id": "npm.script:web/dev", - "execution": "service", - "confidence": "declared", - "extensions": { "npm": { "framework": "nuxt", "server": "nuxt" } } - }, - { - "id": "npm.script:web/build", - "execution": "task", - "extensions": { "npm": { "framework": "nuxt" } } - }, - { - "id": "npm.script:site/dev", - "execution": "service", - "extensions": { "npm": { "framework": "next", "server": "next" } } - }, - { - "id": "npm.script:admin/dev", - "execution": "service", - "extensions": { "npm": { "framework": "vue", "server": "vite" } } - }, - { - "id": "npm.script:admin/check", - "execution": "task", - "extensions": { "npm": { "framework": "vue" } } - } - ] - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "package.json": "{\"scripts\":{\"start\":\"tsc -p .\",\"local\":\"NODE_ENV=development npx vite --port 3000\",\"legacy\":\"./scripts/dev.sh\"}}" - }, - "expected": { - "configurations": [ - { - "id": "npm.script:start", - "execution": "task", - "reason": "The body names a program that runs to completion, so the conventional `start` name is not trusted." - }, - { - "id": "npm.script:local", - "execution": "service", - "confidence": "declared", - "reason": "Environment assignments and `npx` wrap the program without changing it, and bare `vite` serves." - }, - { - "id": "npm.script:legacy", - "execution": "task", - "reason": "An unrecognised body and a name that is not conventionally a server." - } - ] - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "declared/pyproject.toml": "[project]\nname = \"declared\"\ndependencies = [\"fastapi>=0.110\", \"uvicorn[standard]\"]\n", - "declared/main.py": "from fastapi import FastAPI\napp = FastAPI()\n", - "guessed/main.py": "from fastapi import FastAPI\napp = FastAPI()\n", - "edge/asgi.py": "from starlette.applications import Starlette\napp = Starlette()\n" - }, - "expected": { - "configurations": [ - { - "id": "python.uvicorn:declared/main", - "execution": "service", - "confidence": "declared", - "command": "uvicorn", - "args": ["main:app", "--reload"], - "extensions": { - "python": { "module": "main", "framework": "fastapi" } - } - }, - { - "id": "python.uvicorn:guessed/main", - "confidence": "heuristic", - "reason": "No dependency list, so the framework import in one file is the only evidence." - }, - { - "id": "python.uvicorn:edge/asgi", - "command": "uvicorn", - "args": ["asgi:app", "--reload"], - "extensions": { - "python": { "module": "asgi", "framework": "starlette" } - } - } - ] - } - } - ] -} diff --git a/rust/fixtures/execution/gradle.json b/rust/fixtures/execution/gradle.json deleted file mode 100644 index 28a3e365..00000000 --- a/rust/fixtures/execution/gradle.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "name": "Gradle service detection and toolchain requirements", - "notes": [ - "A Gradle build is runnable when a plugin contributes a start task. The build script is matched for plugin ids rather than executed, because running `gradle tasks` on project open would spawn a daemon per module.", - "Subproject tasks are invoked from the build root, because Gradle only resolves task paths from the directory holding the settings file. The configuration name is the full Gradle project path so two modules with the same leaf name stay distinct.", - "The command is the bare program name `gradle`. Wrapper preference travels in the toolchain requirement so the platform adapter resolves `./gradlew` or `gradlew.bat` for its own operating system." - ], - "requests": [ - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "build.gradle": "plugins { id 'org.springframework.boot' version '3.2.0' }\n", - "gradlew": "#!/bin/sh\n" - }, - "expected": { - "configurations": [ - { - "id": "gradle.service:bootRun", - "provider": "gradle.service", - "name": "bootRun", - "execution": "service", - "confidence": "declared", - "command": "gradle", - "args": ["bootRun"], - "cwd": ".", - "source": "build.gradle", - "extensions": { - "gradle": { - "task": "bootRun", - "plugin": "org.springframework.boot", - "project": ":" - } - } - } - ], - "toolchains": { - "project-gradle": { - "type": "gradle", - "wrapper": "./gradlew", - "java": "project-jdk" - }, - "project-jdk": { "type": "java" } - } - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "settings.gradle": "include 'services:orders'\ninclude 'services:shipping'\n", - "services/orders/build.gradle": "plugins { id 'org.springframework.boot' }\n", - "services/shipping/build.gradle": "plugins { id 'application' }\n" - }, - "expected": { - "configurations": [ - { - "id": "gradle.service:services:orders", - "provider": "gradle.service", - "name": "services:orders", - "execution": "service", - "command": "gradle", - "args": [":services:orders:bootRun"], - "cwd": ".", - "source": "build.gradle" - }, - { - "id": "gradle.application:services:shipping", - "provider": "gradle.application", - "name": "services:shipping", - "execution": "application", - "command": "gradle", - "args": [":services:shipping:run"], - "cwd": ".", - "source": "build.gradle" - } - ] - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "build.gradle": "plugins { id 'java-library' }\n// id 'org.springframework.boot'\n" - }, - "expected": { - "configurations": [], - "reason": "A library build contributes no start task, and a commented plugin id is not applied." - } - } - ] -} diff --git a/rust/fixtures/execution/maven-java-main-source-sets-v1.json b/rust/fixtures/execution/maven-java-main-source-sets-v1.json deleted file mode 100644 index e7781682..00000000 --- a/rust/fixtures/execution/maven-java-main-source-sets-v1.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": 1, - "name": "Maven Java main source-set launch plans", - "cases": [ - { - "name": "test source main", - "module": "module-a", - "source": "module-a/src/test/java/com/example/App.java", - "sourceSet": "test", - "configurationId": "java-main:com.example.App:module-a", - "mainClass": "com.example.App", - "classPaths": ["/workspace/module-a/target/test-classes"], - "modulePaths": [], - "expectedArguments": ["com.example.App"] - }, - { - "name": "main source main", - "module": "module-b", - "source": "module-b/src/main/java/com/example/App.java", - "sourceSet": "main", - "configurationId": "java-main:com.example.App:module-b", - "mainClass": "module-b/com.example.App", - "classPaths": ["/workspace/module-b/target/classes"], - "modulePaths": ["/workspace/module-b/target/modules"], - "expectedArguments": ["-m", "module-b/com.example.App"] - } - ] -} diff --git a/rust/fixtures/execution/maven-runtime-versions.json b/rust/fixtures/execution/maven-runtime-versions.json deleted file mode 100644 index 96166679..00000000 --- a/rust/fixtures/execution/maven-runtime-versions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "description": "Maven and mvnd expose the embedded Apache Maven version, never the daemon banner.", - "cases": [ - { "output": "Apache Maven 3.9.11 (fixture)\nMaven home: /fixture/maven\n", "version": "3.9.11" }, - { "output": "Apache Maven Daemon (mvnd) 1.0.6 darwin-aarch64\nApache Maven 3.9.11 (fixture)\n", "version": "3.9.11" }, - { "output": "Apache Maven Daemon (mvnd) 1.0.6 windows-amd64\r\nApache Maven 4.0.0-rc-3 (fixture)\r\n", "version": "4.0.0-rc-3" }, - { "output": "Apache Maven Daemon (mvnd) 1.0.6\n", "version": null }, - { "output": "Java home is invalid\n", "version": null } - ] -} diff --git a/rust/fixtures/execution/maven.json b/rust/fixtures/execution/maven.json deleted file mode 100644 index a8e2a993..00000000 --- a/rust/fixtures/execution/maven.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "name": "Maven framework service detection from the declared module graph", - "notes": [ - "A plugin is what makes a goal available, so the plugin a module applies -- not a `@SpringBootApplication` annotation in a source file -- decides both that a module is a service and which framework it is. An annotation judge reads only the files the caller passed, so the same project generated a different document on every call and every team or local override joined by id came loose.", - "Each framework gets its own provider (`spring-boot.maven`, `quarkus.maven`, `micronaut.maven`) because the goals invented different property names for the same three things. Spring Boot takes `-Dspring-boot.run.jvmArguments` / `.arguments` / `.main-class` and `spring-boot:run`; Quarkus takes `-Djvm.args` / `-Dquarkus.args` and `quarkus:dev`; Micronaut takes `-Dmn.jvmArgs` / `-Dmn.appArgs` and `mn:run`. Passing one framework's names to another's goal is silently ignored, and the service starts with none of the user's arguments.", - "Only Spring Boot's goal accepts a main class. Quarkus and Micronaut resolve it from the build -- the Micronaut mojo's `mainClass` has no user property at all -- so the annotation scan grafts a class onto `spring-boot.maven` configurations only.", - "Debugging is per framework too. `spring-boot:run` forwards JVM arguments verbatim, so JDWP travels among them. Quarkus (`-Ddebug= -Dsuspend=y`) and Micronaut (`-Dmn.debug=true -Dmn.debug.port= -Dmn.debug.suspend=true`) start the agent themselves, so JDWP must NOT also be injected into their JVM arguments: two agents on one port fail to bind and the service never starts. Both are told to suspend because a breakpoint in initialisation is otherwise unreachable -- neither suspends by default.", - "Maven modules are declared, not discovered: the detector reads the graph from the root `pom.xml` instead of judging each directory the shared walk visits. A directory-driven scan is wrong in both directions -- `` may name a directory the walk prunes (one called `build` or `out` is invisible), while a leftover `pom.xml` outside the graph is not part of the build at all.", - "A caller only needs to supply workspace-relative Java paths. Maven root discovery checks their ancestor directories, so a reactor nested below the opened workspace is still selected even when the platform does not include `pom.xml` in the request.", - "Only `` counts. A plugin under `` pins a version for children without applying it, and one under `` never runs. `pom` packaging is an aggregator: it produces no artifact to run, so a plugin declared there configures its children rather than describing a service.", - "Maven is reached through the project toolchain binding rather than a bare program name, because the build may be driven by `./mvnw` instead of an installed `mvn`. Every configuration runs from the reactor root and addresses its module with `-pl`, so `cwd` stays `.` and the module travels in `extensions.maven.module`. The name is therefore what keeps two modules apart: `artifactId` where unique, the module path where two modules share one.", - "The annotation scan still supplies the main class, which is the only place it is named. Where a Spring Boot module holds exactly one annotated class, it becomes `extensions.maven.mainClass`; two candidates are ambiguous and are left for `spring-boot:run` to resolve. A class in a module with no framework plugin is still a runnable `java.main` entry." - ], - "requests": [ - { - "command": "runConfig.generate", - "payload": { "root": "/workspace", "paths": ["backend/src/main/java/com/demo/DemoApplication.java"] }, - "workspace": { - "pom.xml": "platformpombackendshared-lib", - "backend/pom.xml": "backendorg.springframework.bootspring-boot-maven-plugin", - "shared-lib/pom.xml": "shared-lib", - "backend/src/main/java/com/demo/DemoApplication.java": "package com.demo;\n@SpringBootApplication\npublic class DemoApplication { public static void main(String[] a) {} }\n" - }, - "expected": { - "configurations": [ - { - "id": "spring-boot.maven:backend", - "provider": "spring-boot.maven", - "name": "backend", - "execution": "service", - "confidence": "declared", - "command": null, - "cwd": ".", - "source": "backend/pom.xml", - "toolchains": { "java": "project-jdk", "maven": "project-maven" }, - "debug": { "adapter": "jdwp" }, - "extensions": { - "maven": { "module": "backend", "mainClass": "com.demo.DemoApplication" } - } - } - ], - "absentIds": ["spring-boot.maven:shared-lib", "spring:com.demo.DemoApplication"], - "reason": "The library module applies no boot plugin. The annotated class contributes its name to the module's service rather than a service of its own." - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace", "paths": ["projects/demo/backend/src/main/java/com/demo/DemoApplication.java", "samples/Standalone.java"] }, - "workspace": { - "projects/demo/pom.xml": "platformpombackend", - "projects/demo/backend/pom.xml": "backendorg.springframework.bootspring-boot-maven-plugin", - "projects/demo/backend/src/main/java/com/demo/DemoApplication.java": "package com.demo;\n@SpringBootApplication\npublic class DemoApplication { public static void main(String[] a) {} }\n", - "samples/Standalone.java": "public class Standalone { public static void main(String[] a) {} }\n" - }, - "expected": { - "configurations": [ - { - "id": "spring-boot.maven:backend", - "provider": "spring-boot.maven", - "cwd": "projects/demo", - "source": "projects/demo/backend/pom.xml", - "toolchains": { "java": "project-jdk", "maven": "project-maven" }, - "extensions": { "maven": { "module": "backend", "mainClass": "com.demo.DemoApplication" } } - }, - { - "id": "java-main:Standalone", - "provider": "java.main", - "cwd": ".", - "source": "samples/Standalone.java", - "toolchains": { "java": "project-jdk" }, - "extensions": { "maven": { "module": ".", "mainClass": "Standalone" }, "java": { "source": "samples/Standalone.java" } } - } - ], - "reason": "The Maven Java source discovers and binds to the nested reactor, while the standalone source in the same request remains a direct JDK launch." - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "pom.xml": "rootpombuild", - "build/pom.xml": "builder-serviceorg.springframework.bootspring-boot-maven-plugin" - }, - "expected": { - "configurations": [ - { - "id": "spring-boot.maven:builder-service", - "provider": "spring-boot.maven", - "execution": "service", - "source": "build/pom.xml", - "extensions": { "maven": { "module": "build" } } - } - ], - "reason": "The shared walk prunes directories named `build`, but `` names this one explicitly, so reading the declared graph is what makes it visible." - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "pom.xml": "parentpomservice-aorg.springframework.bootspring-boot-maven-plugin", - "service-a/pom.xml": "service-aorg.springframework.bootspring-boot-maven-plugin" - }, - "expected": { - "configurations": [ - { - "id": "spring-boot.maven:service-a", - "provider": "spring-boot.maven", - "execution": "service", - "extensions": { "maven": { "module": "service-a" } } - } - ], - "absentIds": ["spring-boot.maven:parent"], - "reason": "The aggregator produces no artifact, so the plugin it declares configures its children." - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "pom.xml": "rootpomteam-a/gatewayteam-b/gateway", - "team-a/gateway/pom.xml": "gatewayorg.springframework.bootspring-boot-maven-plugin", - "team-b/gateway/pom.xml": "gatewayorg.springframework.bootspring-boot-maven-plugin" - }, - "expected": { - "configurations": [ - { - "id": "spring-boot.maven:team-a/gateway", - "name": "team-a/gateway", - "extensions": { "maven": { "module": "team-a/gateway" } } - }, - { - "id": "spring-boot.maven:team-b/gateway", - "name": "team-b/gateway", - "extensions": { "maven": { "module": "team-b/gateway" } } - } - ], - "reason": "`artifactId` is only unique per `groupId`. Both modules share one and all Spring Boot configurations share `cwd`, so the name falls back to the module path or one of two real services would be dropped during dedup." - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "pom.xml": "liborg.springframework.bootspring-boot-maven-plugin3.2.0" - }, - "expected": { - "configurations": [], - "reason": "`pluginManagement` pins a version for children without applying the plugin, so nothing here is runnable." - } - }, - { - "command": "runConfig.generate", - "payload": { "root": "/workspace" }, - "workspace": { - "pom.xml": "platformpomboot-apiquarkus-apimicronaut-apiquarkus-platform-apimicronaut-maven-api", - "boot-api/pom.xml": "boot-apiorg.springframework.bootspring-boot-maven-plugin", - "quarkus-api/pom.xml": "quarkus-apiio.quarkusquarkus-maven-plugin", - "micronaut-api/pom.xml": "micronaut-apiio.micronaut.buildmicronaut-maven-plugin", - "quarkus-platform-api/pom.xml": "quarkus-platform-apiio.quarkus.platformquarkus-maven-plugin", - "micronaut-maven-api/pom.xml": "micronaut-maven-apiio.micronaut.mavenmicronaut-maven-plugin" - }, - "expected": { - "configurations": [ - { - "id": "spring-boot.maven:boot-api", - "provider": "spring-boot.maven", - "execution": "service", - "extensions": { "maven": { "module": "boot-api" } } - }, - { - "id": "quarkus.maven:quarkus-api", - "provider": "quarkus.maven", - "execution": "service", - "extensions": { "maven": { "module": "quarkus-api" } } - }, - { - "id": "micronaut.maven:micronaut-api", - "provider": "micronaut.maven", - "execution": "service", - "extensions": { "maven": { "module": "micronaut-api" } } - }, - { - "id": "quarkus.maven:quarkus-platform-api", - "provider": "quarkus.maven", - "execution": "service", - "extensions": { "maven": { "module": "quarkus-platform-api" } } - }, - { - "id": "micronaut.maven:micronaut-maven-api", - "provider": "micronaut.maven", - "execution": "service", - "extensions": { "maven": { "module": "micronaut-maven-api" } } - } - ], - "reason": "The plugin decides the framework, and the framework decides the provider, because each goal takes its arguments under different property names." - } - }, - { - "command": "runConfig.createLaunchPlan", - "payload": { "root": "/workspace", "configurationId": "quarkus.maven:api", "debugPort": 5005 }, - "notes": "Given `extensions.maven` of `{module: api, jvmArguments: [-Xmx2g], programArguments: [--dev]}`.", - "expected": { - "executable": { "toolchain": "project-maven" }, - "workingDirectory": ".", - "arguments": [ - "-B", - "-ntp", - "-pl", - "api", - "-am", - "-Djvm.args=-Xmx2g", - "-Dquarkus.args=--dev", - "-Ddebug=5005", - "-Dsuspend=y", - "quarkus:dev" - ], - "reason": "`quarkus:dev` starts the debug agent itself given a port, so JDWP is absent from `jvm.args` -- two agents on one port would fail to bind." - } - }, - { - "command": "runConfig.createLaunchPlan", - "payload": { "root": "/workspace", "configurationId": "micronaut.maven:api", "debugPort": 5005 }, - "notes": "Given the same `extensions.maven` as the Quarkus case.", - "expected": { - "executable": { "toolchain": "project-maven" }, - "workingDirectory": ".", - "arguments": [ - "-B", - "-ntp", - "-pl", - "api", - "-am", - "-Dmn.jvmArgs=-Xmx2g", - "-Dmn.appArgs=--dev", - "-Dmn.debug=true", - "-Dmn.debug.port=5005", - "-Dmn.debug.suspend=true", - "mn:run" - ], - "reason": "The Micronaut run mojo takes debugging as three separate properties, and no main class: it resolves one from the build." - } - } - ] -} diff --git a/rust/fixtures/execution/standalone-java-compile-run-v1.json b/rust/fixtures/execution/standalone-java-compile-run-v1.json deleted file mode 100644 index 94455756..00000000 --- a/rust/fixtures/execution/standalone-java-compile-run-v1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "version": 1, - "name": "Standalone Java compile-then-run launch plans", - "description": "Non-Maven java.main sources compile with javac into a per-configuration cache directory, then launch by qualified class name so JDK 8 (which lacks the JEP 330 single-file source launcher) can run them.", - "outputDirectoryPrefix": ".lithe/run/classes/", - "cases": [ - { - "name": "default package", - "source": "samples/Standalone.java", - "package": null, - "configurationId": "java-main:Standalone", - "outputDirectory": ".lithe/run/classes/java-main-Standalone", - "expectedMainClass": "Standalone", - "expectedClasspath": [".lithe/run/classes/java-main-Standalone"], - "expectedPreLaunchSteps": [ - { - "executable": { "toolchain": "project-jdk", "tool": "javac" }, - "arguments": ["-d", ".lithe/run/classes/java-main-Standalone", "samples/Standalone.java"] - } - ], - "expectedArguments": ["Standalone"] - }, - { - "name": "package qualified", - "source": "src/com/example/WorkerMain.java", - "package": "com.example", - "configurationId": "java-main:com.example.WorkerMain", - "outputDirectory": ".lithe/run/classes/java-main-com.example.WorkerMain", - "expectedMainClass": "com.example.WorkerMain", - "expectedClasspath": [".lithe/run/classes/java-main-com.example.WorkerMain"], - "expectedPreLaunchSteps": [ - { - "executable": { "toolchain": "project-jdk", "tool": "javac" }, - "arguments": ["-d", ".lithe/run/classes/java-main-com.example.WorkerMain", "src/com/example/WorkerMain.java"] - } - ], - "expectedArguments": ["com.example.WorkerMain"] - } - ] -} diff --git a/rust/fixtures/ide-api/calls.json b/rust/fixtures/ide-api/calls.json index 44ccd6cd..98d0f499 100644 --- a/rust/fixtures/ide-api/calls.json +++ b/rust/fixtures/ide-api/calls.json @@ -5,17 +5,10 @@ "name": "lithe_project_inspect", "arguments": {} }, - { - "name": "lithe_environment_configure", - "arguments": { - "javaHomePath": "", - "mavenExecutablePath": "mvnw" - } - }, { "name": "lithe_build_execute", "arguments": { - "instanceID": "maven@.", + "instanceID": "fixture@.", "taskID": "custom", "projectID": "services/api", "userArguments": [ @@ -43,39 +36,32 @@ { "name": "lithe_build_reload", "arguments": { - "instanceID": "maven@." - } - }, - { - "name": "lithe_maven_execute", - "arguments": { - "goals": [ - "clean", - "verify" - ], - "modulePath": "services/api" + "instanceID": "fixture@." } } ], "rejected": [ { - "name": "lithe_environment_configure", + "name": "lithe_build_configure", "arguments": { + "instanceID": "fixture@.", + "optionID": "token", + "value": "never-return-a-secret", "apiKey": "never-return-a-secret" } }, { "name": "lithe_build_execute", "arguments": { - "instanceID": "maven@.", + "instanceID": "fixture@.", "taskID": "verify", - "command": "mvn" + "command": "fixture-tool" } }, { "name": "lithe_build_execute", "arguments": { - "instanceID": "maven@.", + "instanceID": "fixture@.", "taskID": "verify", "userArguments": [ 42 @@ -93,23 +79,6 @@ "arguments": { "sessionID": "ambiguous-output-slot" } - }, - { - "name": "lithe_maven_execute", - "arguments": { - "goals": [ - "verify" - ], - "modulePath": "../outside" - } - }, - { - "name": "lithe_maven_execute", - "arguments": { - "goals": [ - "verify && echo unexpected" - ] - } } ] } diff --git a/rust/fixtures/ide-api/host-tool-exposure.json b/rust/fixtures/ide-api/host-tool-exposure.json index be938ef0..39fc5442 100644 --- a/rust/fixtures/ide-api/host-tool-exposure.json +++ b/rust/fixtures/ide-api/host-tool-exposure.json @@ -39,12 +39,6 @@ { "name": "lithe_project_inspect" }, - { - "name": "lithe_environment_inspect" - }, - { - "name": "lithe_environment_configure" - }, { "name": "lithe_run_list" }, @@ -63,18 +57,6 @@ { "name": "lithe_operation_stop" }, - { - "name": "lithe_maven_inspect" - }, - { - "name": "lithe_maven_configure" - }, - { - "name": "lithe_maven_reload" - }, - { - "name": "lithe_maven_execute" - }, { "name": "lithe_build_inspect" }, diff --git a/rust/fixtures/java/basic.json b/rust/fixtures/java/basic.json deleted file mode 100644 index 989b30b8..00000000 --- a/rust/fixtures/java/basic.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "Java structure", - "requests": [ - { - "command": "java.structure", - "payload": { - "source": "import a.A;\nimport b.B;\ninterface Service { String call(String value); }\n" - }, - "expected": { - "foldKind": "imports", - "syntaxRoles": [ - "functionDeclaration", - "keyword", - "parameter", - "punctuation", - "type" - ] - } - } - ] -} diff --git a/rust/fixtures/java/run-markers-v1.json b/rust/fixtures/java/run-markers-v1.json deleted file mode 100644 index 46795031..00000000 --- a/rust/fixtures/java/run-markers-v1.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "main-methods-including-nested-class", - "request": { - "mainMethods": [ - { - "mainClass": "demo.App", - "projectName": "app", - "range": { "startLine": 3, "startUtf16Column": 23, "endLine": 3, "endUtf16Column": 27 } - }, - { - "mainClass": "demo.App$Inner", - "projectName": "app", - "range": { "startLine": 6, "startUtf16Column": 27, "endLine": 6, "endUtf16Column": 31 } - } - ] - }, - "expected": { - "markers": [ - { "line": 3, "endLine": 3, "kind": "main", "label": "App.main()", "mainClass": "demo.App", "projectName": "app", "status": "none" }, - { "line": 6, "endLine": 6, "kind": "main", "label": "Inner.main()", "mainClass": "demo.App$Inner", "projectName": "app", "status": "none" } - ] - } - }, - { - "name": "nested-test-classes-with-recorded-outcomes", - "request": { - "testItems": [ - { - "id": "app@demo.OrderTest", - "label": "OrderTest", - "fullName": "demo.OrderTest", - "projectName": "app", - "testKind": 0, - "testLevel": 5, - "range": { "startLine": 5, "startUtf16Column": 6, "endLine": 14, "endUtf16Column": 1 }, - "children": [ - { - "id": "app@demo.OrderTest#creates()", - "label": "creates()", - "fullName": "demo.OrderTest#creates()", - "projectName": "app", - "testKind": 0, - "testLevel": 6, - "range": { "startLine": 7, "startUtf16Column": 9, "endLine": 7, "endUtf16Column": 21 }, - "children": [] - }, - { - "id": "app@demo.OrderTest$Refunds", - "label": "Refunds", - "fullName": "demo.OrderTest$Refunds", - "projectName": "app", - "testKind": 0, - "testLevel": 5, - "range": { "startLine": 10, "startUtf16Column": 10, "endLine": 13, "endUtf16Column": 5 }, - "children": [ - { - "id": "app@demo.OrderTest$Refunds#refunds()", - "label": "refunds()", - "fullName": "demo.OrderTest$Refunds#refunds()", - "projectName": "app", - "testKind": 0, - "testLevel": 6, - "range": { "startLine": 12, "startUtf16Column": 13, "endLine": 12, "endUtf16Column": 25 }, - "children": [] - } - ] - } - ] - } - ], - "testCases": [ - { "className": "demo.OrderTest", "method": "creates", "status": "passed", "message": null, "invocations": 1 }, - { "className": "demo.OrderTest$Refunds", "method": "refunds", "status": "failed", "message": "expected refund", "invocations": 1 } - ] - }, - "expected": { - "markers": [ - { "line": 5, "endLine": 14, "kind": "testClass", "label": "OrderTest", "testClass": "demo.OrderTest", "testItemId": "app@demo.OrderTest", "status": "failed" }, - { "line": 7, "endLine": 7, "kind": "testMethod", "label": "OrderTest.creates", "testClass": "demo.OrderTest", "testMethod": "creates", "testItemId": "app@demo.OrderTest#creates()", "status": "passed" }, - { "line": 10, "endLine": 13, "kind": "testClass", "label": "Refunds", "testClass": "demo.OrderTest$Refunds", "testItemId": "app@demo.OrderTest$Refunds", "status": "failed" }, - { "line": 12, "endLine": 12, "kind": "testMethod", "label": "Refunds.refunds", "testClass": "demo.OrderTest$Refunds", "testMethod": "refunds", "testItemId": "app@demo.OrderTest$Refunds#refunds()", "status": "failed" } - ] - } - }, - { - "name": "nested-test-classes-with-canonical-provider-outcomes", - "request": { - "testItems": [ - { - "id": "app@demo.OrderTest", - "label": "OrderTest", - "fullName": "demo.OrderTest", - "projectName": "app", - "testKind": 0, - "testLevel": 5, - "range": { - "startLine": 5, - "startUtf16Column": 6, - "endLine": 14, - "endUtf16Column": 1 - }, - "children": [ - { - "id": "app@demo.OrderTest#creates()", - "label": "creates()", - "fullName": "demo.OrderTest#creates()", - "projectName": "app", - "testKind": 0, - "testLevel": 6, - "range": { - "startLine": 7, - "startUtf16Column": 9, - "endLine": 7, - "endUtf16Column": 21 - }, - "children": [] - }, - { - "id": "app@demo.OrderTest$Refunds", - "label": "Refunds", - "fullName": "demo.OrderTest$Refunds", - "projectName": "app", - "testKind": 0, - "testLevel": 5, - "range": { - "startLine": 10, - "startUtf16Column": 10, - "endLine": 13, - "endUtf16Column": 5 - }, - "children": [ - { - "id": "app@demo.OrderTest$Refunds#refunds()", - "label": "refunds()", - "fullName": "demo.OrderTest$Refunds#refunds()", - "projectName": "app", - "testKind": 0, - "testLevel": 6, - "range": { - "startLine": 12, - "startUtf16Column": 13, - "endLine": 12, - "endUtf16Column": 25 - }, - "children": [] - } - ] - } - ] - } - ], - "testCases": [ - { - "status": "passed", - "message": null, - "invocations": 1, - "suite": "demo.OrderTest", - "case": "creates" - }, - { - "status": "failed", - "message": "expected refund", - "invocations": 1, - "suite": "demo.OrderTest$Refunds", - "case": "refunds" - } - ] - }, - "expected": { - "markers": [ - { - "line": 5, - "endLine": 14, - "kind": "testClass", - "label": "OrderTest", - "testClass": "demo.OrderTest", - "testItemId": "app@demo.OrderTest", - "status": "failed" - }, - { - "line": 7, - "endLine": 7, - "kind": "testMethod", - "label": "OrderTest.creates", - "testClass": "demo.OrderTest", - "testMethod": "creates", - "testItemId": "app@demo.OrderTest#creates()", - "status": "passed" - }, - { - "line": 10, - "endLine": 13, - "kind": "testClass", - "label": "Refunds", - "testClass": "demo.OrderTest$Refunds", - "testItemId": "app@demo.OrderTest$Refunds", - "status": "failed" - }, - { - "line": 12, - "endLine": 12, - "kind": "testMethod", - "label": "Refunds.refunds", - "testClass": "demo.OrderTest$Refunds", - "testMethod": "refunds", - "testItemId": "app@demo.OrderTest$Refunds#refunds()", - "status": "failed" - } - ] - } - } - ] -} diff --git a/rust/fixtures/lsp/java-build-report-v1.json b/rust/fixtures/lsp/java-build-report-v1.json deleted file mode 100644 index 94b69b11..00000000 --- a/rust/fixtures/lsp/java-build-report-v1.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "version": 1, - "continuableFailures": [ - { - "code": "javaBuildCompilationErrors", - "message": "The Java project build reported compilation errors.", - "javaBuildReport": { - "markerScope": "launchTarget", - "builderFailedEarlier": false, - "elapsedMilliseconds": 506, - "recovery": "rebuildJavaIndex" - } - }, - { - "code": "javaBuildFailed", - "message": "The Java language service could not complete the project build. Check the Java language server log for the build error.", - "javaBuildReport": { - "markerScope": "workspace", - "builderFailedEarlier": true, - "elapsedMilliseconds": 5012, - "recovery": "rebuildJavaIndex" - } - } - ], - "nonContinuableCodes": [ - "invalidServerResult", - "javaBuildCancelled", - "requestCancelled", - "requestTimeout" - ] -} diff --git a/rust/fixtures/lsp/java-completion-v1.json b/rust/fixtures/lsp/java-completion-v1.json deleted file mode 100644 index 224648fe..00000000 --- a/rust/fixtures/lsp/java-completion-v1.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 1, - "pluginCompletion": { - "guessMethodArguments": "insertParameterNames", - "favoriteStaticMembers": ["java.util.Objects.*"], - "filteredTypes": ["example.internal.*"] - }, - "javaSettings": { - "completion": { - "guessMethodArguments": "off", - "favoriteStaticMembers": ["java.util.Objects.*"], - "filteredTypes": ["example.internal.*"] - } - }, - "workspaceConfigurationSections": [ - "java", - "java.completion", - "java.completion.guessMethodArguments", - "java.completion.favoriteStaticMembers", - "java.completion.filteredTypes", - "java.completion.unknown" - ] -} diff --git a/rust/fixtures/lsp/java-launch-preparation-v1.json b/rust/fixtures/lsp/java-launch-preparation-v1.json deleted file mode 100644 index 59f3e91f..00000000 --- a/rust/fixtures/lsp/java-launch-preparation-v1.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "id": "windows-module-and-case", - "rootUri": "file:///C:/Work", - "sourcePath": "c:/work/APP/Main.java", - "mainClass": "demo.Main", - "entries": [ - { - "mainClass": "demo.Main", - "projectName": "lib", - "filePath": "C:/Work/lib/Main.java" - }, - { - "mainClass": "demo.app/demo.Main", - "projectName": "app", - "filePath": "C:/Work/app/Main.java" - }, - { - "mainClass": "demo.Other", - "projectName": "app", - "filePath": "C:/Work/app/Main.java" - } - ], - "paths": [ - [ - "C:/Work/custom/modules" - ], - [ - "C:/Work/custom/tests", - "C:/cache/test-only.jar" - ] - ], - "target": { - "mainClass": "demo.app/demo.Main", - "projectName": "app", - "modulePaths": [ - "C:/Work/custom/modules" - ], - "classPaths": [ - "C:/Work/custom/tests", - "C:/cache/test-only.jar" - ] - } - }, - { - "id": "mac-source-identity", - "rootUri": "file:///workspace", - "sourcePath": "/workspace/app/Main.java", - "mainClass": "demo.Main", - "entries": [ - { - "mainClass": "demo.Main", - "projectName": "lib", - "filePath": "/workspace/lib/Main.java" - }, - { - "mainClass": "demo.Main", - "projectName": "app", - "filePath": "/workspace/app/Main.java" - } - ], - "paths": [ - [], - [ - "/workspace/app/custom-output" - ] - ], - "target": { - "mainClass": "demo.Main", - "projectName": "app", - "modulePaths": [], - "classPaths": [ - "/workspace/app/custom-output" - ] - } - } - ] -} diff --git a/rust/fixtures/lsp/java-navigation-v1.json b/rust/fixtures/lsp/java-navigation-v1.json deleted file mode 100644 index f339e8bb..00000000 --- a/rust/fixtures/lsp/java-navigation-v1.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "four-marker-variants-use-deterministic-ordering", - "request": { - "sessionId": "session-1", - "uri": "file:///workspace/src/Service.java", - "documentVersion": 7 - }, - "expectedResult": { - "documentVersion": 7, - "markers": [ - { - "line": 2, - "utf16Column": 9, - "implementationCount": 1, - "direction": "up", - "relation": "interface" - }, - { - "line": 2, - "utf16Column": 9, - "implementationCount": 2, - "direction": "down", - "relation": "interface" - }, - { - "line": 8, - "utf16Column": 16, - "implementationCount": 1, - "direction": "up", - "relation": "inheritance" - }, - { - "line": 8, - "utf16Column": 16, - "implementationCount": 3, - "direction": "down", - "relation": "inheritance" - } - ] - } - }, - { - "name": "zero-target-declarations-are-omitted", - "request": { - "sessionId": "session-1", - "uri": "file:///workspace/src/Unused.java", - "documentVersion": 3 - }, - "expectedResult": { - "documentVersion": 3, - "markers": [] - } - } - ] -} diff --git a/rust/fixtures/lsp/java-project-runtimes-v1.json b/rust/fixtures/lsp/java-project-runtimes-v1.json deleted file mode 100644 index 10e1a51b..00000000 --- a/rust/fixtures/lsp/java-project-runtimes-v1.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "selected-custom-major-wins", - "candidates": [ - { - "homePath": "/bundled/jdk21", - "version": "21" - }, - { - "homePath": "/jdks/jdk25", - "version": "25" - }, - { - "homePath": "/custom/jdk25", - "version": "25.0.1", - "isDefault": true - }, - { - "homePath": "/jdks/jdk17", - "version": "17", - "isDefault": true - } - ], - "expected": [ - { - "name": "JavaSE-17", - "path": "/jdks/jdk17" - }, - { - "name": "JavaSE-21", - "path": "/bundled/jdk21" - }, - { - "name": "JavaSE-25", - "path": "/custom/jdk25", - "default": true - } - ] - }, - { - "name": "legacy-without-default", - "candidates": [ - { - "homePath": "/jdks/jdk21", - "version": "21" - } - ], - "expected": [ - { - "name": "JavaSE-21", - "path": "/jdks/jdk21" - } - ] - }, - { - "name": "invalid-default-is-ignored", - "candidates": [ - { - "homePath": "", - "version": "25", - "isDefault": true - }, - { - "homePath": "/jdks/jdk21", - "version": "21" - } - ], - "expected": [ - { - "name": "JavaSE-21", - "path": "/jdks/jdk21" - } - ] - } - ] -} diff --git a/rust/fixtures/lsp/java-workspace-policy-v1.json b/rust/fixtures/lsp/java-workspace-policy-v1.json deleted file mode 100644 index 8154b9fe..00000000 --- a/rust/fixtures/lsp/java-workspace-policy-v1.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "mixed-workspace", - "workspacePaths": [ - "README.md", - "node_modules/example/Fake.java", - "src/Main.java", - "target/generated/Generated.java" - ], - "changedPaths": [ - "README.md", - "build.gradle.kts", - "src/Main.java", - "target/generated/Generated.java" - ], - "expected": { - "shouldStart": true, - "representativeJavaPath": "src/Main.java", - "changes": [ - { "path": "README.md", "kind": "other" }, - { "path": "build.gradle.kts", "kind": "buildConfiguration" }, - { "path": "src/Main.java", "kind": "source" }, - { "path": "target/generated/Generated.java", "kind": "ignored" } - ] - } - }, - { - "name": "ignored-java-only", - "workspacePaths": [ - ".worktrees/topic/src/Topic.java", - "build/generated/Generated.java", - "node_modules/example/Fake.java" - ], - "changedPaths": [ - ".mvn/wrapper/maven-wrapper.properties", - "gradle/wrapper/gradle-wrapper.properties", - "settings.gradle" - ], - "expected": { - "shouldStart": false, - "changes": [ - { "path": ".mvn/wrapper/maven-wrapper.properties", "kind": "buildConfiguration" }, - { "path": "gradle/wrapper/gradle-wrapper.properties", "kind": "buildConfiguration" }, - { "path": "settings.gradle", "kind": "buildConfiguration" } - ] - } - }, - { - "name": "maven-project-with-conventional-layout", - "workspacePaths": [ - "pom.xml", - "src/main/java/com/example/demo/DemoApplication.java" - ], - "changedPaths": [], - "expected": { - "shouldStart": true, - "representativeJavaPath": "src/main/java/com/example/demo/DemoApplication.java", - "changes": [] - } - }, - { - "name": "java-fixture-inside-another-ecosystem", - "workspacePaths": [ - "Cargo.toml", - "Package.swift", - "rust/fixtures/projects/demo/pom.xml", - "rust/fixtures/projects/demo/src/main/java/com/example/demo/DemoApplication.java" - ], - "changedPaths": [], - "expected": { - "shouldStart": false, - "representativeJavaPath": "rust/fixtures/projects/demo/src/main/java/com/example/demo/DemoApplication.java", - "changes": [] - } - } - ] -} diff --git a/rust/fixtures/lsp/jdt-cache-retention-v1.json b/rust/fixtures/lsp/jdt-cache-retention-v1.json deleted file mode 100644 index 98f4b776..00000000 --- a/rust/fixtures/lsp/jdt-cache-retention-v1.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "expires-only-inactive-cache-older-than-thirty-days", - "nowUnixSeconds": 4000000, - "activeWorkspaceKey": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - "entries": [ - { - "workspaceKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "lastModifiedUnixSeconds": 1000000 - }, - { - "workspaceKey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "lastModifiedUnixSeconds": 1408000 - }, - { - "workspaceKey": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - "lastModifiedUnixSeconds": 1 - }, - { - "workspaceKey": "not-a-workspace-key", - "lastModifiedUnixSeconds": 1 - } - ], - "expected": { - "retentionDays": 30, - "expiredWorkspaceKeys": [ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - ] - } - }, - { - "name": "duplicate-observations-use-newest-timestamp", - "nowUnixSeconds": 4000000, - "entries": [ - { - "workspaceKey": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", - "lastModifiedUnixSeconds": 1 - }, - { - "workspaceKey": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", - "lastModifiedUnixSeconds": 3999999 - } - ], - "expected": { - "retentionDays": 30, - "expiredWorkspaceKeys": [] - } - } - ] -} diff --git a/rust/fixtures/lsp/jdt-direct-launch-v1.json b/rust/fixtures/lsp/jdt-direct-launch-v1.json deleted file mode 100644 index 90959256..00000000 --- a/rust/fixtures/lsp/jdt-direct-launch-v1.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "schemaVersion": 1, - "request": { - "providerId": "java", - "executablePath": "/opt/lithe/jdtls/bin/jdtls", - "arguments": [ - "--jvm-arg=-Duser.language=en" - ], - "environment": { - "JAVA_HOME": "/opt/lithe/jdk" - }, - "rootUri": "file:///workspace/project", - "workingDirectory": "/workspace/project", - "initializationOptions": null, - "runtimeExecutablePath": "/opt/lithe/jdk/bin/java", - "jdtlsLaunchResources": { - "launcherJarPath": "/opt/lithe/jdtls/plugins/org.eclipse.equinox.launcher_1.7.0.jar", - "configurationDirectory": "/opt/lithe/jdtls/config_mac", - "lombokAgentPath": "/opt/lithe/jdtls/lombok/lombok.jar", - "javaDebugBundlePath": "/opt/lithe/jdtls/java-debug/com.microsoft.java.debug.plugin-0.53.1.jar", - "javaExtensionBundlePaths": [ - "/opt/lithe/jdtls/java-debug/com.microsoft.java.debug.plugin-0.53.1.jar", - "/opt/lithe/jdtls/java-test/extensions/com.microsoft.java.test.plugin-0.42.0.jar" - ] - }, - "cacheDirectory": "/var/cache/lithe/language-servers", - "workspaceFingerprint": "build=|modules=|jdtls=1.55.0", - "initializeTimeoutMilliseconds": 30000, - "serviceReadyIdleTimeoutMilliseconds": 45000, - "serviceReadyAbsoluteTimeoutMilliseconds": 600000, - "requestTimeoutMilliseconds": 30000, - "shutdownTimeoutMilliseconds": 2000 - } -} diff --git a/rust/fixtures/lsp/jdt-readiness-v1.json b/rust/fixtures/lsp/jdt-readiness-v1.json deleted file mode 100644 index 2d1172b4..00000000 --- a/rust/fixtures/lsp/jdt-readiness-v1.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "schemaVersion": 1, - "idleWarningMilliseconds": 45000, - "absoluteTimeoutMilliseconds": 600000, - "cases": [ - { - "name": "quiet Maven import eventually becomes ready", - "steps": [ - { - "atMilliseconds": 0, - "action": "progress", - "percentage": 23 - }, - { - "atMilliseconds": 46000, - "action": "poll", - "warning": true, - "timeout": false - }, - { - "atMilliseconds": 60000, - "action": "poll", - "warning": false, - "timeout": false - }, - { - "atMilliseconds": 90000, - "action": "ready" - }, - { - "atMilliseconds": 601000, - "action": "poll", - "warning": false, - "timeout": false - } - ] - }, - { - "name": "quiet Maven import reaches the absolute deadline", - "steps": [ - { - "atMilliseconds": 46000, - "action": "poll", - "warning": true, - "timeout": false - }, - { - "atMilliseconds": 600000, - "action": "poll", - "warning": false, - "timeout": true, - "classification": "noProgressStall" - } - ] - }, - { - "name": "progress never extends the absolute deadline", - "steps": [ - { - "atMilliseconds": 599000, - "action": "progress", - "percentage": 99 - }, - { - "atMilliseconds": 600000, - "action": "poll", - "warning": false, - "timeout": true, - "classification": "projectImportActive" - } - ] - } - ] -} diff --git a/rust/fixtures/lsp/jdt-workspace-fingerprint-v1.json b/rust/fixtures/lsp/jdt-workspace-fingerprint-v1.json deleted file mode 100644 index 2e0103d8..00000000 --- a/rust/fixtures/lsp/jdt-workspace-fingerprint-v1.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "sorts-and-deduplicates-platform-observations", - "buildFiles": [ - { "path": "pom.xml" }, - { "path": "build.gradle.kts" }, - { "path": "pom.xml" } - ], - "directMavenModules": ["core", "api", "core"], - "jdtlsVersion": "1.38.0", - "workspaceFingerprint": "build=build.gradle.kts,pom.xml|modules=api,core|jdtls=1.38.0" - }, - { - "name": "gradle-wrapper-version-selects-the-state", - "buildFiles": [ - { "path": "settings.gradle.kts" }, - { "path": "build.gradle.kts" }, - { "path": "gradle/wrapper/gradle-wrapper.properties" } - ], - "directMavenModules": [], - "gradleWrapperDistribution": "https://services.gradle.org/distributions/gradle-8.10-bin.zip", - "jdtlsVersion": "1.38.0", - "workspaceFingerprint": "build=build.gradle.kts,gradle/wrapper/gradle-wrapper.properties,settings.gradle.kts|modules=|jdtls=1.38.0|gradle=https://services.gradle.org/distributions/gradle-8.10-bin.zip" - }, - { - "name": "version-only-workspace", - "buildFiles": [], - "directMavenModules": [], - "jdtlsVersion": "7.6.5", - "workspaceFingerprint": "build=|modules=|jdtls=7.6.5" - } - ] -} diff --git a/rust/fixtures/lsp/jdt-workspace-key-v1.json b/rust/fixtures/lsp/jdt-workspace-key-v1.json deleted file mode 100644 index 16655c8e..00000000 --- a/rust/fixtures/lsp/jdt-workspace-key-v1.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "legacy-workspace-path", - "workspaceRoot": "/workspace/demo", - "workspaceFingerprint": null, - "workspaceKey": "4de1d2f8b60db00ae8307a6fe19690b23893974b259297719107bb84e767148a" - }, - { - "name": "maven-modules-and-jdtls-version", - "workspaceRoot": "/workspace/demo", - "workspaceFingerprint": "build=pom.xml@1700000000000:42|modules=api,core|jdtls=1.38.0", - "workspaceKey": "b21826804a57043eec9f155c7ee324870f7df48d7d51a311e25cb04e9671df25" - } - ] -} diff --git a/rust/fixtures/lsp/maven-profile-events-v1.json b/rust/fixtures/lsp/maven-profile-events-v1.json deleted file mode 100644 index ed53dd6e..00000000 --- a/rust/fixtures/lsp/maven-profile-events-v1.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": 1, - "events": [ - { "type": "log", "sequence": 1, "providerId": "java", "sessionId": "java-session", "mavenProfileTask": "running" }, - { "type": "log", "sequence": 2, "providerId": "java", "sessionId": "java-session", "mavenProfileProject": { "projectUri": "file:///common-11111111", "status": "running" } }, - { "type": "stateChanged", "sequence": 3, "providerId": "java", "sessionId": "java-session", "state": "ready" }, - { "type": "log", "sequence": 4, "providerId": "java", "sessionId": "java-session", "mavenProfileProject": { "projectUri": "file:///common-11111111", "status": "failed", "errorDetails": "Maven profile project update failed." } }, - { "type": "log", "sequence": 5, "providerId": "java", "sessionId": "java-session", "mavenProfileProject": { "projectUri": "file:///common-22222222", "status": "succeeded" } }, - { "type": "log", "sequence": 6, "providerId": "java", "sessionId": "java-session", "mavenProfileTask": "partiallySucceeded" } - ] -} diff --git a/rust/fixtures/lsp/project-preparation-v1.json b/rust/fixtures/lsp/project-preparation-v1.json deleted file mode 100644 index eb84cdf6..00000000 --- a/rust/fixtures/lsp/project-preparation-v1.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "snapshots": [ - { "phase": "starting", "status": "loading", "blocksRun": true }, - { "phase": "importing", "status": "loading", "blocksRun": true }, - { "phase": "configuring", "status": "loading", "blocksRun": true }, - { "phase": "building", "status": "loading", "blocksRun": true }, - { "phase": "ready", "status": "ready", "blocksRun": false }, - { "phase": "configuring", "status": "failed", "blocksRun": false }, - { "phase": "stopped", "status": "idle", "blocksRun": true } - ] -} diff --git a/rust/fixtures/maven/basic.json b/rust/fixtures/maven/basic.json deleted file mode 100644 index 113eab02..00000000 --- a/rust/fixtures/maven/basic.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "recursive Maven project scan", - "request": { - "command": "maven.scan", - "payload": { - "root": "/workspace", - "paths": ["pom.xml", "module-a/pom.xml", "module-a/module-b/pom.xml"] - } - }, - "expected": { - "relativePath": ".", - "artifactId": "demo", - "packaging": "pom", - "sourceRoots": [], - "modules": [ - { - "relativePath": "module-a", - "sourceRoots": [ - { "path": "src/main/java", "kind": "mainJava" }, - { "path": "src/main/resources", "kind": "mainResources" }, - { "path": "src/test/java", "kind": "testJava" }, - { "path": "src/test/resources", "kind": "testResources" }, - { "path": "target/generated-sources", "kind": "generatedMain" }, - { "path": "target/generated-test-sources", "kind": "generatedTest" } - ], - "modules": [ - { - "relativePath": "module-a/module-b", - "sourceRoots": [ - { "path": "src/main/java", "kind": "mainJava" }, - { "path": "src/main/resources", "kind": "mainResources" }, - { "path": "src/test/java", "kind": "testJava" }, - { "path": "src/test/resources", "kind": "testResources" }, - { "path": "target/generated-sources", "kind": "generatedMain" }, - { "path": "target/generated-test-sources", "kind": "generatedTest" } - ] - } - ] - } - ], - "profiles": [ - { - "id": "dev", - "isActiveByDefault": true - } - ], - "hasWrapper": true - } -} diff --git a/rust/fixtures/maven/dependency-tree-v2.json b/rust/fixtures/maven/dependency-tree-v2.json deleted file mode 100644 index de3937b1..00000000 --- a/rust/fixtures/maven/dependency-tree-v2.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "version": 2, - "modulePath": "service", - "treeFile": "com.example:service:jar:1.0.0\n+- org.example:runtime-lib:jar:2.0:runtime\n| \\- org.example:transitive-lib:jar:1.5:runtime (scope updated from test)\n+- org.example:provided-lib:jar:3.0:provided (scope managed from compile)\n+- org.example:test-lib:jar:tests:4.0:test\n+- org.example:managed-lib:jar:2.1:compile (version managed from 0.9.0; scope not updated to compile)\n\\- org.example:compile-lib:jar:1.0:compile\n +- (org.example:duplicate-lib:jar:1.0:compile - version managed from 1.0; omitted for duplicate)\n \\- (org.example:conflict-lib:jar:1.0:compile - version managed from 1.1; omitted for conflict with 2.0)\n", - "expected": { - "modulePath": "service", - "dependencies": [ - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "compile-lib", - "version": "1.0", - "type": "jar", - "classifier": null, - "scope": "compile", - "resolution": "resolved", - "selectedVersion": null, - "premanagedVersion": null, - "premanagedScope": null, - "originalScope": null, - "ignoredScope": null, - "children": [ - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "conflict-lib", - "version": "1.0", - "type": "jar", - "classifier": null, - "scope": "compile", - "resolution": "omittedConflict", - "selectedVersion": "2.0", - "premanagedVersion": "1.1", - "premanagedScope": null, - "originalScope": null, - "ignoredScope": null, - "children": [] - }, - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "duplicate-lib", - "version": "1.0", - "type": "jar", - "classifier": null, - "scope": "compile", - "resolution": "omittedDuplicate", - "selectedVersion": null, - "premanagedVersion": "1.0", - "premanagedScope": null, - "originalScope": null, - "ignoredScope": null, - "children": [] - } - ] - }, - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "managed-lib", - "version": "2.1", - "type": "jar", - "classifier": null, - "scope": "compile", - "resolution": "resolved", - "selectedVersion": null, - "premanagedVersion": "0.9.0", - "premanagedScope": null, - "originalScope": null, - "ignoredScope": "compile", - "children": [] - }, - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "provided-lib", - "version": "3.0", - "type": "jar", - "classifier": null, - "scope": "provided", - "resolution": "resolved", - "selectedVersion": null, - "premanagedVersion": null, - "premanagedScope": "compile", - "originalScope": null, - "ignoredScope": null, - "children": [] - }, - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "runtime-lib", - "version": "2.0", - "type": "jar", - "classifier": null, - "scope": "runtime", - "resolution": "resolved", - "selectedVersion": null, - "premanagedVersion": null, - "premanagedScope": null, - "originalScope": null, - "ignoredScope": null, - "children": [ - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "transitive-lib", - "version": "1.5", - "type": "jar", - "classifier": null, - "scope": "runtime", - "resolution": "resolved", - "selectedVersion": null, - "premanagedVersion": null, - "premanagedScope": null, - "originalScope": "test", - "ignoredScope": null, - "children": [] - } - ] - }, - { - "modulePath": "service", - "groupId": "org.example", - "artifactId": "test-lib", - "version": "4.0", - "type": "jar", - "classifier": "tests", - "scope": "test", - "resolution": "resolved", - "selectedVersion": null, - "premanagedVersion": null, - "premanagedScope": null, - "originalScope": null, - "ignoredScope": null, - "children": [] - } - ] - } -} diff --git a/rust/fixtures/maven/diagnostics.json b/rust/fixtures/maven/diagnostics.json deleted file mode 100644 index 33df033f..00000000 --- a/rust/fixtures/maven/diagnostics.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "Maven compiler diagnostics", - "request": { - "command": "maven.diagnostics", - "payload": { - "root": "/workspace", - "output": "[ERROR] src/main/java/App.java:[12,4] cannot find symbol\n[WARNING] src/main/java/App.java:[4] unused import\n" - } - }, - "expected": { - "issues": [ - { - "path": "src/main/java/App.java", - "line": 12, - "column": 4, - "severity": "error", - "message": "cannot find symbol" - }, - { - "path": "src/main/java/App.java", - "line": 4, - "column": null, - "severity": "warning", - "message": "unused import" - } - ] - } -} diff --git a/rust/fixtures/maven/launch-plan-v1.json b/rust/fixtures/maven/launch-plan-v1.json deleted file mode 100644 index 8d95427e..00000000 --- a/rust/fixtures/maven/launch-plan-v1.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "nested reactor module lifecycle with local settings", - "context": { - "version": 1, - "reactorPath": "projects/demo", - "profiles": ["qa", "dev", "dev"], - "settingsPath": "/Users/example/.m2/settings.xml", - "localRepositoryPath": "/Users/example/.m2/custom-repository", - "skipTests": true, - "mavenExecutablePath": "/opt/apache-maven/bin/mvn", - "javaHomePath": "/Library/Java/JavaVirtualMachines/example/Contents/Home" - }, - "module": "service-api", - "goals": ["verify"], - "expected": { - "version": 1, - "executable": { "toolchain": "project-maven" }, - "arguments": [ - "-B", - "-ntp", - "-P", - "dev,qa", - "-s", - "/Users/example/.m2/settings.xml", - "-Dmaven.repo.local=/Users/example/.m2/custom-repository", - "-pl", - "service-api", - "-am", - "-DskipTests", - "verify" - ], - "workingDirectory": "projects/demo", - "configurationFingerprint": "sha256:0e84a43d287c8e889ff15740114e8de0d719cbf3ca6b6ce3530b97e164770f4a" - } - }, - { - "name": "root reactor custom goal preserves maven config behavior", - "context": { - "version": 1, - "reactorPath": ".", - "profiles": [], - "skipTests": false - }, - "module": null, - "goals": ["spring-boot:run"], - "expected": { - "version": 1, - "executable": { "toolchain": "project-maven" }, - "arguments": ["-B", "-ntp", "spring-boot:run"], - "workingDirectory": ".", - "configurationFingerprint": "sha256:65a5de6ae2a3135ad844ed30ba8558865f97a6696654a83d3d1d8544cc63380f" - } - }, - { - "name": "custom goal keeps property and quiet arguments as argv", - "context": { - "version": 1, - "reactorPath": ".", - "profiles": [], - "skipTests": false - }, - "module": null, - "goals": [ - "help:evaluate", - "-Dexpression=fixture.config", - "-q", - "-DforceStdout" - ], - "expected": { - "version": 1, - "executable": { "toolchain": "project-maven" }, - "arguments": [ - "-B", - "-ntp", - "help:evaluate", - "-Dexpression=fixture.config", - "-q", - "-DforceStdout" - ], - "workingDirectory": ".", - "configurationFingerprint": "sha256:65a5de6ae2a3135ad844ed30ba8558865f97a6696654a83d3d1d8544cc63380f" - } - } - ] -} diff --git a/rust/fixtures/maven/platform-contract-v1.json b/rust/fixtures/maven/platform-contract-v1.json deleted file mode 100644 index afa7ca9b..00000000 --- a/rust/fixtures/maven/platform-contract-v1.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "version": 1, - "lifecyclePhases": [ - "clean", - "validate", - "compile", - "test", - "package", - "verify", - "install", - "site", - "deploy" - ], - "storageIdentityCases": [ - { - "name": "macOS preserves workspace path case", - "platform": "macos", - "workspacePath": "/Users/Example/Projects/Lithe", - "reactorPath": "services/api", - "expectedIdentity": "/Users/Example/Projects/Lithe\u0000services/api" - }, - { - "name": "Windows folds workspace case and normalizes reactor separators", - "platform": "windows", - "workspacePath": "C:\\Users\\Example\\Projects\\Lithe", - "reactorPath": "services\\api", - "expectedIdentity": "c:\\users\\example\\projects\\lithe\u0000services/api" - } - ] -} diff --git a/rust/fixtures/maven/source-roots-v1.json b/rust/fixtures/maven/source-roots-v1.json deleted file mode 100644 index ac76765b..00000000 --- a/rust/fixtures/maven/source-roots-v1.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "version": 1, - "kindOrder": [ - "mainJava", - "mainResources", - "testJava", - "testResources", - "generatedMain", - "generatedTest" - ], - "cases": [ - { - "name": "standard module", - "sourceRoots": [ - { "path": "src/main/java", "kind": "mainJava" }, - { "path": "src/main/resources", "kind": "mainResources" }, - { "path": "src/test/java", "kind": "testJava" }, - { "path": "src/test/resources", "kind": "testResources" }, - { "path": "target/generated-sources", "kind": "generatedMain" }, - { "path": "target/generated-test-sources", "kind": "generatedTest" } - ] - }, - { - "name": "configured module", - "sourceRoots": [ - { "path": "custom/java", "kind": "mainJava" }, - { "path": "custom/resources", "kind": "mainResources" }, - { "path": "custom/test-java", "kind": "testJava" }, - { "path": "custom/test-resources", "kind": "testResources" }, - { "path": "target/generated-sources/annotations", "kind": "generatedMain" }, - { "path": "target/generated-test-sources", "kind": "generatedTest" } - ] - }, - { - "name": "custom build directory", - "sourceRoots": [ - { "path": "src/main/java", "kind": "mainJava" }, - { "path": "src/main/resources", "kind": "mainResources" }, - { "path": "src/test/java", "kind": "testJava" }, - { "path": "src/test/resources", "kind": "testResources" }, - { "path": "build-output/generated-sources/annotations", "kind": "generatedMain" }, - { "path": "build-output/generated-test-sources/fixtures", "kind": "generatedTest" } - ] - }, - { - "name": "execution-scoped generated sources", - "sourceRoots": [ - { "path": "src/main/java", "kind": "mainJava" }, - { "path": "src/main/resources", "kind": "mainResources" }, - { "path": "src/test/java", "kind": "testJava" }, - { "path": "src/test/resources", "kind": "testResources" }, - { "path": "target/generated-sources/openapi", "kind": "generatedMain" }, - { "path": "target/generated-test-sources/fixtures", "kind": "generatedTest" } - ] - } - ] -} diff --git a/rust/fixtures/maven/test-results-v1.json b/rust/fixtures/maven/test-results-v1.json deleted file mode 100644 index cea6a710..00000000 --- a/rust/fixtures/maven/test-results-v1.json +++ /dev/null @@ -1,304 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "junit5-failure-and-error", - "sourceFiles": ["src/test/java/com/example/CalculatorTest.java"], - "output": "[ERROR] Failures:\n[ERROR] additionIsCorrect(com.example.CalculatorTest): expected <4> but was <5>\n[ERROR] at com.example.CalculatorTest.additionIsCorrect(CalculatorTest.java:42)\n[ERROR] Errors:\n[ERROR] contextLoads(com.example.CalculatorTest): application context failed\n[ERROR] at com.example.CalculatorTest.contextLoads(CalculatorTest.java:55)\n[ERROR] Tests run: 4, Failures: 1, Errors: 1, Skipped: 1\n", - "expected": { - "testsRun": 4, - "failures": 1, - "errors": 1, - "skipped": 1, - "passed": 1, - "success": false, - "failureDetails": [ - { - "name": "additionIsCorrect(com.example.CalculatorTest)", - "kind": "failure", - "message": "expected <4> but was <5>", - "path": "src/test/java/com/example/CalculatorTest.java", - "line": 42, - "column": null - }, - { - "name": "contextLoads(com.example.CalculatorTest)", - "kind": "error", - "message": "application context failed", - "path": "src/test/java/com/example/CalculatorTest.java", - "line": 55, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "junit4-pass", - "sourceFiles": [], - "output": "[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 2, - "failures": 0, - "errors": 0, - "skipped": 0, - "passed": 2, - "success": true, - "failureDetails": [], - "testCases": [] - } - }, - { - "name": "surefire-3-detailed-failure-with-reactor-footer", - "sourceFiles": ["src/test/java/review/AppTest.java"], - "output": "[ERROR] review.AppTest.targetFailure -- Time elapsed: 0.023 s <<< FAILURE!\norg.opentest4j.AssertionFailedError: expected review failure\n at org.junit.jupiter.api.Assertions.fail(Assertions.java:138)\n at review.AppTest.targetFailure(AppTest.java:4)\n\n[INFO] Results:\n[ERROR] Failures:\n[ERROR] AppTest.targetFailure:4 expected review failure\n[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n[INFO] Reactor Summary for reactor 1.0-SNAPSHOT:\n[INFO] base ................................ SUCCESS\n[INFO] app ................................ FAILURE\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "review.AppTest.targetFailure", - "kind": "failure", - "message": "expected review failure", - "path": "src/test/java/review/AppTest.java", - "line": 4, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "surefire-3-parameterized-detailed-failure", - "sourceFiles": ["src/test/java/review/AppTest.java"], - "output": "[ERROR] review.AppTest.targetFailure(int)[1] -- Time elapsed: 0.023 s <<< FAILURE!\njava.lang.AssertionError: expected review failure\n at review.AppTest.targetFailure(AppTest.java:4)\n\n[INFO] Results:\n[ERROR] Failures:\n[ERROR] targetFailure(int)[1](review.AppTest):4 expected review failure\n[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "review.AppTest.targetFailure(int)[1]", - "kind": "failure", - "message": "expected review failure", - "path": "src/test/java/review/AppTest.java", - "line": 4, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "surefire-2-detailed-failure", - "sourceFiles": ["src/test/java/review/AppTest.java"], - "output": "[ERROR] fails(review.AppTest) Time elapsed: 0.01 s <<< FAILURE!\njava.lang.AssertionError: expected review failure\n at review.AppTest.fails(AppTest.java:9)\n[INFO] Results:\n[ERROR] Failures:\n[ERROR] fails(review.AppTest): expected review failure\n[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "fails(review.AppTest)", - "kind": "failure", - "message": "expected review failure", - "path": "src/test/java/review/AppTest.java", - "line": 9, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "qualified-source-wins-over-simple-name", - "sourceFiles": [ - "module-a/src/test/java/other/AppTest.java", - "module-b/src/test/java/review/AppTest.java" - ], - "output": "[ERROR] review.AppTest.fails -- Time elapsed: 0.01 s <<< FAILURE!\njava.lang.AssertionError: expected review failure\n at review.AppTest.fails(AppTest.java:12)\n[INFO] Results:\n[ERROR] Failures:\n[ERROR] AppTest.fails:12 expected review failure\n[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "review.AppTest.fails", - "kind": "failure", - "message": "expected review failure", - "path": "module-b/src/test/java/review/AppTest.java", - "line": 12, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "ambiguous-qualified-source-has-no-location", - "sourceFiles": [ - "module-a/src/test/java/review/AppTest.java", - "module-b/src/test/java/review/AppTest.java" - ], - "output": "[ERROR] review.AppTest.fails -- Time elapsed: 0.01 s <<< FAILURE!\njava.lang.AssertionError: expected review failure\n at review.AppTest.fails(AppTest.java:12)\n[INFO] Results:\n[ERROR] Failures:\n[ERROR] AppTest.fails:12 expected review failure\n[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "review.AppTest.fails", - "kind": "failure", - "message": "expected review failure", - "path": null, - "line": null, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "junit5-parameterized-nested-module", - "sourceFiles": ["service/src/test/java/com/example/CalculatorTest.java"], - "output": "[ERROR] Failures:\n[ERROR] additionIsCorrect(int)[1](com.example.CalculatorTest): expected <4> but was <5>\n[ERROR] at com.example.CalculatorTest.additionIsCorrect(CalculatorTest.java:42)\n[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "additionIsCorrect(int)[1](com.example.CalculatorTest)", - "kind": "failure", - "message": "expected <4> but was <5>", - "path": "service/src/test/java/com/example/CalculatorTest.java", - "line": 42, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "packaged-source-wins-over-root-same-name", - "sourceFiles": [ - "src/test/java/AppTest.java", - "service/src/test/java/com/example/AppTest.java" - ], - "output": "[ERROR] com.example.AppTest.fails -- Time elapsed: 0.023 s <<< FAILURE!\njava.lang.AssertionError: expected failure\n at com.example.AppTest.fails(AppTest.java:8)\n\n[ERROR] Failures:\n[ERROR] com.example.AppTest.fails:8 expected failure\n[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "com.example.AppTest.fails", - "kind": "failure", - "message": "expected failure", - "path": "service/src/test/java/com/example/AppTest.java", - "line": 8, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "duplicate-packaged-source-has-no-location", - "sourceFiles": [ - "service-one/src/test/java/com/example/AppTest.java", - "service-two/src/test/java/com/example/AppTest.java" - ], - "output": "[ERROR] com.example.AppTest.fails -- Time elapsed: 0.023 s <<< FAILURE!\njava.lang.AssertionError: expected failure\n at com.example.AppTest.fails(AppTest.java:8)\n\n[ERROR] Failures:\n[ERROR] com.example.AppTest.fails:8 expected failure\n[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "com.example.AppTest.fails", - "kind": "failure", - "message": "expected failure", - "path": null, - "line": null, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "surefire-legacy-detailed-failure", - "sourceFiles": ["src/test/java/review/AppTest.java"], - "output": "review.AppTest.targetFailure Time elapsed: 0.01 sec <<< FAILURE!\njava.lang.AssertionError: expected legacy failure\n at org.junit.Assert.fail(Assert.java:88)\n at review.AppTest.targetFailure(AppTest.java:4)\n\nFailed tests:\n targetFailure(review.AppTest):4 expected legacy failure\n\nTests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "review.AppTest.targetFailure", - "kind": "failure", - "message": "expected legacy failure", - "path": "src/test/java/review/AppTest.java", - "line": 4, - "column": null - } - ], - "testCases": [] - } - }, - { - "name": "junit4-failed-tests-footer-only", - "sourceFiles": ["src/test/java/review/AppTest.java"], - "output": "Failed tests:\n targetFailure(review.AppTest):4 expected legacy failure\n\nTests run: 1, Failures: 1, Errors: 0, Skipped: 0\n", - "expected": { - "testsRun": 1, - "failures": 1, - "errors": 0, - "skipped": 0, - "passed": 0, - "success": false, - "failureDetails": [ - { - "name": "targetFailure(review.AppTest)", - "kind": "failure", - "message": "expected legacy failure", - "path": "src/test/java/review/AppTest.java", - "line": 4, - "column": null - } - ], - "testCases": [] - } - } - ] -} diff --git a/rust/fixtures/maven/testcase-durations-v1.json b/rust/fixtures/maven/testcase-durations-v1.json deleted file mode 100644 index 114330b6..00000000 --- a/rust/fixtures/maven/testcase-durations-v1.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "xml": "\n\n\n\n\n\n\n\n\n\n\n\n", - "expected": [ - { - "className": "demo.DurationTest", - "method": "fast", - "status": "passed", - "message": null, - "invocations": 1, - "durationMillis": 10 - }, - { - "className": "demo.DurationTest", - "method": "invalid", - "status": "passed", - "message": null, - "invocations": 1 - }, - { - "className": "demo.DurationTest", - "method": "missing", - "status": "passed", - "message": null, - "invocations": 1 - }, - { - "className": "demo.DurationTest", - "method": "negative", - "status": "passed", - "message": null, - "invocations": 1 - }, - { - "className": "demo.DurationTest", - "method": "overflow", - "status": "passed", - "message": null, - "invocations": 1 - }, - { - "className": "demo.DurationTest", - "method": "partial", - "status": "passed", - "message": null, - "invocations": 2 - }, - { - "className": "demo.DurationTest", - "method": "repeat", - "status": "passed", - "message": null, - "invocations": 2, - "durationMillis": 50 - }, - { - "className": "demo.DurationTest", - "method": "slow", - "status": "passed", - "message": null, - "invocations": 1, - "durationMillis": 125 - }, - { - "className": "demo.DurationTest", - "method": "zero", - "status": "passed", - "message": null, - "invocations": 1, - "durationMillis": 0 - } - ] -} diff --git a/rust/fixtures/modules/built-in-v1.json b/rust/fixtures/modules/built-in-v1.json index aafa878d..d4a529e1 100644 --- a/rust/fixtures/modules/built-in-v1.json +++ b/rust/fixtures/modules/built-in-v1.json @@ -58,7 +58,6 @@ "dependencies": ["dev.lithe.workspace"], "capabilities": ["dev.lithe.capability.execution-workspace"], "contributions": [ - { "id": "execution.maven", "kind": "toolWindow", "actionID": "execution.maven.toggle", "rendererID": "execution.maven" }, { "id": "execution.run", "kind": "toolWindow", "actionID": "execution.run.toggle", "rendererID": "execution.run" }, { "id": "execution.tests", "kind": "toolWindow", "actionID": "execution.tests.toggle", "rendererID": "execution.tests" } ], diff --git a/rust/fixtures/mybatis/basic.json b/rust/fixtures/mybatis/basic.json deleted file mode 100644 index 76a40ce3..00000000 --- a/rust/fixtures/mybatis/basic.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "MyBatis mapper XML navigation index", - "request": { - "command": "mybatis.index", - "payload": { - "root": "/fixture/workspace", - "paths": [ - "src/main/java/example/UserMapper.java", - "src/main/resources/mapper/UserMapper.xml" - ], - "textOverrides": { - "src/main/resources/mapper/UserMapper.xml": "" - } - } - }, - "expected": { - "statement": { - "namespace": "example.UserMapper", - "statementId": "selectById", - "kind": "select", - "javaPath": "src/main/java/example/UserMapper.java", - "xmlPath": "src/main/resources/mapper/UserMapper.xml" - } - } -} diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/.classpath b/rust/fixtures/projects/lithe-spring-boot-git-graph/.classpath deleted file mode 100644 index 4e7f6701..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/.classpath +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/.project b/rust/fixtures/projects/lithe-spring-boot-git-graph/.project deleted file mode 100644 index 441ab09e..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/.project +++ /dev/null @@ -1,34 +0,0 @@ - - - lithe-spring-boot-git-graph - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - - - 1785591560813 - - 30 - - org.eclipse.core.resources.regexFilterMatcher - node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ - - - - diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.core.resources.prefs b/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index abdea9ac..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding/=UTF-8 diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.apt.core.prefs b/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.apt.core.prefs deleted file mode 100644 index d4313d4b..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.apt.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.apt.aptEnabled=false diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.core.prefs b/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index e60fa3f3..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,10 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.methodParameters=generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 -org.eclipse.jdt.core.compiler.compliance=21 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.processAnnotations=disabled -org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=21 diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.m2e.core.prefs b/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/README.md b/rust/fixtures/projects/lithe-spring-boot-git-graph/README.md deleted file mode 100644 index 284e6e94..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Lithe Spring Boot Git Graph Fixture - -这是 Git Graph 的单一验收项目,不参与 Lithe 本身的构建。 - -项目模拟一个常见的 Spring Boot 用户服务,配套脚本会生成: - -- `main` 主线 -- `feature/users` 分支及真实 Merge Commit -- `feature/orders` 的 Rebase 历史 -- Tag `v0.1.0` -- `origin/main` 远程引用 - -生成: - -```bash -./scripts/git-graph/create-git-graph-fixture.sh -``` diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/history/OrderController-feature.java b/rust/fixtures/projects/lithe-spring-boot-git-graph/history/OrderController-feature.java deleted file mode 100644 index a5451a8a..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/history/OrderController-feature.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.demo.order; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/orders") -public class OrderController { - @GetMapping("/health") - public String health() { - return "ok"; - } -} diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/history/UserService-feature.java b/rust/fixtures/projects/lithe-spring-boot-git-graph/history/UserService-feature.java deleted file mode 100644 index cb649964..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/history/UserService-feature.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.demo.user; - -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public class UserService { - private final UserRepository repository; - - public UserService(UserRepository repository) { - this.repository = repository; - } - - public List listUsers() { - return repository.findAll().stream().toList(); - } -} diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/pom.xml b/rust/fixtures/projects/lithe-spring-boot-git-graph/pom.xml deleted file mode 100644 index 98da55eb..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.3.5 - - - - com.example - lithe-spring-boot-git-graph - 0.1.0-SNAPSHOT - lithe-spring-boot-git-graph - - - 21 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/DemoApplication.java b/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/DemoApplication.java deleted file mode 100644 index 1bc95549..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/DemoApplication.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.demo; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class DemoApplication { - public static void main(String[] args) { - SpringApplication.run(DemoApplication.class, args); - } -} diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserController.java b/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserController.java deleted file mode 100644 index abdefbac..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.demo.user; - -import jakarta.validation.constraints.NotBlank; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -@Validated -@RestController -@RequestMapping("/api/users") -public class UserController { - private final UserService service; - - public UserController(UserService service) { - this.service = service; - } - - @GetMapping - public List list() { - return service.listUsers(); - } - - public record UserQuery(@NotBlank String tenant) {} -} diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserRepository.java b/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserRepository.java deleted file mode 100644 index 2e1a0888..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.demo.user; - -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public class UserRepository { - public List findAll() { - return List.of( - new UserSummary(1L, "Ada Lovelace"), - new UserSummary(2L, "Grace Hopper") - ); - } - - public record UserSummary(Long id, String name) {} -} diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserService.java b/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserService.java deleted file mode 100644 index 2c2cf882..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/java/com/example/demo/user/UserService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.demo.user; - -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public class UserService { - private final UserRepository repository; - - public UserService(UserRepository repository) { - this.repository = repository; - } - - public List listUsers() { - return repository.findAll(); - } -} diff --git a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/resources/application.yml b/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/resources/application.yml deleted file mode 100644 index 5befe7a5..00000000 --- a/rust/fixtures/projects/lithe-spring-boot-git-graph/src/main/resources/application.yml +++ /dev/null @@ -1,5 +0,0 @@ -spring: - application: - name: lithe-spring-boot-git-graph -server: - port: 8080 diff --git a/rust/fixtures/run-configuration/automatic-java-selection.json b/rust/fixtures/run-configuration/automatic-java-selection.json deleted file mode 100644 index 93c355ef..00000000 --- a/rust/fixtures/run-configuration/automatic-java-selection.json +++ /dev/null @@ -1,284 +0,0 @@ -{ - "version": 1, - "cases": [ - { - "name": "skip-incompatible-java-home", - "minimum": "17", - "candidates": [ - { - "id": "env", - "version": "1.8.0_402", - "priority": 0 - }, - { - "id": "path", - "version": "17.0.12", - "priority": 1 - }, - { - "id": "latest", - "version": "21.0.4", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "compatible-java-home-wins", - "minimum": "17", - "candidates": [ - { - "id": "env", - "version": "17.0.12", - "priority": 0 - }, - { - "id": "latest", - "version": "21.0.4", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "numeric-version-order", - "minimum": "8", - "candidates": [ - { - "id": "eleven", - "version": "11.0.22", - "priority": 2 - }, - { - "id": "twenty-one", - "version": "21.0.4", - "priority": 2 - }, - { - "id": "nine", - "version": "9.0.4", - "priority": 2 - } - ], - "fallbackId": "eleven" - }, - { - "name": "numeric-patch-order", - "minimum": "17", - "candidates": [ - { - "id": "nine", - "version": "17.0.9", - "priority": 2 - }, - { - "id": "ten", - "version": "17.0.10", - "priority": 2 - } - ], - "fallbackId": "nine" - }, - { - "name": "legacy-java-eight", - "minimum": "8", - "candidates": [ - { - "id": "env", - "version": "1.8.0_402", - "priority": 0 - }, - { - "id": "latest", - "version": "21.0.4", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "stable-tie", - "minimum": "17", - "candidates": [ - { - "id": "z", - "version": "17.0.12", - "priority": 2 - }, - { - "id": "a", - "version": "17.0.12", - "priority": 2 - } - ], - "fallbackId": "z" - }, - { - "name": "exact-feature-beats-newer-java-home", - "minimum": "8", - "candidates": [ - { - "id": "env", - "version": "21.0.4", - "priority": 0 - }, - { - "id": "zulu8", - "version": "1.8.0_504", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "oldest-newer-when-exact-missing", - "minimum": "11", - "candidates": [ - { - "id": "env", - "version": "21.0.4", - "priority": 0 - }, - { - "id": "seventeen", - "version": "17.0.12", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "priority-breaks-ties-within-feature", - "minimum": "17", - "candidates": [ - { - "id": "detected", - "version": "17.0.14", - "priority": 2 - }, - { - "id": "path", - "version": "17.0.12", - "priority": 1 - }, - { - "id": "latest", - "version": "21.0.4", - "priority": 0 - } - ], - "fallbackId": "latest" - }, - { - "name": "unsatisfied-retains-fallback", - "minimum": "21", - "candidates": [ - { - "id": "env", - "version": "8.0.402", - "priority": 0 - }, - { - "id": "other", - "version": "17.0.12", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "missing-requirement-retains-fallback", - "minimum": null, - "candidates": [ - { - "id": "env", - "version": "8.0.402", - "priority": 0 - }, - { - "id": "latest", - "version": "21.0.4", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "unknown-version-is-not-compatible", - "minimum": "17", - "candidates": [ - { - "id": "env", - "version": "", - "priority": 0 - }, - { - "id": "other", - "version": "17.0.12", - "priority": 2 - } - ], - "fallbackId": "env" - }, - { - "name": "no-runtimes", - "minimum": "17", - "candidates": [], - "fallbackId": null - } - ], - "expected": { - "skip-incompatible-java-home": { - "id": "path", - "warning": false - }, - "compatible-java-home-wins": { - "id": "env", - "warning": false - }, - "numeric-version-order": { - "id": "nine", - "warning": false - }, - "numeric-patch-order": { - "id": "ten", - "warning": false - }, - "legacy-java-eight": { - "id": "env", - "warning": false - }, - "stable-tie": { - "id": "a", - "warning": false - }, - "unsatisfied-retains-fallback": { - "id": "env", - "warning": true - }, - "missing-requirement-retains-fallback": { - "id": "env", - "warning": false - }, - "unknown-version-is-not-compatible": { - "id": "other", - "warning": false - }, - "no-runtimes": { - "id": null, - "warning": true - }, - "exact-feature-beats-newer-java-home": { - "id": "zulu8", - "warning": false - }, - "oldest-newer-when-exact-missing": { - "id": "seventeen", - "warning": false - }, - "priority-breaks-ties-within-feature": { - "id": "path", - "warning": false - } - } -} diff --git a/rust/fixtures/run-configuration/basic.json b/rust/fixtures/run-configuration/basic.json deleted file mode 100644 index aae03705..00000000 --- a/rust/fixtures/run-configuration/basic.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 1, - "generated": { - "version": 1, - "configurations": [ - { - "id": "spring:com.example.DemoApplication", - "name": "DemoApplication", - "type": "spring-boot.maven", - "module": ".", - "mainClass": "com.example.DemoApplication", - "toolchains": { "java": "project-jdk", "maven": "project-maven" }, - "workingDirectory": ".", - "jvmArguments": [], - "programArguments": [], - "mavenProfiles": [] - } - ] - }, - "team": { "version": 1, "configurations": [] }, - "local": { "version": 1, "configurations": [] }, - "expected": { "selectedId": "spring:com.example.DemoApplication", "launchToolchain": "project-maven" } -} diff --git a/rust/fixtures/run-configuration/build-provider.json b/rust/fixtures/run-configuration/build-provider.json index bf80250f..40ab29cd 100644 --- a/rust/fixtures/run-configuration/build-provider.json +++ b/rust/fixtures/run-configuration/build-provider.json @@ -16,8 +16,8 @@ "unrelated": { "version": 2, "toolchain": { - "java": { - "homePath": "/fixture/jdk" + "owner-field": { + "homePath": "/fixture/runtime" } }, "configurations": [] diff --git a/rust/fixtures/run-configuration/configuration-toolchain-selection.json b/rust/fixtures/run-configuration/configuration-toolchain-selection.json deleted file mode 100644 index 7e6217c2..00000000 --- a/rust/fixtures/run-configuration/configuration-toolchain-selection.json +++ /dev/null @@ -1,446 +0,0 @@ -{ - "description": "Canonical reactor paths plus explicit Run overrides, without applying project defaults as configuration overrides or writing source documents.", - "cases": [ - { - "name": "explicit configuration paths override canonical project defaults", - "request": { - "path": ".", - "configurationId": "maven-run", - "localDocument": { - "version": 2, - "toolchain": { - "java": { - "homePath": "C:/Global JDK" - }, - "maven": { - "executablePath": "C:/Global Maven/bin/mvn.cmd", - "javaHomePath": "C:/Global Maven JDK" - }, - "pathOverrides": { - "reactor": { - "javaHomePath": "C:/Reactor JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Reactor Maven JDK" - } - } - }, - "configurations": [ - { - "id": "maven-run", - "extensions": { - "java": { - "homePath": " C:/Run JDK ", - "mavenExecutablePath": "C:/Run Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Run Maven JDK" - } - } - } - ] - } - }, - "generatedDocument": { - "version": 2, - "configurations": [ - { - "id": "maven-run", - "name": "Module", - "provider": "maven.module", - "execution": "task", - "cwd": ".", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": "." - } - } - } - ] - } - }, - { - "name": "empty configuration values inherit the canonical reactor instead of global defaults", - "request": { - "path": "reactor", - "configurationId": "maven-run", - "localDocument": { - "version": 2, - "toolchain": { - "java": { - "homePath": "C:/Global JDK" - }, - "maven": { - "executablePath": "C:/Global Maven/bin/mvn.cmd", - "javaHomePath": "C:/Global Maven JDK" - }, - "pathOverrides": { - "reactor": { - "javaHomePath": "C:/Reactor JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Reactor Maven JDK" - } - } - }, - "configurations": [ - { - "id": "maven-run", - "extensions": { - "java": { - "homePath": "", - "mavenExecutablePath": "", - "mavenJavaHomePath": "" - } - } - } - ] - } - }, - "generatedDocument": { - "version": 2, - "configurations": [ - { - "id": "maven-run", - "name": "Module", - "provider": "maven.module", - "execution": "task", - "cwd": "reactor", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": "." - } - } - } - ] - } - }, - { - "name": "a configuration cannot select another provider root", - "request": { - "path": ".", - "configurationId": "maven-run", - "localDocument": { - "version": 2, - "toolchain": { - "java": { - "homePath": "C:/Global JDK" - }, - "maven": { - "executablePath": "C:/Global Maven/bin/mvn.cmd", - "javaHomePath": "C:/Global Maven JDK" - }, - "pathOverrides": { - "reactor": { - "javaHomePath": "C:/Reactor JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Reactor Maven JDK" - } - } - }, - "configurations": [ - { - "id": "maven-run", - "extensions": { - "java": {} - } - } - ] - } - }, - "generatedDocument": { - "version": 2, - "configurations": [ - { - "id": "maven-run", - "name": "Module", - "provider": "maven.module", - "execution": "task", - "cwd": "other", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": "." - } - } - } - ] - } - }, - { - "name": "an unknown configuration cannot fall back to project paths", - "request": { - "path": ".", - "configurationId": "missing-run", - "localDocument": { - "version": 2, - "toolchain": { - "java": { - "homePath": "C:/Global JDK" - }, - "maven": { - "executablePath": "C:/Global Maven/bin/mvn.cmd", - "javaHomePath": "C:/Global Maven JDK" - }, - "pathOverrides": { - "reactor": { - "javaHomePath": "C:/Reactor JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Reactor Maven JDK" - } - } - }, - "configurations": [ - { - "id": "maven-run", - "extensions": { - "java": {} - } - } - ] - } - }, - "generatedDocument": { - "version": 2, - "configurations": [ - { - "id": "maven-run", - "name": "Module", - "provider": "maven.module", - "execution": "task", - "cwd": ".", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": "." - } - } - } - ] - } - }, - { - "name": "malformed explicit paths cannot fall back to a valid default", - "request": { - "path": ".", - "configurationId": "maven-run", - "localDocument": { - "version": 2, - "toolchain": { - "java": { - "homePath": "C:/Global JDK" - }, - "maven": { - "executablePath": "C:/Global Maven/bin/mvn.cmd", - "javaHomePath": "C:/Global Maven JDK" - }, - "pathOverrides": { - "reactor": { - "javaHomePath": "C:/Reactor JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Reactor Maven JDK" - } - } - }, - "configurations": [ - { - "id": "maven-run", - "extensions": { - "java": { - "mavenExecutablePath": false - } - } - } - ] - } - }, - "generatedDocument": { - "version": 2, - "configurations": [ - { - "id": "maven-run", - "name": "Module", - "provider": "maven.module", - "execution": "task", - "cwd": ".", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": "." - } - } - } - ] - } - }, - { - "name": "team and local explicit fields reuse the existing Run merge", - "request": { - "path": "reactor", - "configurationId": "maven-run", - "localDocument": { - "version": 2, - "toolchain": { - "java": { - "homePath": "C:/Global JDK" - }, - "maven": { - "executablePath": "C:/Global Maven/bin/mvn.cmd", - "javaHomePath": "C:/Global Maven JDK" - }, - "pathOverrides": { - "reactor": { - "javaHomePath": "C:/Reactor JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Reactor Maven JDK" - } - } - }, - "configurations": [ - { - "id": "maven-run", - "extensions": { - "java": { - "mavenExecutablePath": "", - "mavenJavaHomePath": "C:/Local Maven JDK" - } - } - } - ] - } - }, - "teamDocument": { - "version": 2, - "configurations": [ - { - "id": "maven-run", - "extensions": { - "java": { - "homePath": "C:/Team JDK", - "mavenExecutablePath": "C:/Team Maven/bin/mvn.cmd" - } - } - } - ] - }, - "generatedDocument": { - "version": 2, - "configurations": [ - { - "id": "maven-run", - "name": "Module", - "provider": "maven.module", - "execution": "task", - "cwd": "reactor", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": "." - } - } - } - ] - } - }, - { - "name": "saved selectors inherit canonical paths without a generated layer", - "request": { - "path": "reactor", - "configurationId": "user:reference", - "localDocument": { - "version": 2, - "toolchain": { - "maven": { - "executablePath": "C:/Global Maven/bin/mvn.cmd" - }, - "pathOverrides": { - "reactor": { - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd" - } - } - }, - "configurations": [ - { - "id": "user:reference", - "name": "Reference", - "provider": "build.provider", - "execution": "task", - "extensions": { - "build": { - "pluginID": "dev.example.build", - "buildSystemID": "example", - "root": "reactor", - "run": "module", - "projectID": "p", - "targetID": "t", - "execution": "task", - "userArguments": [] - } - } - } - ] - } - } - } - ], - "version": 1, - "expected": { - "explicit configuration paths override canonical project defaults": { - "toolchain": { - "javaHomePath": "C:/Run JDK", - "mavenExecutablePath": "C:/Run Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Run Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "empty configuration values inherit the canonical reactor instead of global defaults": { - "toolchain": { - "javaHomePath": "C:/Reactor JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Reactor Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "a configuration cannot select another provider root": { - "errorCode": "invalid_request" - }, - "an unknown configuration cannot fall back to project paths": { - "errorCode": "invalid_request" - }, - "malformed explicit paths cannot fall back to a valid default": { - "errorCode": "parse_failed" - }, - "team and local explicit fields reuse the existing Run merge": { - "toolchain": { - "javaHomePath": "C:/Team JDK", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Local Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "saved selectors inherit canonical paths without a generated layer": { - "toolchain": { - "javaHomePath": "", - "mavenExecutablePath": "C:/Reactor Maven/bin/mvn.cmd", - "mavenJavaHomePath": "", - "runtimeExecutablePaths": {} - } - } - } -} diff --git a/rust/fixtures/run-configuration/editor-save.json b/rust/fixtures/run-configuration/editor-save.json index 9afa8597..591ea348 100644 --- a/rust/fixtures/run-configuration/editor-save.json +++ b/rust/fixtures/run-configuration/editor-save.json @@ -5,24 +5,22 @@ "payload": { "root": "/workspace", "scope": "project", - "configurationId": "spring", + "configurationId": "server", "workingDirectory": "backend", - "jvmArguments": "-Xmx2g", "arguments": "--dev", - "environment": { "APP_ENV": "dev" }, - "mavenProfiles": [], - "javaHomePath": "", - "mavenExecutablePath": "", - "mavenJavaHomePath": "", + "environment": { + "APP_ENV": "dev" + }, "toolchain": { - "javaHomePath": "C:/lithe-fixture/jdk-21", - "mavenExecutablePath": "C:/lithe-fixture/apache-maven", - "mavenJavaHomePath": "C:/lithe-fixture/jdk-17" + "runtimeExecutablePaths": { + "project-runtime": "C:/lithe-fixture/runtime/bin/runtime.exe" + } } } }, "expected": { "localDocument": "string", - "projectDocument": "string" + "projectDocument": "string", + "toolchainDocument": "string" } } diff --git a/rust/fixtures/run-configuration/hybrid-service-web.json b/rust/fixtures/run-configuration/hybrid-service-web.json new file mode 100644 index 00000000..b2591df4 --- /dev/null +++ b/rust/fixtures/run-configuration/hybrid-service-web.json @@ -0,0 +1,58 @@ +{ + "version": 1, + "generated": { + "version": 2, + "configurations": [ + { + "id": "build.service:backend", + "name": "backend", + "provider": "build.service", + "execution": "service", + "cwd": ".", + "toolchains": { + "runtime": "project-runtime", + "build": "project-build" + } + }, + { + "id": "npm.script:web/dev", + "name": "dev", + "provider": "npm.script", + "execution": "service", + "command": "npm", + "args": [ + "run", + "dev" + ], + "cwd": "web", + "toolchains": { + "runtime": "project-node" + } + } + ] + }, + "requirements": { + "version": 1, + "toolchains": { + "project-runtime": { + "type": "runtime", + "minimumVersion": "21" + }, + "project-build": { + "type": "build-tool" + }, + "project-node": { + "type": "node", + "minimumVersion": "22" + } + } + }, + "expected": { + "missingNodeDiagnosticConfigurationIds": [ + "npm.script:web/dev" + ], + "unblockedConfigurationIds": [ + "build.service:backend" + ] + } +} diff --git a/rust/fixtures/run-configuration/hybrid-spring-vue.json b/rust/fixtures/run-configuration/hybrid-spring-vue.json deleted file mode 100644 index 1b341033..00000000 --- a/rust/fixtures/run-configuration/hybrid-spring-vue.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "version": 1, - "generated": { - "version": 2, - "configurations": [ - { - "id": "spring-boot.maven:backend", - "name": "backend", - "provider": "spring-boot.maven", - "execution": "service", - "cwd": ".", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - } - }, - { - "id": "npm.script:web/dev", - "name": "dev", - "provider": "npm.script", - "execution": "service", - "command": "npm", - "args": ["run", "dev"], - "cwd": "web", - "toolchains": { - "runtime": "project-node" - } - } - ] - }, - "requirements": { - "version": 1, - "toolchains": { - "project-jdk": { "type": "java", "minimumVersion": "21" }, - "project-maven": { "type": "maven", "java": "project-jdk" }, - "project-node": { "type": "node", "minimumVersion": "22" } - } - }, - "expected": { - "missingNodeDiagnosticConfigurationIds": ["npm.script:web/dev"], - "unblockedConfigurationIds": ["spring-boot.maven:backend"] - } -} diff --git a/rust/fixtures/run-configuration/invalid-version.json b/rust/fixtures/run-configuration/invalid-version.json deleted file mode 100644 index cc7a973f..00000000 --- a/rust/fixtures/run-configuration/invalid-version.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 1, - "generated": { "version": 2, "configurations": [] }, - "team": { "version": 1, "configurations": [] }, - "local": { "version": 1, "configurations": [] }, - "expected": { "errorCode": "not_supported" } -} diff --git a/rust/fixtures/run-configuration/maven-module-ownership.json b/rust/fixtures/run-configuration/maven-module-ownership.json deleted file mode 100644 index 23dad637..00000000 --- a/rust/fixtures/run-configuration/maven-module-ownership.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "version": 1, - "files": { - "services/alpha/pom.xml": "4.0.0examplealpha1", - "services/beta/pom.xml": "4.0.0examplebeta1", - "services/alpha/src/main/java/example/Alpha.java": "package example; public class Alpha { public static void main(String[] args) {} }", - "services/beta/src/main/java/example/Beta.java": "package example; public class Beta { public static void main(String[] args) {} }" - }, - "paths": [ - "services/alpha/src/main/java/example/Alpha.java", - "services/beta/src/main/java/example/Beta.java" - ], - "javaEntrypoints": { - "schemaVersion": 1, - "entries": [ - { - "sourcePath": "services/alpha/src/main/java/example/Alpha.java", - "mainClass": "example.Alpha", - "projectName": "alpha" - }, - { - "sourcePath": "services/beta/src/main/java/example/Beta.java", - "mainClass": "example.Beta", - "projectName": "beta" - } - ], - "diagnostics": [] - }, - "local": { - "version": 2, - "configurations": [ - { - "id": "java-main:example.Alpha", - "cwd": ".", - "extensions": { - "maven": { - "reactorPath": "services/beta" - } - } - } - ] - }, - "expected": { - "configurations": [ - { - "id": "java-main:example.Alpha", - "name": "Alpha", - "provider": "java.main", - "execution": "application", - "cwd": ".", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": ".", - "reactorPath": "services/alpha" - } - } - }, - { - "id": "java-main:example.Beta", - "name": "Beta", - "provider": "java.main", - "execution": "application", - "cwd": "services/beta", - "toolchains": { - "java": "project-jdk", - "maven": "project-maven" - }, - "extensions": { - "maven": { - "module": ".", - "reactorPath": "services/beta" - } - } - }, - { - "id": "current-file", - "name": "Current File", - "provider": "java.current-file", - "execution": "application", - "cwd": ".", - "debug": { - "adapter": "jdwp" - } - } - ] - } -} diff --git a/rust/fixtures/run-configuration/multi-module.json b/rust/fixtures/run-configuration/multi-module.json deleted file mode 100644 index d9cc4bbc..00000000 --- a/rust/fixtures/run-configuration/multi-module.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "version": 1, - "generated": { - "version": 1, - "configurations": [ - { - "id": "module:backend", - "name": "backend", - "type": "maven.module", - "module": "backend", - "toolchains": { "java": "project-jdk", "maven": "project-maven" }, - "workingDirectory": ".", - "jvmArguments": [], - "programArguments": [], - "mavenProfiles": [] - }, - { - "id": "module:worker", - "name": "worker", - "type": "maven.module", - "module": "worker", - "toolchains": { "java": "project-jdk", "maven": "project-maven" }, - "workingDirectory": ".", - "jvmArguments": [], - "programArguments": [], - "mavenProfiles": [] - } - ] - }, - "team": { - "version": 1, - "configurations": [{ "id": "module:worker", "mavenProfiles": ["dev"] }] - }, - "local": { - "version": 1, - "configurations": [{ "id": "module:backend", "jvmArguments": ["-Xmx2g"] }] - }, - "expected": { "configurationCount": 2, "selectedId": "module:backend" } -} diff --git a/rust/fixtures/run-configuration/no-entry.json b/rust/fixtures/run-configuration/no-entry.json deleted file mode 100644 index 636fb87f..00000000 --- a/rust/fixtures/run-configuration/no-entry.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 1, - "generated": { "version": 1, "configurations": [] }, - "team": { "version": 1, "configurations": [] }, - "local": { "version": 1, "configurations": [] }, - "expected": { "configurationCount": 0, "state": "noEntries" } -} diff --git a/rust/fixtures/run-configuration/project-environment.json b/rust/fixtures/run-configuration/project-environment.json deleted file mode 100644 index bf70b300..00000000 --- a/rust/fixtures/run-configuration/project-environment.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "version": 1, - "local": { - "version": 2, - "configurations": [ - { "id": "user:service", "extensions": { "java": { "homePath": "/fixture/service-jdk" } } } - ] - }, - "toolchain": { - "javaHomePath": "/fixture/project-jdk", - "mavenExecutablePath": "/fixture/maven", - "mavenJavaHomePath": "", - "pathOverrides": {} - }, - "expected": { - "toolchain": { - "java": { "homePath": "/fixture/project-jdk" }, - "maven": { "executablePath": "/fixture/maven", "javaHomePath": "" }, - "pathOverrides": {} - } - } -} diff --git a/rust/fixtures/run-configuration/project-toolchain-selection.json b/rust/fixtures/run-configuration/project-toolchain-selection.json deleted file mode 100644 index b3a538c0..00000000 --- a/rust/fixtures/run-configuration/project-toolchain-selection.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "description": "Read-only canonical Run tool paths for a Provider root; platforms discover executables after shared selection.", - "cases": [ - { - "name": "defaults without generated configurations", - "request": { - "path": ".", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - } - } - } - } - }, - { - "name": "nearest reactor override", - "request": { - "path": "reactor/app", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - }, - "pathOverrides": { - "reactor": { - "mavenExecutablePath": "C:/Reactor Maven/mvn.cmd" - } - } - } - } - } - }, - { - "name": "nearest empty fields inherit project rather than an ancestor", - "request": { - "path": "reactor/app/sub", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - }, - "pathOverrides": { - "reactor": { - "javaHomePath": "C:/Parent JDK" - }, - "reactor/app": { - "mavenExecutablePath": "C:/App Maven/mvn.cmd", - "javaHomePath": "" - } - } - } - } - } - }, - { - "name": "path segments do not match adjacent project names", - "request": { - "path": "reactor/app2", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - }, - "pathOverrides": { - "reactor/app": { - "javaHomePath": "C:/Wrong JDK" - } - } - } - } - } - }, - { - "name": "explicit automatic override inherits project defaults", - "request": { - "path": "reactor", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - }, - "pathOverrides": { - "reactor": {} - } - } - } - } - }, - { - "name": "missing toolchain stays automatic", - "request": { - "path": ".", - "localDocument": { - "version": 2, - "configurations": [] - } - } - }, - { - "name": "escaping owner", - "request": { - "path": "../outside", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - } - } - } - } - }, - { - "name": "noncanonical owner", - "request": { - "path": "reactor\\app", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - } - } - } - } - }, - { - "name": "empty owner", - "request": { - "path": "", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - } - } - } - } - }, - { - "name": "ambiguous owner", - "request": { - "path": "reactor/./app", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - } - } - } - } - }, - { - "name": "unknown document version", - "request": { - "path": ".", - "localDocument": { - "version": 99 - } - } - }, - { - "name": "malformed tool path", - "request": { - "path": ".", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": 42 - } - } - } - } - }, - { - "name": "malformed override", - "request": { - "path": "app", - "localDocument": { - "version": 2, - "configurations": [], - "toolchain": { - "java": { - "homePath": " C:/Example JDK " - }, - "maven": { - "executablePath": "C:/Example Maven/bin/mvn.cmd", - "javaHomePath": "C:/Maven JDK" - }, - "pathOverrides": { - "app": { - "unknownChoice": "value" - } - } - } - } - } - } - ], - "version": 1, - "expected": { - "defaults without generated configurations": { - "toolchain": { - "javaHomePath": "C:/Example JDK", - "mavenExecutablePath": "C:/Example Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "nearest reactor override": { - "toolchain": { - "javaHomePath": "C:/Example JDK", - "mavenExecutablePath": "C:/Reactor Maven/mvn.cmd", - "mavenJavaHomePath": "C:/Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "nearest empty fields inherit project rather than an ancestor": { - "toolchain": { - "javaHomePath": "C:/Example JDK", - "mavenExecutablePath": "C:/App Maven/mvn.cmd", - "mavenJavaHomePath": "C:/Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "path segments do not match adjacent project names": { - "toolchain": { - "javaHomePath": "C:/Example JDK", - "mavenExecutablePath": "C:/Example Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "explicit automatic override inherits project defaults": { - "toolchain": { - "javaHomePath": "C:/Example JDK", - "mavenExecutablePath": "C:/Example Maven/bin/mvn.cmd", - "mavenJavaHomePath": "C:/Maven JDK", - "runtimeExecutablePaths": {} - } - }, - "missing toolchain stays automatic": { - "toolchain": { - "javaHomePath": "", - "mavenExecutablePath": "", - "mavenJavaHomePath": "", - "runtimeExecutablePaths": {} - } - }, - "escaping owner": { - "errorCode": "invalid_request" - }, - "noncanonical owner": { - "errorCode": "invalid_request" - }, - "empty owner": { - "errorCode": "invalid_request" - }, - "ambiguous owner": { - "errorCode": "invalid_request" - }, - "unknown document version": { - "errorCode": "not_supported" - }, - "malformed tool path": { - "errorCode": "parse_failed" - }, - "malformed override": { - "errorCode": "invalid_request" - } - } -} diff --git a/rust/fixtures/run-configuration/provider-prepared-java.json b/rust/fixtures/run-configuration/provider-prepared-java.json deleted file mode 100644 index 3a594fc7..00000000 --- a/rust/fixtures/run-configuration/provider-prepared-java.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "javaLaunch": { - "mainClass": "com.example.MainTests", - "classPaths": [ - "/workspace/target/test-classes" - ], - "modulePaths": [], - "buildPrepared": true - }, - "version": 1, - "expected": { - "arguments": [ - "com.example.MainTests" - ], - "hasPreLaunchSteps": false - } -} diff --git a/rust/fixtures/run-configuration/provider-project-ownership.json b/rust/fixtures/run-configuration/provider-project-ownership.json deleted file mode 100644 index 76f47307..00000000 --- a/rust/fixtures/run-configuration/provider-project-ownership.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "version": 1, - "files": { - "alpha/pom.xml": "99", - "alpha/app/pom.xml": "", - "alpha/app/custom-checks/demo/Main.java": "package demo; class Main { public static void main(String[] args) {} }", - "beta/pom.xml": "", - "beta/app/sources/demo/Main.java": "package demo; @org.springframework.boot.autoconfigure.SpringBootApplication class Main { public static void main(String[] args) {} }", - ".java-version": "11", - "alpha/mvnw.cmd": "exit /b 99" - }, - "javaEntrypoints": { - "schemaVersion": 1, - "entries": [ - { - "sourcePath": "alpha/app/custom-checks/demo/Main.java", - "mainClass": "demo.Main", - "projectName": "alpha-app" - }, - { - "sourcePath": "beta/app/sources/demo/Main.java", - "mainClass": "demo.Main", - "projectName": "beta-app" - } - ], - "diagnostics": [] - }, - "projectOwnership": [ - { - "path": "alpha", - "contentRoots": [], - "name": "reactor", - "build": { - "pluginID": "dev.example.engine", - "buildSystemID": "engine", - "root": "alpha", - "projectID": "alpha" - } - }, - { - "path": "alpha/app", - "contentRoots": [ - { - "path": "alpha/app/custom-checks", - "kind": "testSource" - }, - { - "path": "alpha/app/generated-checks", - "kind": "generatedTestSource" - } - ], - "name": "app", - "minimumJavaVersion": "17", - "build": { - "pluginID": "dev.example.engine", - "buildSystemID": "engine", - "root": "alpha", - "projectID": "alpha/app" - } - }, - { - "path": "beta/app", - "contentRoots": [ - { - "path": "beta/app/sources", - "kind": "source" - } - ], - "name": "app", - "minimumJavaVersion": "21", - "build": { - "pluginID": "dev.example.engine", - "buildSystemID": "engine", - "root": "beta", - "projectID": "beta/app" - } - } - ], - "expected": { - "configurations": [ - { - "id": "java-main:demo.Main:alpha/app", - "cwd": "alpha", - "module": "app", - "sourceSet": "test" - }, - { - "id": "java-main:demo.Main:beta/app", - "cwd": "beta", - "module": "app", - "sourceSet": "main" - } - ], - "services": [ - { - "id": "build.provider:41b56f3f0d9046e60aad2d801b216bcc301a0e515dcf749413fbabff07b0ece1", - "cwd": "alpha", - "reference": { - "pluginID": "dev.example.engine", - "buildSystemID": "engine", - "root": "alpha", - "run": "serve", - "projectID": "opaque-project-1", - "targetID": "http/opaque:1", - "execution": "service", - "userArguments": [] - } - }, - { - "id": "build.provider:0d63f34f97ab396280a2b2b3226ad1a539db4d4cc35232c79a6ec4acba568ac2", - "cwd": "beta", - "reference": { - "pluginID": "dev.example.engine", - "buildSystemID": "engine", - "root": "beta", - "run": "serve", - "projectID": "opaque-project-2", - "targetID": "http/opaque:2", - "execution": "service", - "userArguments": [] - } - } - ], - "minimumJavaVersion": "21" - }, - "providerRuns": [ - { - "name": "Application service", - "reference": { - "pluginID": "dev.example.engine", - "buildSystemID": "engine", - "root": "alpha", - "run": "serve", - "projectID": "opaque-project-1", - "targetID": "http/opaque:1", - "execution": "service", - "userArguments": [] - }, - "projectPath": "alpha/app" - }, - { - "name": "Application service", - "reference": { - "pluginID": "dev.example.engine", - "buildSystemID": "engine", - "root": "beta", - "run": "serve", - "projectID": "opaque-project-2", - "targetID": "http/opaque:2", - "execution": "service", - "userArguments": [] - }, - "projectPath": "beta/app" - } - ] -} diff --git a/rust/fixtures/run-configuration/single-module.json b/rust/fixtures/run-configuration/single-module.json deleted file mode 100644 index 94f6fbd1..00000000 --- a/rust/fixtures/run-configuration/single-module.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 1, - "generated": { - "version": 1, - "configurations": [ - { - "id": "spring:com.example.App", - "name": "App", - "type": "spring-boot.maven", - "module": ".", - "mainClass": "com.example.App", - "toolchains": { "java": "project-jdk", "maven": "project-maven" }, - "workingDirectory": ".", - "jvmArguments": [], - "programArguments": [], - "mavenProfiles": [] - } - ] - }, - "team": { "version": 1, "configurations": [] }, - "local": { "version": 1, "configurations": [] }, - "expected": { "configurationCount": 1, "selectedId": "spring:com.example.App" } -} diff --git a/rust/fixtures/run-configuration/subproject-toolchains.json b/rust/fixtures/run-configuration/subproject-toolchains.json deleted file mode 100644 index 03e10773..00000000 --- a/rust/fixtures/run-configuration/subproject-toolchains.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "version": 1, - "toolchain": { - "javaHomePath": "/fixture/project-jdk", - "pathOverrides": { - "services/a": { - "javaHomePath": "/fixture/a-jdk", - "mavenExecutablePath": "/fixture/a maven/bin/mvn", - "mavenJavaHomePath": "" - }, - "services/b": { - "javaHomePath": "", - "mavenExecutablePath": "./mvnw", - "mavenJavaHomePath": "/fixture/b-maven-jdk" - } - } - }, - "invalidRoots": [ - "", - ".", - "..", - "../outside", - "/absolute", - "a/../b", - "a/./b", - "a//b", - "a/", - "C:/absolute", - "a\\b" - ], - "expected": { - "pathOverrides": { - "services/a": { - "javaHomePath": "/fixture/a-jdk", - "mavenExecutablePath": "/fixture/a maven/bin/mvn", - "mavenJavaHomePath": "" - }, - "services/b": { - "javaHomePath": "", - "mavenExecutablePath": "./mvnw", - "mavenJavaHomePath": "/fixture/b-maven-jdk" - } - } - } -} diff --git a/rust/fixtures/run-configuration/toolchain-mismatch.json b/rust/fixtures/run-configuration/toolchain-mismatch.json deleted file mode 100644 index 15a29a3b..00000000 --- a/rust/fixtures/run-configuration/toolchain-mismatch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": 1, - "requirements": { - "version": 1, - "toolchains": { - "project-jdk": { "type": "java", "minimumVersion": "21", "preferredVendor": "temurin" } - } - }, - "candidates": [ - { "id": "project-jdk", "type": "java", "version": "17.0.12", "vendor": "Zulu" } - ], - "expected": { - "diagnosticCodes": ["toolchainVersionMismatch", "toolchainVendorMismatch"] - } -} diff --git a/rust/fixtures/spring/basic.json b/rust/fixtures/spring/basic.json deleted file mode 100644 index 1d5c134c..00000000 --- a/rust/fixtures/spring/basic.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "Spring workspace semantic index", - "request": { - "command": "spring.index", - "payload": { - "root": "/fixture/workspace", - "paths": [ - "src/main/java/example/DemoProperties.java", - "src/main/java/example/GreetingController.java", - "src/main/resources/application-dev.yml" - ], - "metadataRepositories": [ - "/fixture/gradle-repository", - "/fixture/maven-repository" - ], - "refreshDependencyMetadata": true, - "textOverrides": { - "src/main/resources/application-dev.yml": "demo:\n enabled: true\n" - } - } - }, - "expected": { - "property": { - "name": "demo.enabled", - "sourcePath": "src/main/java/example/DemoProperties.java", - "sourceLine": 5 - }, - "value": { - "path": "src/main/resources/application-dev.yml", - "profile": "dev", - "overridesBaseValue": false - }, - "propertyReference": { - "key": "demo.enabled" - }, - "injection": { - "typeName": "GreetingService", - "qualifier": "primaryGreeting" - }, - "endpoint": { - "httpMethods": ["GET"], - "route": "/api/greeting" - } - } -} diff --git a/rust/lithe-core/Cargo.toml b/rust/lithe-core/Cargo.toml index f5ddd096..17aeb1fa 100644 --- a/rust/lithe-core/Cargo.toml +++ b/rust/lithe-core/Cargo.toml @@ -35,8 +35,6 @@ sha2 = "0.10" shell-words = "=1.1.1" toml = { version = "1.1.4", default-features = false, features = ["parse", "serde"] } toml_edit = "0.25" -tree-sitter = "0.25" -tree-sitter-java = "0.23" serde_yaml_ng = "0.10.0" similar = { version = "3.2", default-features = false, features = ["std"] } zip = { version = "2.4", default-features = false, features = ["deflate"] } diff --git a/rust/lithe-core/include/lithe_core.h b/rust/lithe-core/include/lithe_core.h index fbcc8145..ad4c2da1 100644 --- a/rust/lithe-core/include/lithe_core.h +++ b/rust/lithe-core/include/lithe_core.h @@ -16,7 +16,6 @@ char *lithe_core_execute_json(const char *request); /* Immutable source borrowed only until return; response uses lithe_core_free_string. */ char *lithe_core_execute_document_json(const char *request, const uint8_t *bytes, size_t length); char *lithe_core_execute_json_with_events(const char *request, void (*callback)(const char *, void *), void *context); -char *lithe_core_lsp_provider_catalog_json(const char *workspace_root); int32_t lithe_core_cancel(const char *operation_id); void lithe_core_free_string(char *value); void lithe_core_install_log_waker(void (*callback)(void *), void *context); diff --git a/rust/lithe-core/resources/lsp/language-providers.json b/rust/lithe-core/resources/lsp/language-providers.json deleted file mode 100644 index 4275a3d3..00000000 --- a/rust/lithe-core/resources/lsp/language-providers.json +++ /dev/null @@ -1,500 +0,0 @@ -{ - "version": 2, - "providers": [ - { - "id": "java", - "displayName": "Java", - "fileExtensions": ["java"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "java", - "languageServerLaunch": { - "executableNames": ["jdtls"], - "arguments": [] - }, - "languageServerInstallation": { - "homebrewFormula": "jdtls", - "officialDownloadURL": "https://download.eclipse.org/jdtls/milestones/" - } - }, - { - "id": "go", - "displayName": "Go", - "fileExtensions": ["go"], - "capabilities": ["run", "languageServer", "debugAdapter", "formatting", "testing"], - "activationPolicy": "onDemand", - "languageId": "go", - "languageServerLaunch": { - "executableNames": ["gopls"], - "arguments": [] - }, - "languageServerInstallation": { - "homebrewFormula": "gopls", - "officialDownloadURL": "https://go.dev/gopls/" - } - }, - { - "id": "python", - "displayName": "Python", - "fileExtensions": ["py", "pyw"], - "capabilities": ["run", "languageServer", "formatting", "testing"], - "activationPolicy": "onDemand", - "languageId": "python", - "languageServerLaunch": { - "executableNames": ["basedpyright-langserver", "pyright-langserver"], - "arguments": ["--stdio"] - }, - "languageServerInstallation": { - "homebrewFormula": "pyright", - "officialDownloadURL": "https://github.com/microsoft/pyright" - } - }, - { - "id": "node", - "displayName": "Node.js", - "fileExtensions": ["js", "jsx", "ts", "tsx", "mjs", "cjs"], - "capabilities": ["run", "languageServer", "debugAdapter", "formatting", "testing"], - "activationPolicy": "onDemand", - "languageId": "javascript", - "languageServerLaunch": { - "executableNames": ["typescript-language-server"], - "arguments": ["--stdio"] - }, - "languageServerInstallation": { - "homebrewFormula": "typescript-language-server", - "officialDownloadURL": "https://github.com/typescript-language-server/typescript-language-server" - }, - "languageIdsByExtension": { - "jsx": "javascriptreact", - "ts": "typescript", - "tsx": "typescriptreact" - } - }, - { - "id": "rust", - "displayName": "Rust", - "fileExtensions": ["rs"], - "capabilities": ["run", "languageServer", "formatting", "testing"], - "activationPolicy": "onDemand", - "languageId": "rust", - "languageServerLaunch": { - "executableNames": ["rust-analyzer"], - "arguments": [], - "validationArguments": ["--version"] - }, - "languageServerInstallation": { - "homebrewFormula": "rust-analyzer", - "officialDownloadURL": "https://rust-analyzer.github.io/manual.html#installation" - } - }, - { - "id": "clangd", - "displayName": "C/C++/Objective-C", - "fileExtensions": ["c", "h", "hh", "hpp", "hxx", "cpp", "cc", "cxx", "m", "mm"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "cpp", - "languageServerLaunch": { - "executableNames": ["clangd"], - "arguments": [] - }, - "languageServerInstallation": { - "homebrewFormula": "llvm", - "officialDownloadURL": "https://clangd.llvm.org/installation" - }, - "languageIdsByExtension": { - "c": "c", - "h": "c", - "m": "objective-c", - "mm": "objective-cpp" - } - }, - { - "id": "csharp", - "displayName": "C#", - "fileExtensions": ["cs", "csx"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "csharp" - }, - { - "id": "fsharp", - "displayName": "F#", - "fileExtensions": ["fs", "fsi", "fsx"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "fsharp" - }, - { - "id": "swift", - "displayName": "Swift", - "fileExtensions": ["swift"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "swift", - "languageServerLaunch": { - "executableNames": ["sourcekit-lsp"], - "arguments": [] - }, - "languageServerInstallation": { - "officialDownloadURL": "https://github.com/swiftlang/sourcekit-lsp" - } - }, - { - "id": "kotlin", - "displayName": "Kotlin", - "fileExtensions": ["kt", "kts"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "kotlin", - "languageServerLaunch": { - "executableNames": ["kotlin-language-server"], - "arguments": [] - }, - "languageServerInstallation": { - "officialDownloadURL": "https://github.com/Kotlin/kotlin-lsp" - } - }, - { - "id": "scala", - "displayName": "Scala", - "fileExtensions": ["scala", "sc"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "scala", - "languageServerLaunch": { - "executableNames": ["metals"], - "arguments": [] - }, - "languageServerInstallation": { - "homebrewFormula": "metals", - "officialDownloadURL": "https://scalameta.org/metals/docs/editors/overview.html" - } - }, - { - "id": "groovy", - "displayName": "Groovy", - "fileExtensions": ["groovy"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "groovy" - }, - { - "id": "ruby", - "displayName": "Ruby", - "fileExtensions": ["rb", "rake", "gemspec"], - "fileNames": ["Rakefile", "Gemfile"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "ruby", - "languageServerLaunch": { - "executableNames": ["ruby-lsp"], - "arguments": [] - }, - "languageServerInstallation": { - "officialDownloadURL": "https://shopify.github.io/ruby-lsp/" - } - }, - { - "id": "php", - "displayName": "PHP", - "fileExtensions": ["php", "phtml"], - "capabilities": ["run", "languageServer", "formatting", "testing"], - "activationPolicy": "onDemand", - "languageId": "php", - "languageServerLaunch": { - "executableNames": ["intelephense"], - "arguments": ["--stdio"] - }, - "languageServerInstallation": { - "officialDownloadURL": "https://intelephense.com/" - } - }, - { - "id": "dart", - "displayName": "Dart", - "fileExtensions": ["dart"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "dart" - }, - { - "id": "lua", - "displayName": "Lua", - "fileExtensions": ["lua"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "lua" - }, - { - "id": "shell", - "displayName": "Shell", - "fileExtensions": ["sh", "bash", "zsh", "fish", "ksh"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "shellscript", - "languageServerLaunch": { - "executableNames": ["bash-language-server"], - "arguments": ["start"] - }, - "languageServerInstallation": { - "officialDownloadURL": "https://github.com/bash-lsp/bash-language-server" - } - }, - { - "id": "powershell", - "displayName": "PowerShell", - "fileExtensions": ["ps1", "psm1", "psd1"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "powershell" - }, - { - "id": "html", - "displayName": "HTML", - "fileExtensions": ["html", "htm", "xhtml"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "html" - }, - { - "id": "css", - "displayName": "CSS", - "fileExtensions": ["css", "scss", "sass", "less"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "css", - "languageIdsByExtension": { - "less": "less", - "sass": "sass", - "scss": "scss" - } - }, - { - "id": "vue", - "displayName": "Vue", - "fileExtensions": ["vue"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "vue" - }, - { - "id": "svelte", - "displayName": "Svelte", - "fileExtensions": ["svelte"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "svelte" - }, - { - "id": "astro", - "displayName": "Astro", - "fileExtensions": ["astro"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "astro" - }, - { - "id": "json", - "displayName": "JSON", - "fileExtensions": ["json", "jsonc", "json5"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "json", - "languageIdsByExtension": { - "jsonc": "jsonc" - } - }, - { - "id": "yaml", - "displayName": "YAML", - "fileExtensions": ["yml", "yaml"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "yaml", - "languageServerLaunch": { - "executableNames": ["yaml-language-server"], - "arguments": ["--stdio"] - }, - "languageServerInstallation": { - "homebrewFormula": "yaml-language-server", - "officialDownloadURL": "https://github.com/redhat-developer/yaml-language-server" - } - }, - { - "id": "xml", - "displayName": "XML", - "fileExtensions": ["xml", "xsd", "wsdl", "pom"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "xml" - }, - { - "id": "markdown", - "displayName": "Markdown", - "fileExtensions": ["md", "markdown", "mdx"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "markdown" - }, - { - "id": "sql", - "displayName": "SQL", - "fileExtensions": ["sql"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "sql" - }, - { - "id": "terraform", - "displayName": "Terraform", - "fileExtensions": ["tf", "tfvars"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "terraform" - }, - { - "id": "dockerfile", - "displayName": "Dockerfile", - "fileExtensions": ["dockerfile"], - "fileNames": ["Dockerfile"], - "fileNamePrefixes": ["Dockerfile."], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "dockerfile", - "languageIdsByFileName": { - "Dockerfile": "dockerfile" - } - }, - { - "id": "cmake", - "displayName": "CMake", - "fileExtensions": ["cmake"], - "fileNames": ["CMakeLists.txt"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "cmake" - }, - { - "id": "make", - "displayName": "Make", - "fileExtensions": ["mk"], - "fileNames": ["Makefile", "GNUmakefile"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "makefile" - }, - { - "id": "toml", - "displayName": "TOML", - "fileExtensions": ["toml"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "toml" - }, - { - "id": "graphql", - "displayName": "GraphQL", - "fileExtensions": ["graphql", "gql"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "graphql" - }, - { - "id": "protobuf", - "displayName": "Protocol Buffers", - "fileExtensions": ["proto"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "proto" - }, - { - "id": "prisma", - "displayName": "Prisma", - "fileExtensions": ["prisma"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "prisma" - }, - { - "id": "elixir", - "displayName": "Elixir", - "fileExtensions": ["ex", "exs"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "elixir" - }, - { - "id": "erlang", - "displayName": "Erlang", - "fileExtensions": ["erl", "hrl"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "erlang" - }, - { - "id": "haskell", - "displayName": "Haskell", - "fileExtensions": ["hs", "lhs"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "haskell" - }, - { - "id": "ocaml", - "displayName": "OCaml", - "fileExtensions": ["ml", "mli"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "ocaml" - }, - { - "id": "clojure", - "displayName": "Clojure", - "fileExtensions": ["clj", "cljs", "cljc", "edn"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "clojure" - }, - { - "id": "julia", - "displayName": "Julia", - "fileExtensions": ["jl"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "julia" - }, - { - "id": "r", - "displayName": "R", - "fileExtensions": ["r"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "r" - }, - { - "id": "perl", - "displayName": "Perl", - "fileExtensions": ["pl", "pm", "t"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "perl" - }, - { - "id": "zig", - "displayName": "Zig", - "fileExtensions": ["zig"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "zig" - }, - { - "id": "solidity", - "displayName": "Solidity", - "fileExtensions": ["sol"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "solidity" - } - ] -} diff --git a/rust/lithe-core/src/build/run_start.rs b/rust/lithe-core/src/build/run_start.rs index f8861c3d..f6cca314 100644 --- a/rust/lithe-core/src/build/run_start.rs +++ b/rust/lithe-core/src/build/run_start.rs @@ -57,11 +57,16 @@ struct StartParams { rerun: Option, } +/// Exactly one of `contents` and `base64` carries the file. Text is written as UTF-8; +/// bytes are written as given, for a format whose encoding the plugin must choose itself. #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct TemporaryFile { name: String, - contents: String, + #[serde(default)] + contents: Option, + #[serde(default)] + base64: Option, } #[derive(Debug, Deserialize)] @@ -158,7 +163,10 @@ fn decode_start(payload: &DecodeStartPayload, required_tools: &[String]) -> Vali "environment": environment, "temporaryFiles": files .iter() - .map(|file| json!({"path": file.path, "contents": file.contents})) + .map(|file| match &file.contents { + PlannedContents::Text(text) => json!({"path": file.path, "contents": text}), + PlannedContents::Bytes(base64) => json!({"path": file.path, "base64": base64}), + }) .collect::>(), }); if let Some(debug) = ¶ms.debug { @@ -226,7 +234,13 @@ fn executable(process: &Map, required_tools: &[String]) -> Valida struct PlannedFile { name: String, path: String, - contents: String, + contents: PlannedContents, +} + +/// What the host writes: UTF-8 text, or bytes as validated standard base64. +enum PlannedContents { + Text(String), + Bytes(String), } fn temporary_files( @@ -265,21 +279,50 @@ fn temporary_files( "A temporary file name is unique, 1 to 64 letters, digits, '.', '_' or '-', and starts with a letter or digit.", )); } - if file.contents.len() > MAX_TEMPORARY_FILE_BYTES || file.contents.contains('\0') { - return Err(invalid( - &format!("temporaryFiles[{index}].contents"), - "A temporary file holds at most 4 MiB of text.", - )); - } + let contents = temporary_contents(file, index)?; planned.push(PlannedFile { name: file.name.clone(), path: format!("{directory}{}{}", std::path::MAIN_SEPARATOR, file.name), - contents: file.contents.clone(), + contents, }); } Ok(planned) } +/// Text without NUL or standard-base64 bytes, each at most 4 MiB once written. +fn temporary_contents(file: &TemporaryFile, index: usize) -> Validation { + use base64::Engine as _; + match (&file.contents, &file.base64) { + (Some(text), None) => { + if text.len() > MAX_TEMPORARY_FILE_BYTES || text.contains('\0') { + return Err(invalid( + &format!("temporaryFiles[{index}].contents"), + "A temporary file holds at most 4 MiB of text.", + )); + } + Ok(PlannedContents::Text(text.clone())) + } + (None, Some(encoded)) => { + // Bound the encoded form first so an oversized string is never decoded. + let fits = encoded.len() <= MAX_TEMPORARY_FILE_BYTES.div_ceil(3) * 4 + && base64::engine::general_purpose::STANDARD + .decode(encoded) + .is_ok_and(|bytes| bytes.len() <= MAX_TEMPORARY_FILE_BYTES); + if !fits { + return Err(invalid( + &format!("temporaryFiles[{index}].base64"), + "A temporary file holds at most 4 MiB of bytes in standard base64.", + )); + } + Ok(PlannedContents::Bytes(encoded.clone())) + } + _ => Err(invalid( + &format!("temporaryFiles[{index}]"), + "A temporary file has either text contents or base64 bytes.", + )), + } +} + fn valid_temporary_name(name: &str) -> bool { name.len() <= MAX_TEMPORARY_NAME_BYTES && name diff --git a/rust/lithe-core/src/debug/engine.rs b/rust/lithe-core/src/debug/engine.rs index 36561003..2775393e 100644 --- a/rust/lithe-core/src/debug/engine.rs +++ b/rust/lithe-core/src/debug/engine.rs @@ -1013,7 +1013,7 @@ pub(crate) fn disconnect(request: DisconnectRequest) -> Result, } diff --git a/rust/lithe-core/src/execution/launch_plan.rs b/rust/lithe-core/src/execution/launch_plan.rs deleted file mode 100644 index 00abd4a7..00000000 --- a/rust/lithe-core/src/execution/launch_plan.rs +++ /dev/null @@ -1,451 +0,0 @@ -//! Plans Java launches whose command line exceeds the operating system limit. -//! -//! Oversized path-list options (`-cp`, `-classpath`, `--class-path`, `-p`, -//! `--module-path`) move into a JDK argument file. The JVM options before the -//! application target and every program argument stay on the command line. - -use crate::protocol::{CoreError, ErrorCode}; -use serde::{Deserialize, Serialize}; - -/// Windows refuses a CreateProcess command line of 32,767 UTF-16 code units. -/// The 2048-unit headroom covers the terminating null and host quoting. -const DEFAULT_COMMAND_LINE_LIMIT: usize = 32767 - 2048; - -/// The JDK reads `@argfile` references from JDK 9 onward. -const ARGUMENT_FILE_MIN_FEATURE_VERSION: u32 = 9; - -/// Options whose value is a path list; they carry nearly all of an oversized command line. -const PATH_OPTIONS: [&str; 5] = ["-cp", "-classpath", "--class-path", "-p", "--module-path"]; - -/// Launcher options whose following argument is a value, not the application target. -const VALUE_OPTIONS: [&str; 7] = [ - "--add-modules", - "--add-exports", - "--add-opens", - "--add-reads", - "--patch-module", - "--limit-modules", - "--upgrade-module-path", -]; - -/// A Java launch request. The executable is the launcher path; the JDK feature version -/// is either explicit or derived from host-read `release` contents, nearest first. -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LaunchCommandRequest { - /// Launcher path as the host will spawn it. - pub executable: String, - /// Complete argument list, excluding the executable. - #[serde(default)] - pub arguments: Vec, - /// Known JDK feature version. Takes precedence over `java_release_contents`. - pub java_feature_version: Option, - /// Host-read `release` file contents, ordered nearest first. Core reads the first - /// entry that contains `JAVA_VERSION`; an unrecognized value is unknown. - pub java_release_contents: Option>, - /// Command-line byte budget. Defaults to the Windows-safe budget. - pub command_line_limit: Option, -} - -/// The launch the host spawns. -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct LaunchCommandPlan { - /// Arguments to spawn. When `argument_file` is set, the element at - /// `argument_file.argument_index` is an empty placeholder that the host replaces - /// with `@`. - pub arguments: Vec, - /// Argument-file text to write, or `None` when the launch is unchanged. - pub argument_file: Option, -} - -/// Argument-file text and the index of the placeholder that references it. -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct LaunchArgumentFile { - /// Unicode text in the JDK argument-file quoting format. - pub text: String, - /// Index into `LaunchCommandPlan::arguments` of the placeholder. - pub argument_index: usize, -} - -/// Plans a launch. Only a Java launcher of feature version 9 or later whose command line -/// exceeds the limit is changed; every other launch is returned unchanged. -pub fn plan_launch_command(request: &LaunchCommandRequest) -> Result { - if request.executable.is_empty() { - return Err(CoreError::new( - ErrorCode::InvalidRequest, - "Launch command has no executable.", - )); - } - let unchanged = || LaunchCommandPlan { - arguments: request.arguments.clone(), - argument_file: None, - }; - let limit = request - .command_line_limit - .unwrap_or(DEFAULT_COMMAND_LINE_LIMIT); - if !is_java_launcher(&request.executable) - || command_line_size(&request.executable, &request.arguments) <= limit - { - return Ok(unchanged()); - } - let version = request.java_feature_version.or_else(|| { - request - .java_release_contents - .as_deref() - .and_then(feature_version_from_nearest) - }); - match version { - Some(version) if version >= ARGUMENT_FILE_MIN_FEATURE_VERSION => {} - _ => return Ok(unchanged()), - } - - let mut arguments = Vec::with_capacity(request.arguments.len()); - let mut body = String::new(); - let mut placeholder = None; - let mut index = 0; - while index < request.arguments.len() { - let argument = request.arguments[index].as_str(); - // The application target (main class, -jar, -m/--module) ends the JVM options; - // it and everything after it are program arguments and stay verbatim. - if is_application_target(argument) || !argument.starts_with('-') { - arguments.extend(request.arguments[index..].iter().cloned()); - break; - } - if PATH_OPTIONS.contains(&argument) && index + 1 < request.arguments.len() { - placeholder.get_or_insert_with(|| { - arguments.push(String::new()); - arguments.len() - 1 - }); - push_argument_file_pair(&mut body, argument, &request.arguments[index + 1]); - index += 2; - continue; - } - // The --class-path=value spelling is one argument and moves the same way. - if let Some((name, value)) = argument.split_once('=') { - if PATH_OPTIONS.contains(&name) { - placeholder.get_or_insert_with(|| { - arguments.push(String::new()); - arguments.len() - 1 - }); - push_argument_file_pair(&mut body, name, value); - index += 1; - continue; - } - } - if VALUE_OPTIONS.contains(&argument) && index + 1 < request.arguments.len() { - arguments.push(argument.to_owned()); - arguments.push(request.arguments[index + 1].clone()); - index += 2; - continue; - } - arguments.push(argument.to_owned()); - index += 1; - } - - match placeholder { - Some(argument_index) => Ok(LaunchCommandPlan { - arguments, - argument_file: Some(LaunchArgumentFile { - text: body, - argument_index, - }), - }), - None => Ok(unchanged()), - } -} - -/// Reads the JDK feature version from the text of one `release` file. Legacy `1.x` -/// versions report the second number, so `1.8.0_392` is 8 and `17.0.2` is 17. -pub fn java_feature_version_from_release(contents: &str) -> Option { - parse_feature_version(release_java_version(contents)?) -} - -/// Returns the raw value of the first `JAVA_VERSION=` line, without surrounding spaces. -fn release_java_version(contents: &str) -> Option<&str> { - contents - .lines() - .find_map(|line| line.trim().strip_prefix("JAVA_VERSION=")) -} - -/// Uses the nearest release file that names a `JAVA_VERSION`. A farther file never -/// answers for an unrecognized nearer one. -fn feature_version_from_nearest(contents: &[String]) -> Option { - let nearest = contents - .iter() - .find(|text| release_java_version(text).is_some())?; - java_feature_version_from_release(nearest) -} - -fn parse_feature_version(value: &str) -> Option { - let parts: Vec<&str> = value - .trim_matches('"') - .split(|c: char| matches!(c, '.' | '_' | '-' | '+')) - .filter(|part| !part.is_empty()) - .collect(); - let first: u32 = parts.first()?.parse().ok()?; - if first == 1 && parts.len() > 1 { - parts[1].parse().ok() - } else { - Some(first) - } -} - -fn is_application_target(argument: &str) -> bool { - matches!(argument, "-jar" | "-m" | "--module") || argument.starts_with("--module=") -} - -/// Accepts the launcher names of every platform, with or without the `.exe` suffix. -fn is_java_launcher(executable: &str) -> bool { - let name = executable - .rsplit(|c: char| c == '/' || c == '\\') - .next() - .unwrap_or(executable) - .to_ascii_lowercase(); - matches!(name.as_str(), "java" | "java.exe" | "javaw" | "javaw.exe") -} - -/// Estimates the command line: each argument carries a quote and a separator. -fn command_line_size(executable: &str, arguments: &[String]) -> usize { - arguments - .iter() - .fold(executable.len() + 3, |size, argument| { - size + argument.len() + 3 - }) -} - -fn push_argument_file_pair(body: &mut String, option: &str, value: &str) { - push_argument_file_token(body, option); - push_argument_file_token(body, value); -} - -/// Quotes one token the way the JDK argument-file reader expects: double quotes, with -/// backslashes and double quotes escaped. -fn push_argument_file_token(body: &mut String, token: &str) { - body.push('"'); - for character in token.chars() { - if character == '\\' || character == '"' { - body.push('\\'); - } - body.push(character); - } - body.push_str("\"\n"); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn request(executable: &str, arguments: &[&str]) -> LaunchCommandRequest { - LaunchCommandRequest { - executable: executable.to_owned(), - arguments: arguments.iter().map(|value| (*value).to_owned()).collect(), - java_feature_version: Some(17), - java_release_contents: None, - command_line_limit: Some(40), - } - } - - fn unchanged(request: &LaunchCommandRequest) -> LaunchCommandPlan { - LaunchCommandPlan { - arguments: request.arguments.clone(), - argument_file: None, - } - } - - #[test] - fn launch_plan_keeps_a_command_line_within_the_limit() { - let mut request = request("/jdk/bin/java", &["-cp", "a.jar", "Main"]); - request.command_line_limit = None; - assert_eq!(plan_launch_command(&request).unwrap(), unchanged(&request)); - } - - #[test] - fn launch_plan_moves_an_oversized_classpath_into_an_argument_file() { - let request = request( - "/jdk/bin/java", - &[ - "-Xmx1g", - "-cp", - "lib/one.jar:lib/two.jar", - "com.example.Main", - "--flag", - ], - ); - let plan = plan_launch_command(&request).unwrap(); - assert_eq!(plan.arguments, ["-Xmx1g", "", "com.example.Main", "--flag"]); - assert_eq!( - plan.argument_file, - Some(LaunchArgumentFile { - text: "\"-cp\"\n\"lib/one.jar:lib/two.jar\"\n".to_owned(), - argument_index: 1, - }) - ); - } - - #[test] - fn launch_plan_escapes_spaces_quotes_and_backslashes_in_values() { - let request = request( - "C:\\jdk\\bin\\java.exe", - &["-classpath", "C:\\my libs\\\"x\".jar", "Main"], - ); - let plan = plan_launch_command(&request).unwrap(); - let file = plan.argument_file.unwrap(); - assert_eq!( - file.text, - "\"-classpath\"\n\"C:\\\\my libs\\\\\\\"x\\\".jar\"\n" - ); - } - - #[test] - fn launch_plan_moves_module_path_in_both_spellings() { - let request = request( - "/jdk/bin/java", - &["-p", "mods/a", "--module-path=mods/b", "com.example.Main"], - ); - let plan = plan_launch_command(&request).unwrap(); - assert_eq!(plan.arguments, ["", "com.example.Main"]); - assert_eq!( - plan.argument_file.unwrap().text, - "\"-p\"\n\"mods/a\"\n\"--module-path\"\n\"mods/b\"\n" - ); - } - - #[test] - fn launch_plan_leaves_java_older_than_nine_unchanged() { - let mut request = request("/jdk/bin/java", &["-cp", "a.jar", "Main"]); - request.java_feature_version = Some(8); - assert_eq!(plan_launch_command(&request).unwrap(), unchanged(&request)); - } - - #[test] - fn launch_plan_leaves_an_unknown_jdk_version_unchanged() { - let mut request = request("/jdk/bin/java", &["-cp", "a.jar", "Main"]); - request.java_feature_version = None; - request.java_release_contents = None; - assert_eq!(plan_launch_command(&request).unwrap(), unchanged(&request)); - } - - #[test] - fn launch_plan_leaves_non_java_launches_unchanged() { - let request = request("/usr/bin/node", &["-cp", "a.jar", "Main"]); - assert_eq!(plan_launch_command(&request).unwrap(), unchanged(&request)); - } - - #[test] - fn launch_plan_preserves_program_arguments_after_the_main_class() { - let request = request( - "/jdk/bin/java", - &["-cp", "a.jar", "com.example.Main", "-cp", "user-value"], - ); - let plan = plan_launch_command(&request).unwrap(); - assert_eq!( - plan.arguments, - ["", "com.example.Main", "-cp", "user-value"] - ); - assert_eq!(plan.argument_file.unwrap().text, "\"-cp\"\n\"a.jar\"\n"); - } - - #[test] - fn launch_plan_stops_at_jar_and_module_targets() { - let jar = request("/jdk/bin/java", &["-jar", "app.jar", "-cp", "x"]); - assert_eq!(plan_launch_command(&jar).unwrap(), unchanged(&jar)); - - let module = request( - "/jdk/bin/java", - &["--module", "com.example/com.example.Main", "-p", "mods"], - ); - assert_eq!(plan_launch_command(&module).unwrap(), unchanged(&module)); - } - - #[test] - fn launch_plan_keeps_value_options_intact() { - let request = request( - "/jdk/bin/java", - &["--add-modules", "-cp", "-cp", "a.jar", "Main"], - ); - let plan = plan_launch_command(&request).unwrap(); - assert_eq!(plan.arguments, ["--add-modules", "-cp", "", "Main"]); - } - - #[test] - fn launch_plan_reads_the_release_version_forms() { - assert_eq!( - java_feature_version_from_release("JAVA_VERSION=\"1.8.0_392\"\n"), - Some(8) - ); - assert_eq!( - java_feature_version_from_release("IMPLEMENTOR=\"x\"\nJAVA_VERSION=\"17.0.2\"\n"), - Some(17) - ); - assert_eq!( - java_feature_version_from_release("JAVA_VERSION=\"21\""), - Some(21) - ); - assert_eq!(java_feature_version_from_release("IMPLEMENTOR=\"x\""), None); - assert_eq!( - java_feature_version_from_release("JAVA_VERSION=\"weird\""), - None - ); - } - - #[test] - fn launch_plan_takes_the_nearest_release_and_does_not_fall_back() { - let mut request = request("/jdk/bin/java", &["-cp", "a.jar", "Main"]); - request.java_feature_version = None; - request.java_release_contents = Some(vec![ - "JAVA_VERSION=\"weird\"".to_owned(), - "JAVA_VERSION=\"17\"".to_owned(), - ]); - assert_eq!(plan_launch_command(&request).unwrap(), unchanged(&request)); - - request.command_line_limit = Some(10); - request.java_release_contents = Some(vec![ - "IMPLEMENTOR=\"x\"".to_owned(), - "JAVA_VERSION=\"21\"".to_owned(), - ]); - assert!(plan_launch_command(&request) - .unwrap() - .argument_file - .is_some()); - } - - #[test] - fn launch_plan_prefers_an_explicit_feature_version() { - let mut request = request("/jdk/bin/java", &["-cp", "a.jar", "Main"]); - request.java_feature_version = Some(8); - request.java_release_contents = Some(vec!["JAVA_VERSION=\"17\"".to_owned()]); - assert_eq!(plan_launch_command(&request).unwrap(), unchanged(&request)); - } - - #[test] - fn launch_plan_command_dispatches_through_the_protocol() { - let response: serde_json::Value = serde_json::from_str(&crate::execute_json( - &serde_json::json!({ - "id": "plan", - "command": "execution.planLaunchCommand", - "payload": { - "executable": "/jdk/bin/java", - "arguments": ["-cp", "a.jar", "Main"], - "javaFeatureVersion": 17, - "commandLineLimit": 10 - } - }) - .to_string(), - )) - .unwrap(); - assert_eq!(response["ok"], true, "{response}"); - assert_eq!(response["data"]["argumentFile"]["argumentIndex"], 0); - assert_eq!( - response["data"]["arguments"], - serde_json::json!(["", "Main"]) - ); - } - - #[test] - fn launch_plan_rejects_an_empty_executable() { - let request = request("", &[]); - assert!(plan_launch_command(&request).is_err()); - } -} diff --git a/rust/lithe-core/src/execution/mod.rs b/rust/lithe-core/src/execution/mod.rs index af5f01ff..850d31ef 100644 --- a/rust/lithe-core/src/execution/mod.rs +++ b/rust/lithe-core/src/execution/mod.rs @@ -30,9 +30,3 @@ pub use environment::{ resolve_launch_environment, resolve_search_path, LaunchEnvironmentRequest, LaunchEnvironmentResponse, SearchPathCandidate, SearchPathRequest, }; - -mod launch_plan; -pub use launch_plan::{ - java_feature_version_from_release, plan_launch_command, LaunchArgumentFile, LaunchCommandPlan, - LaunchCommandRequest, -}; diff --git a/rust/lithe-core/src/lsp/interface/client.rs b/rust/lithe-core/src/lsp/interface/client.rs index 2b2195de..ebbef4b5 100644 --- a/rust/lithe-core/src/lsp/interface/client.rs +++ b/rust/lithe-core/src/lsp/interface/client.rs @@ -44,8 +44,8 @@ pub(super) fn client_initialize_for_plugin( "completionItem": { "snippetSupport": true, "documentationFormat": ["markdown", "plaintext"], - // Intelephense and JDT LS omit typed class/namespace labels - // unless the client advertises labelDetailsSupport. + // Some servers (Intelephense, for one) omit typed class and + // namespace labels unless the client advertises labelDetailsSupport. "labelDetailsSupport": true, "resolveSupport": { "properties": [ @@ -1512,7 +1512,7 @@ fn parse_completion_item(item: &Value) -> Option { { mapped["additionalTextEdits"] = json!(edits); } - // `data: null` makes Intelephense/JDT LS treat resolve as a no-op, so only + // `data: null` makes some servers treat resolve as a no-op, so only // forward a real resolve token. Namespace display lives in labelDetails; // import `use`/`import` edits usually arrive only from completionItem/resolve. if let Some(data) = item.get("data").filter(|value| !value.is_null()) { @@ -2404,7 +2404,7 @@ mod semantic_tests { let mut state = LspClientState::default(); apply_dynamic_registration( &mut state, - &json!({"params": {"registrations": [{"id": "java", "method": "textDocument/semanticTokens", "registerOptions": options()}]}}), + &json!({"params": {"registrations": [{"id": "semantic", "method": "textDocument/semanticTokens", "registerOptions": options()}]}}), ); assert!(state .server_capabilities @@ -2412,7 +2412,7 @@ mod semantic_tests { let id = allocate_request(&mut state, "textDocument/semanticTokens/full"); apply_dynamic_unregistration( &mut state, - &json!({"params": {"unregisterations": [{"id": "java", "method": "textDocument/semanticTokens"}]}}), + &json!({"params": {"unregisterations": [{"id": "semantic", "method": "textDocument/semanticTokens"}]}}), ); assert!(!state .server_capabilities diff --git a/rust/lithe-core/src/lsp/interface/engine.rs b/rust/lithe-core/src/lsp/interface/engine.rs index 132a2737..6d833b5a 100644 --- a/rust/lithe-core/src/lsp/interface/engine.rs +++ b/rust/lithe-core/src/lsp/interface/engine.rs @@ -8289,9 +8289,9 @@ mod tests { } } - // Regression: JDT publishes diagnostics under the resolved path of a file - // the host opened through a symbolic link; matching the URIs textually - // dropped every Java error in such a workspace. + // Regression: a server publishes diagnostics under the resolved path of a + // file the host opened through a symbolic link; matching the URIs textually + // dropped every error in such a workspace. #[cfg(unix)] #[test] fn diagnostics_published_under_the_resolved_path_reach_the_linked_document() { @@ -8977,7 +8977,7 @@ mod tests { fn a_negotiated_build_request_forwards_the_plan_and_returns_the_reply() { let mut harness = negotiated_build_provider(); let params = json!({ - "buildSystemID": "gradle", + "buildSystemID": "make", "root": "", "options": { "offline": true }, "optionArguments": ["--offline"], @@ -9608,7 +9608,7 @@ mod tests { // deadline, late reply, revocation, and compatibility with a backend that // only speaks the build protocol) is driven at a chosen moment. - const FUNCTION_PLUGIN: &str = "dev.lithe.fixture.maven"; + const FUNCTION_PLUGIN: &str = "dev.lithe.fixture.build"; /// A session owned by `FUNCTION_PLUGIN` whose backend answered /// `initialize` with `experimental`. @@ -9782,7 +9782,7 @@ mod tests { // Another plugin's reference never reaches this process. let error = invoke( &harness, - "dev.lithe.fixture.gradle", + "dev.lithe.fixture.other-build", "snapshot.refresh", None, ) @@ -9846,7 +9846,7 @@ mod tests { harness.server.send(json!({ "jsonrpc": "2.0", "id": request_id, - "error": { "code": -32000, "message": "Snapshot is locked.", "data": { "code": "maven.snapshotLocked" } } + "error": { "code": -32000, "message": "Snapshot is locked.", "data": { "code": "build.snapshotLocked" } } })); let error = harness .await_event(|event| event.operation_id.as_deref() == Some(operation.as_str())) @@ -9857,7 +9857,7 @@ mod tests { assert_eq!(error.message, "Snapshot is locked."); assert_eq!( error.plugin_error_code.as_deref(), - Some("maven.snapshotLocked") + Some("build.snapshotLocked") ); } diff --git a/rust/lithe-core/src/lsp/interface/process.rs b/rust/lithe-core/src/lsp/interface/process.rs index dc9ee61a..6c8889c2 100644 --- a/rust/lithe-core/src/lsp/interface/process.rs +++ b/rust/lithe-core/src/lsp/interface/process.rs @@ -7,7 +7,7 @@ //! against a scripted server instead of a real one. //! //! On Windows a server and everything it starts share one Job Object, so a -//! batch launcher's JVM ends with it and none outlives a crashed host. +//! runtime a batch launcher starts ends with it and none outlives a crashed host. use crate::protocol::{CoreError, ErrorCode}; use std::collections::BTreeMap; @@ -385,7 +385,7 @@ mod tests { #[test] fn batch_language_server_uses_cmd_exe() { let spec = LspProcessSpec { - executable: PathBuf::from(r"C:\Program Files\Lithe\jdtls.bat"), + executable: PathBuf::from(r"C:\Program Files\Lithe\language-server.bat"), arguments: vec!["-data".to_string(), r"C:\workspace data".to_string()], working_directory: PathBuf::from(r"C:\workspace"), environment: BTreeMap::new(), @@ -434,7 +434,7 @@ mod tests { /// Not a scenario of its own: a no-op unless the termination test launches /// this test binary with it. It then stays alive until its inherited stdin - /// closes, like the JVM a batch language-server launcher starts. + /// closes, like the runtime a batch language-server launcher starts. #[test] fn stdin_hold_helper() { if std::env::var_os(STDIN_HOLD_ENV).is_none() { @@ -529,7 +529,7 @@ mod tests { let executable = root.join("scripted server.cmd"); let echo_output = root.join("arguments.txt"); // The script forwards `%*` to a native Windows program, which parses the - // command line the way a real language server such as java.exe does. + // command line the way a real native language server does. // This test binary plays that program: starting PowerShell here instead // took from 0.2 s to the whole per-test budget on CI runners. fs::write( diff --git a/rust/lithe-core/src/lsp/languages/catalog.rs b/rust/lithe-core/src/lsp/languages/catalog.rs deleted file mode 100644 index 4c6c522a..00000000 --- a/rust/lithe-core/src/lsp/languages/catalog.rs +++ /dev/null @@ -1,392 +0,0 @@ -//! Loading and validation for built-in and workspace language-provider catalogs. -//! -//! Note: LSP runtime ownership and provider-routing invariants are recorded in -//! .agents/notes/implemented/architecture/2026-09-13-language-tooling-and-lsp-runtime-ownership.md -//! -//! Checklist for adding a new language server: -//! 1. Define a stable `id`, file-match rules, `languageId`, candidate executable -//! names and arguments; declare side-effect-free `validationArguments` for -//! any shim/proxy tool. -//! 2. Define `languageServerInstallation` only if an install entry point is -//! needed; do not add a provider-ID branch in Swift UI. -//! 3. Confirm the server supports stdio with standard `Content-Length` framing. -//! 4. Do not branch by language in UI or manager code; server differences -//! belong in the descriptor or a dedicated adapter. -//! 5. Verify capability with the `initialize` response; never treat the -//! catalog's `languageServer` flag as proof of feature support. -//! 6. Test at minimum: initialize timeout/error, didOpen/change/close, request -//! timeout/late response, shutdown/exit, forced stop, and abnormal exit. -//! 7. Cover malformed/partial/multiple frames, dynamic capability changes, -//! stale diagnostics, UTF-16 positions, file URIs with spaces/non-ASCII -//! characters, and servers that emit output immediately on launch. - -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::BTreeMap; -use std::path::{Path, PathBuf}; - -const BUILTIN_LANGUAGE_PROVIDERS: &str = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/resources/lsp/language-providers.json" -)); - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -/// Effective provider catalog after applying an optional workspace override. -pub struct LspProviderCatalog { - pub version: u32, - pub origin: LspProviderCatalogOrigin, - pub providers: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub diagnostics: Vec, -} - -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -/// Source that last changed the effective provider configuration. -pub enum LspProviderCatalogOrigin { - /// The effective catalog is exactly the embedded provider document. - Builtin, - /// At least one workspace-local patch was merged into the built-ins. - WorkspaceOverride, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -/// Non-fatal configuration problem surfaced alongside usable providers. -pub struct LspProviderConfigDiagnostic { - pub path: String, - pub message: String, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -/// File recognition, capabilities, and server metadata for one language provider. -pub struct LspProviderDescriptor { - pub id: String, - pub display_name: String, - pub file_extensions: Vec, - pub file_names: Vec, - pub file_name_prefixes: Vec, - pub capabilities: Vec, - pub activation_policy: LspActivationPolicy, - pub language_id: Option, - pub language_ids_by_extension: BTreeMap, - pub language_ids_by_file_name: BTreeMap, - pub language_server_launch: Option, - pub language_server_installation: Option, -} - -#[derive(Debug, Clone, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -/// Executable candidates and initialization values for a language server. -pub struct LspServerLaunchDescriptor { - pub executable_names: Vec, - #[serde(default)] - pub arguments: Vec, - #[serde(default)] - pub validation_arguments: Vec, - #[serde(default)] - pub environment: BTreeMap, - #[serde(default)] - pub initialization_options: Option, -} - -#[derive(Debug, Clone, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -/// Installation hints shown when no compatible server executable is available. -pub struct LspServerInstallationDescriptor { - #[serde(default)] - pub homebrew_formula: Option, - #[serde(default, rename = "officialDownloadURL")] - pub official_download_url: Option, -} - -#[derive(Debug, Clone, Copy, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -/// Product feature that a language provider can contribute. -pub enum LspProviderCapability { - /// Produces runnable project configurations. - Run, - /// Supplies semantic language features through an LSP process. - LanguageServer, - /// Supplies an adapter that implements the Debug Adapter Protocol. - DebugAdapter, - /// Formats source documents. - Formatting, - /// Discovers or runs project tests. - Testing, -} - -#[derive(Debug, Clone, Copy, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -/// Policy controlling when a provider's external process may start. -pub enum LspActivationPolicy { - /// Starts the provider only after a feature explicitly requests it. - OnDemand, - /// Starts the provider as soon as its project activation conditions match. - Always, -} - -impl Default for LspActivationPolicy { - fn default() -> Self { - Self::OnDemand - } -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -struct LspProviderConfigDocument { - #[serde(default, rename = "$schema")] - _schema: Option, - #[serde(default = "default_config_version")] - version: u32, - #[serde(default)] - providers: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -struct LspProviderPatch { - id: String, - #[serde(default)] - display_name: Option, - #[serde(default)] - file_extensions: Option>, - #[serde(default)] - file_names: Option>, - #[serde(default)] - file_name_prefixes: Option>, - #[serde(default)] - capabilities: Option>, - #[serde(default)] - activation_policy: Option, - #[serde(default)] - language_id: Option, - #[serde(default)] - language_ids_by_extension: Option>, - #[serde(default)] - language_ids_by_file_name: Option>, - #[serde(default)] - language_server_launch: Option, - #[serde(default)] - language_server_installation: Option, - #[serde(default)] - disabled: bool, -} -/// Serializes the effective provider catalog for the C ABI. -pub fn provider_catalog_json(workspace_root: Option<&Path>) -> String { - let catalog = provider_catalog(workspace_root); - serde_json::to_string(&catalog).unwrap_or_else(|_| { - concat!( - "{\"version\":1,\"origin\":\"builtin\",\"providers\":[],", - "\"diagnostics\":[{\"path\":\"builtin:lsp\",", - "\"message\":\"Could not serialize the language-provider catalog.\"}]}" - ) - .to_string() - }) -} - -/// Loads built-ins and applies a workspace-local provider configuration, if present. -pub fn provider_catalog(workspace_root: Option<&Path>) -> LspProviderCatalog { - let mut diagnostics = Vec::new(); - let mut origin = LspProviderCatalogOrigin::Builtin; - let mut document = match parse_document(BUILTIN_LANGUAGE_PROVIDERS, "builtin:lsp") { - Ok(document) => document, - Err(message) => { - diagnostics.push(LspProviderConfigDiagnostic { - path: "builtin:lsp".to_string(), - message, - }); - LspProviderConfigDocument { - _schema: None, - version: 1, - providers: Vec::new(), - } - } - }; - - if let Some(root) = workspace_root { - let path = project_config_path(root); - if path.is_file() { - match std::fs::read_to_string(&path) { - Ok(raw) => match parse_document(&raw, &path.display().to_string()) { - Ok(project_document) => { - document = merge_documents(document, project_document); - origin = LspProviderCatalogOrigin::WorkspaceOverride; - } - Err(message) => diagnostics.push(LspProviderConfigDiagnostic { - path: path.display().to_string(), - message, - }), - }, - Err(error) => diagnostics.push(LspProviderConfigDiagnostic { - path: path.display().to_string(), - message: error.to_string(), - }), - } - } - } - - let mut providers = Vec::new(); - for patch in document.providers { - if patch.disabled { - continue; - } - providers.push(LspProviderDescriptor::from_patch(patch)); - } - LspProviderCatalog { - version: document.version, - origin, - providers, - diagnostics, - } -} - -fn parse_document(raw: &str, source: &str) -> Result { - serde_json::from_str(raw).map_err(|error| format!("{source}: {error}")) -} - -fn merge_documents( - mut base: LspProviderConfigDocument, - project: LspProviderConfigDocument, -) -> LspProviderConfigDocument { - base.version = project.version.max(base.version); - for patch in project.providers { - if let Some(existing) = base - .providers - .iter_mut() - .find(|provider| provider.id == patch.id) - { - existing.apply(patch); - } else { - base.providers.push(patch); - } - } - base -} - -fn project_config_path(root: &Path) -> PathBuf { - root.join(".lithe") - .join("lsp") - .join("language-providers.json") -} - -fn default_config_version() -> u32 { - 1 -} - -impl LspProviderPatch { - fn apply(&mut self, patch: LspProviderPatch) { - if patch.display_name.is_some() { - self.display_name = patch.display_name; - } - if patch.file_extensions.is_some() { - self.file_extensions = patch.file_extensions; - } - if patch.file_names.is_some() { - self.file_names = patch.file_names; - } - if patch.file_name_prefixes.is_some() { - self.file_name_prefixes = patch.file_name_prefixes; - } - if patch.capabilities.is_some() { - self.capabilities = patch.capabilities; - } - if patch.activation_policy.is_some() { - self.activation_policy = patch.activation_policy; - } - if patch.language_id.is_some() { - self.language_id = patch.language_id; - } - if patch.language_ids_by_extension.is_some() { - self.language_ids_by_extension = patch.language_ids_by_extension; - } - if patch.language_ids_by_file_name.is_some() { - self.language_ids_by_file_name = patch.language_ids_by_file_name; - } - if patch.language_server_launch.is_some() { - self.language_server_launch = patch.language_server_launch; - } - if patch.language_server_installation.is_some() { - self.language_server_installation = patch.language_server_installation; - } - self.disabled = patch.disabled; - } -} - -impl LspProviderDescriptor { - fn from_patch(patch: LspProviderPatch) -> Self { - let id = normalized_id(&patch.id); - let display_name = patch - .display_name - .filter(|value| !value.trim().is_empty()) - .unwrap_or_else(|| id.clone()); - let capabilities = patch.capabilities.unwrap_or_else(|| { - vec![ - LspProviderCapability::LanguageServer, - LspProviderCapability::Formatting, - ] - }); - Self { - id: id.clone(), - display_name, - file_extensions: normalized_values(patch.file_extensions.unwrap_or_default(), true), - file_names: normalized_values(patch.file_names.unwrap_or_default(), false), - file_name_prefixes: normalized_values( - patch.file_name_prefixes.unwrap_or_default(), - false, - ), - capabilities, - activation_policy: patch.activation_policy.unwrap_or_default(), - language_id: patch.language_id.filter(|value| !value.trim().is_empty()), - language_ids_by_extension: normalized_map( - patch.language_ids_by_extension.unwrap_or_default(), - true, - ), - language_ids_by_file_name: normalized_map( - patch.language_ids_by_file_name.unwrap_or_default(), - false, - ), - language_server_launch: patch.language_server_launch, - language_server_installation: patch.language_server_installation, - } - } -} - -fn normalized_id(value: &str) -> String { - value.trim().to_ascii_lowercase() -} - -fn normalized_values(values: Vec, trim_dot: bool) -> Vec { - let mut result = Vec::new(); - for value in values { - let normalized = normalized_key(&value, trim_dot); - if !normalized.is_empty() && !result.contains(&normalized) { - result.push(normalized); - } - } - result -} - -fn normalized_map(values: BTreeMap, trim_dot: bool) -> BTreeMap { - values - .into_iter() - .filter_map(|(key, value)| { - let key = normalized_key(&key, trim_dot); - if key.is_empty() || value.trim().is_empty() { - None - } else { - Some((key, value)) - } - }) - .collect() -} - -fn normalized_key(value: &str, trim_dot: bool) -> String { - let mut value = value.trim().to_ascii_lowercase(); - if trim_dot { - value = value.trim_start_matches('.').to_string(); - } - value -} diff --git a/rust/lithe-core/src/lsp/languages/mod.rs b/rust/lithe-core/src/lsp/languages/mod.rs index aed65c66..f69c0402 100644 --- a/rust/lithe-core/src/lsp/languages/mod.rs +++ b/rust/lithe-core/src/lsp/languages/mod.rs @@ -1,8 +1,5 @@ //! Dynamic provider metadata and host-model adapters for individual languages. -mod catalog; pub(crate) mod indexing; #[cfg(test)] pub(crate) mod swift; - -pub(crate) use catalog::*; diff --git a/rust/lithe-core/src/lsp/mod.rs b/rust/lithe-core/src/lsp/mod.rs index ea2e495a..9e44b41e 100644 --- a/rust/lithe-core/src/lsp/mod.rs +++ b/rust/lithe-core/src/lsp/mod.rs @@ -11,7 +11,6 @@ pub(crate) mod workspace_edit_plan; mod workspace_scope; pub(crate) use interface::*; -pub(crate) use languages::*; pub(crate) use lightweight::*; #[cfg(test)] diff --git a/rust/lithe-core/src/lsp/tests.rs b/rust/lithe-core/src/lsp/tests.rs index b4b3329b..6ab260ee 100644 --- a/rust/lithe-core/src/lsp/tests.rs +++ b/rust/lithe-core/src/lsp/tests.rs @@ -1,258 +1,6 @@ use super::*; use crate::protocol::ErrorCode; use serde_json::{json, Value}; -use std::fs; -use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; - -fn temporary_root(label: &str) -> PathBuf { - let nonce = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system clock should be valid") - .as_nanos(); - std::env::temp_dir().join(format!("lithe-lsp-{label}-{}-{nonce}", std::process::id())) -} - -#[test] -fn builtin_catalog_describes_market_lsp_providers() { - let catalog = provider_catalog(None); - let ids: Vec<_> = catalog - .providers - .iter() - .map(|provider| provider.id.as_str()) - .collect(); - assert!(ids.starts_with(&["java", "go", "python", "node", "rust"])); - assert!(ids.contains(&"swift")); - assert!(ids.contains(&"clangd")); - assert!(ids.contains(&"dockerfile")); - assert!(ids.contains(&"graphql")); - let clangd = catalog - .providers - .iter() - .find(|provider| provider.id == "clangd") - .expect("clangd provider should exist"); - assert_eq!( - clangd.language_ids_by_extension.get("m"), - Some(&"objective-c".to_string()) - ); - let swift = catalog - .providers - .iter() - .find(|provider| provider.id == "swift") - .expect("swift provider should exist"); - let swift_launch = swift - .language_server_launch - .as_ref() - .expect("swift launch descriptor should exist"); - assert_eq!( - swift_launch.executable_names, - vec!["sourcekit-lsp".to_string()] - ); - let go = catalog - .providers - .iter() - .find(|provider| provider.id == "go") - .expect("go provider should exist"); - let go_installation = go - .language_server_installation - .as_ref() - .expect("go installation descriptor should exist"); - assert_eq!(go_installation.homebrew_formula.as_deref(), Some("gopls")); - assert_eq!( - go_installation.official_download_url.as_deref(), - Some("https://go.dev/gopls/") - ); - let rust = catalog - .providers - .iter() - .find(|provider| provider.id == "rust") - .expect("rust provider should exist"); - assert_eq!( - rust.language_server_launch - .as_ref() - .expect("rust launch descriptor should exist") - .validation_arguments, - vec!["--version".to_string()] - ); - // Intelephense only speaks LSP over stdio, so a bare launch would exit - // instead of serving the editor. The catalog is the only truth for the - // macOS language server settings, so the arguments are asserted here. - let php = catalog - .providers - .iter() - .find(|provider| provider.id == "php") - .expect("php provider should exist"); - assert_eq!( - php.capabilities, - vec![ - LspProviderCapability::Run, - LspProviderCapability::LanguageServer, - LspProviderCapability::Formatting, - LspProviderCapability::Testing, - ] - ); - let php_launch = php - .language_server_launch - .as_ref() - .expect("php launch descriptor should exist"); - assert_eq!( - php_launch.executable_names, - vec!["intelephense".to_string()] - ); - assert_eq!(php_launch.arguments, vec!["--stdio".to_string()]); -} - -#[test] -fn project_config_extends_and_overrides_builtin_catalog() { - let root = temporary_root("project-config"); - fs::create_dir_all(root.join(".lithe").join("lsp")).unwrap(); - fs::write( - root.join(".lithe") - .join("lsp") - .join("language-providers.json"), - r#"{ - "version": 1, - "providers": [ - { - "id": "roc", - "displayName": "Roc", - "fileExtensions": ["roc"], - "capabilities": ["languageServer", "formatting"], - "activationPolicy": "onDemand", - "languageId": "roc" - }, - { - "id": "swift", - "fileExtensions": ["swift", "swiftinterface"], - "languageServerLaunch": { - "executableNames": ["custom-sourcekit-lsp"], - "arguments": ["--stdio"], - "environment": { - "SOURCEKIT_TOOLCHAIN": "custom" - }, - "initializationOptions": { - "indexing": true - } - }, - "languageServerInstallation": { - "homebrewFormula": "custom-sourcekit-lsp", - "officialDownloadURL": "https://example.com/sourcekit-lsp" - } - }, - { - "id": "perl", - "disabled": true - } - ] - }"#, - ) - .unwrap(); - - let catalog = provider_catalog(Some(&root)); - assert_eq!(catalog.origin, LspProviderCatalogOrigin::WorkspaceOverride); - assert!(catalog - .providers - .iter() - .any(|provider| provider.id == "roc")); - let swift = catalog - .providers - .iter() - .find(|provider| provider.id == "swift") - .expect("swift provider should still exist"); - assert!(swift - .file_extensions - .contains(&"swiftinterface".to_string())); - let swift_launch = swift - .language_server_launch - .as_ref() - .expect("swift launch descriptor should be overridden"); - assert_eq!( - swift_launch.executable_names, - vec!["custom-sourcekit-lsp".to_string()] - ); - assert_eq!(swift_launch.arguments, vec!["--stdio".to_string()]); - assert_eq!( - swift_launch.environment.get("SOURCEKIT_TOOLCHAIN"), - Some(&"custom".to_string()) - ); - assert_eq!( - swift_launch.initialization_options, - Some(json!({ "indexing": true })) - ); - let swift_installation = swift - .language_server_installation - .as_ref() - .expect("swift installation descriptor should be overridden"); - assert_eq!( - swift_installation.homebrew_formula.as_deref(), - Some("custom-sourcekit-lsp") - ); - assert!(!catalog - .providers - .iter() - .any(|provider| provider.id == "perl")); - - fs::remove_dir_all(root).unwrap(); -} - -#[test] -fn ffi_json_is_a_standalone_catalog_document() { - let raw = provider_catalog_json(None); - let value: Value = serde_json::from_str(&raw).expect("catalog should be JSON"); - assert_eq!(value["version"], 2); - assert_eq!(value["origin"], "builtin"); - assert!(value["providers"].as_array().unwrap().len() > 10); - assert!(value.get("ok").is_none()); - assert!(value.get("command").is_none()); -} - -#[test] -fn project_catalog_reports_unknown_configuration_fields() { - let root = temporary_root("project-config-unknown-field"); - fs::create_dir_all(root.join(".lithe").join("lsp")).unwrap(); - fs::write( - root.join(".lithe") - .join("lsp") - .join("language-providers.json"), - r#"{ - "version": 2, - "providers": [{ "id": "go", "languageServerLanch": {} }] - }"#, - ) - .unwrap(); - - let catalog = provider_catalog(Some(&root)); - assert_eq!(catalog.origin, LspProviderCatalogOrigin::Builtin); - assert_eq!(catalog.diagnostics.len(), 1); - assert!(catalog.diagnostics[0].message.contains("unknown field")); - assert!(catalog.providers.iter().any(|provider| provider.id == "go")); - - fs::remove_dir_all(root).unwrap(); -} - -#[test] -fn malformed_project_catalog_is_rejected_with_a_visible_diagnostic() { - let root = temporary_root("project-config-malformed"); - let config_path = root - .join(".lithe") - .join("lsp") - .join("language-providers.json"); - fs::create_dir_all(config_path.parent().unwrap()).unwrap(); - fs::write(&config_path, r#"{"version":2,"providers":["#).unwrap(); - - let catalog = provider_catalog(Some(&root)); - - assert_eq!(catalog.origin, LspProviderCatalogOrigin::Builtin); - assert!(catalog.providers.iter().any(|provider| provider.id == "go")); - assert_eq!(catalog.diagnostics.len(), 1); - assert_eq!( - catalog.diagnostics[0].path, - config_path.display().to_string() - ); - assert!(catalog.diagnostics[0].message.contains("EOF")); - - fs::remove_dir_all(root).unwrap(); -} #[test] fn text_edits_use_lsp_utf16_positions() { diff --git a/rust/lithe-core/src/plugins/declarative.rs b/rust/lithe-core/src/plugins/declarative.rs index b6d830b4..6dcd03e6 100644 --- a/rust/lithe-core/src/plugins/declarative.rs +++ b/rust/lithe-core/src/plugins/declarative.rs @@ -2446,8 +2446,8 @@ impl ServerProcess { .unwrap_or(language_server_sessions::RESOLVE_TIMEOUT_MILLISECONDS), }), }; - // Initialization options can name code the server loads (JDT LS - // bundles, for example), so changing them is a new program too. + // Initialization options can name code the server loads (bundles or + // extensions, for example), so changing them is a new program too. if let Some(options) = &self.initialization_options { program["initializationOptions"] = options.clone(); } diff --git a/rust/lithe-core/src/plugins/web_content_tests.rs b/rust/lithe-core/src/plugins/web_content_tests.rs index 19c60777..c2e80df1 100644 --- a/rust/lithe-core/src/plugins/web_content_tests.rs +++ b/rust/lithe-core/src/plugins/web_content_tests.rs @@ -382,8 +382,8 @@ fn build_methods_pass_to_a_host_that_serves_them() { page, 2, "functions.list", - json!({ "instance": "maven:root" }), + json!({ "instance": "build:root" }), ); - assert_eq!(with_instance["action"]["params"]["instance"], "maven:root"); + assert_eq!(with_instance["action"]["params"]["instance"], "build:root"); close_command(json!({ "page": page })).expect("close"); } diff --git a/rust/lithe-core/src/project_generators/archive.rs b/rust/lithe-core/src/project_generators/archive.rs index d477cc40..6180bdfa 100644 --- a/rust/lithe-core/src/project_generators/archive.rs +++ b/rust/lithe-core/src/project_generators/archive.rs @@ -358,13 +358,13 @@ mod tests { let (directory, path) = archive(&[ ("pom.xml", b"", None), ("mvnw", b"#!/bin/sh", Some(0o755)), - ("src/main/java/App.java", b"class App {}", None), + ("src/main/app/App.src", b"class App {}", None), ]); let destination = directory.path().join("project"); fs::create_dir(&destination).unwrap(); assert_eq!(extract(&path, &destination).unwrap(), 3); assert_eq!( - fs::read_to_string(destination.join("src/main/java/App.java")).unwrap(), + fs::read_to_string(destination.join("src/main/app/App.src")).unwrap(), "class App {}" ); #[cfg(unix)] diff --git a/rust/lithe-core/src/project_generators/form.rs b/rust/lithe-core/src/project_generators/form.rs index f5280dbc..8c7e941d 100644 --- a/rust/lithe-core/src/project_generators/form.rs +++ b/rust/lithe-core/src/project_generators/form.rs @@ -371,7 +371,7 @@ fn tool_choices(field: &GeneratorField, input: &FormInput) -> Vec { } /// Compares a value with the field's `maximumVersion`, expanded over the -/// earlier fields. An empty value or bound (no JDK chosen yet) is left to the +/// earlier fields. An empty value or bound (no tool chosen yet) is left to the /// fields' own checks. fn version_too_high( field: &GeneratorField, @@ -493,9 +493,9 @@ mod tests { fn hidden_fields_are_not_validated_or_read() { let generator = generator(json!([ {"id": "buildSystem", "type": "enum", "title": "Build", "options": [ - {"value": "none", "title": "None"}, {"value": "maven", "title": "Maven"}]}, + {"value": "none", "title": "None"}, {"value": "packaged", "title": "Packaged"}]}, {"id": "groupId", "type": "text", "title": "Group", - "visibleWhen": {"field": "buildSystem", "equals": "maven"}} + "visibleWhen": {"field": "buildSystem", "equals": "packaged"}} ])); let state = evaluate(&generator, &input("demo", json!({}), &[])); assert!(!field(&state, "groupId").visible); @@ -504,7 +504,7 @@ mod tests { let state = evaluate( &generator, - &input("demo", json!({"buildSystem": "maven"}), &[]), + &input("demo", json!({"buildSystem": "packaged"}), &[]), ); assert_eq!( field(&state, "groupId").problem, @@ -516,31 +516,34 @@ mod tests { fn rejects_unsafe_text_and_old_tools() { let generator = generator(json!([ {"id": "groupId", "type": "text", "title": "Group", "pattern": "[a-z.]+"}, - {"id": "jdk", "type": "tool", "title": "JDK", "tool": "java", "minimumVersion": "17"} + {"id": "compiler", "type": "tool", "title": "Compiler", "tool": "compiler", "minimumVersion": "17"} ])); let mut form = input("demo", json!({"groupId": "-Dx=y"}), &["groupId"]); form.tools - .insert("jdk".into(), vec![installation("/jdk", "11.0.2")]); + .insert("compiler".into(), vec![installation("/compiler", "11.0.2")]); let state = evaluate(&generator, &form); assert_eq!( field(&state, "groupId").problem, Some(FieldProblem::InvalidFormat) ); - assert_eq!(field(&state, "jdk").problem, Some(FieldProblem::ToolTooOld)); + assert_eq!( + field(&state, "compiler").problem, + Some(FieldProblem::ToolTooOld) + ); } - /// A target release newer than the selected JDK cannot compile, so the - /// bound follows the JDK; before a JDK is chosen nothing is compared. + /// A target level newer than the selected compiler cannot compile, so the + /// bound follows the compiler; before one is chosen nothing is compared. #[test] fn maximum_version_follows_an_earlier_tool() { let generator = generator(json!([ - {"id": "jdk", "type": "tool", "title": "JDK", "tool": "java", "minimumVersion": "17"}, - {"id": "javaVersion", "type": "enum", "title": "Java", "maximumVersion": "${field.jdk.major}", + {"id": "compiler", "type": "tool", "title": "Compiler", "tool": "compiler", "minimumVersion": "17"}, + {"id": "languageLevel", "type": "enum", "title": "Level", "maximumVersion": "${field.compiler.major}", "options": [{"value": "17", "title": "17"}, {"value": "21", "title": "21"}]} ])); - let mut form = input("demo", json!({"javaVersion": "21"}), &[]); + let mut form = input("demo", json!({"languageLevel": "21"}), &[]); assert_eq!( - field(&evaluate(&generator, &form), "javaVersion").problem, + field(&evaluate(&generator, &form), "languageLevel").problem, None ); for (version, problem) in [ @@ -549,9 +552,9 @@ mod tests { ("25", None), ] { form.tools - .insert("jdk".into(), vec![installation("/jdk", version)]); + .insert("compiler".into(), vec![installation("/compiler", version)]); assert_eq!( - field(&evaluate(&generator, &form), "javaVersion").problem, + field(&evaluate(&generator, &form), "languageLevel").problem, problem, "{version}" ); diff --git a/rust/lithe-core/src/project_generators/manifest.rs b/rust/lithe-core/src/project_generators/manifest.rs index 0148b6e1..e8830c61 100644 --- a/rust/lithe-core/src/project_generators/manifest.rs +++ b/rust/lithe-core/src/project_generators/manifest.rs @@ -304,8 +304,8 @@ pub(crate) enum GeneratorStep { arguments: Vec, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] environment: BTreeMap, - /// The tool writes the project into this subdirectory (Maven's - /// archetype plugin uses the artifact ID); its contents become the + /// The tool writes the project into this subdirectory (a scaffolding + /// tool may name it after the project); its contents become the /// project root. #[serde(default, skip_serializing_if = "Option::is_none")] collect: Option, diff --git a/rust/lithe-core/src/project_generators/mod.rs b/rust/lithe-core/src/project_generators/mod.rs index 9e77afb0..968b80f6 100644 --- a/rust/lithe-core/src/project_generators/mod.rs +++ b/rust/lithe-core/src/project_generators/mod.rs @@ -5,8 +5,8 @@ //! A plugin describes a way to start a project as data (`projectGenerators` //! in `plugin.json`): where it is listed, which fields the user fills in, //! which plugins the result needs, and the steps that produce it — template -//! files from the package, a scaffolding command such as `mvn -//! archetype:generate`, or a zip archive such as Spring Initializr's. Core +//! files from the package, a scaffolding command the plugin names, or a zip +//! archive downloaded from a project starter service. Core //! validates the declaration, evaluates the form (derived defaults, //! visibility, errors), and turns a filled form into a plan: expanded files, //! argv, and URLs. Hosts own the window, resolve executables, download, run diff --git a/rust/lithe-core/src/project_generators/template.rs b/rust/lithe-core/src/project_generators/template.rs index f871805c..ca98af17 100644 --- a/rust/lithe-core/src/project_generators/template.rs +++ b/rust/lithe-core/src/project_generators/template.rs @@ -1,8 +1,8 @@ //! Variables of project generator templates. //! //! Strings in the manifest use the `${...}` syntax every plugin template -//! uses. Template files in the package use `{{ ... }}` instead, because Maven -//! POMs, Gradle scripts, and Spring properties already contain `${...}` that +//! uses. Template files in the package use `{{ ... }}` instead, because build +//! scripts and property files of many tools already contain `${...}` that //! must reach the project unchanged. In files, only `{{` followed by //! `project.` or `field.` is a variable; any other `{{` is copied as text. @@ -31,7 +31,7 @@ pub(crate) enum Source { pub(crate) enum Modifier { /// Dots become `/`: a package name as a source directory. Path, - /// A valid Java-style package: lowercase, only letters, digits, + /// A dotted package identifier: lowercase, only letters, digits, /// underscores, and single dots, each segment starting with a letter or /// underscore. `com.example.my-app` becomes `com.example.myapp`. Package, @@ -204,16 +204,17 @@ fn apply(modifier: Modifier, value: &str) -> String { } } -/// `1.8.0_412` → `8`, `21.0.2` → `21`, `25` → `25`. +/// The first numeric component: `21.0.2` → `21`, `25` → `25`. +/// +/// Tool-specific numbering (such as a legacy `1.` prefix) is normalized by the +/// tool definition's `version.pattern` capture before the version reaches a +/// template, so this reader never interprets a tool's scheme. pub(crate) fn major_version(version: &str) -> String { - let mut numbers = version + version .split(|character: char| !character.is_ascii_digit()) - .filter(|part| !part.is_empty()); - match (numbers.next(), numbers.next()) { - (Some("1"), Some(minor)) => minor.to_string(), - (Some(major), _) => major.to_string(), - _ => String::new(), - } + .find(|part| !part.is_empty()) + .unwrap_or_default() + .to_string() } fn package_name(value: &str) -> String { @@ -259,18 +260,18 @@ mod tests { }) ); assert_eq!( - Variable::parse("field.jdk.major").map(|variable| variable.source), - Some(Source::ToolMajor("jdk".into())) + Variable::parse("field.compiler.major").map(|variable| variable.source), + Some(Source::ToolMajor("compiler".into())) ); assert_eq!( - Variable::parse("field.jdk.version").map(|variable| variable.source), - Some(Source::ToolVersion("jdk".into())) + Variable::parse("field.compiler.version").map(|variable| variable.source), + Some(Source::ToolVersion("compiler".into())) ); assert_eq!( Variable::parse("field.python.executable").map(|variable| variable.source), Some(Source::ToolExecutable("python".into())) ); - assert!(Variable::parse("field.jdk.home").is_none()); + assert!(Variable::parse("field.compiler.home").is_none()); assert!(Variable::parse("project.path").is_none()); assert!(Variable::parse("field.name|upper").is_none()); } @@ -310,8 +311,9 @@ mod tests { } #[test] - fn major_versions_follow_java_numbering() { - assert_eq!(major_version("1.8.0_412"), "8"); + fn major_versions_take_the_first_numeric_component() { + assert_eq!(major_version("8.0_412"), "8"); + assert_eq!(major_version("1.8.0"), "1"); assert_eq!(major_version("25.0.3"), "25"); assert_eq!(major_version(""), ""); } diff --git a/rust/lithe-core/src/protocol/command.rs b/rust/lithe-core/src/protocol/command.rs index b79780eb..d261453f 100644 --- a/rust/lithe-core/src/protocol/command.rs +++ b/rust/lithe-core/src/protocol/command.rs @@ -374,8 +374,6 @@ pub enum CoreCommand { ExecutionResolveEnvironment, /// Resolves deterministic process search-path ordering (`execution.resolveSearchPath`). ExecutionResolveSearchPath, - /// Moves oversized Java path-list options into a JDK argument file (`execution.planLaunchCommand`). - ExecutionPlanLaunchCommand, /// Decides how each Lithe-owned process ends (`processLifecycle.resolve`). ProcessLifecycleResolve, /// Classifies a process snapshot into lifecycle items (`processLifecycle.inventory`). @@ -999,7 +997,6 @@ impl CoreCommand { "diff.compareTexts" => Some(Self::DiffCompareTexts), "execution.resolveEnvironment" => Some(Self::ExecutionResolveEnvironment), "execution.resolveSearchPath" => Some(Self::ExecutionResolveSearchPath), - "execution.planLaunchCommand" => Some(Self::ExecutionPlanLaunchCommand), "processLifecycle.resolve" => Some(Self::ProcessLifecycleResolve), "processLifecycle.inventory" => Some(Self::ProcessLifecycleInventory), "processLifecycle.settle" => Some(Self::ProcessLifecycleSettle), diff --git a/rust/lithe-core/src/runtime/dispatcher.rs b/rust/lithe-core/src/runtime/dispatcher.rs index 857434df..25b0e142 100644 --- a/rust/lithe-core/src/runtime/dispatcher.rs +++ b/rust/lithe-core/src/runtime/dispatcher.rs @@ -1361,22 +1361,6 @@ fn execute_parsed(mut parsed: CoreRequest, document: Option<&str>) -> CoreRespon ), } } - CoreCommand::ExecutionPlanLaunchCommand => { - match serde_json::from_value::(parsed.payload) { - Ok(request) => match crate::execution::plan_launch_command(&request) { - Ok(plan) => CoreResponse::success( - id, - serde_json::to_value(plan).expect("launch plan encodes"), - ), - Err(error) => CoreResponse::failure(id, error), - }, - Err(error) => CoreResponse::failure( - id, - CoreError::new(ErrorCode::InvalidRequest, "Invalid launch command request") - .with_details(error.to_string()), - ), - } - } CoreCommand::ProcessLifecycleResolve => { match serde_json::from_value::( parsed.payload, diff --git a/rust/lithe-core/src/runtime/ffi.rs b/rust/lithe-core/src/runtime/ffi.rs index 16441f68..25e721dd 100644 --- a/rust/lithe-core/src/runtime/ffi.rs +++ b/rust/lithe-core/src/runtime/ffi.rs @@ -2,7 +2,6 @@ use std::ffi::{CStr, CString}; use std::os::raw::c_char; -use std::path::PathBuf; /// Returns a pointer to the static, NUL-terminated Core ABI version. /// @@ -95,32 +94,6 @@ pub unsafe extern "C" fn lithe_core_execute_document_json( } } -/// Loads the merged language-provider catalog for an optional workspace root. -/// -/// The returned string is owned by the caller and must be released exactly -/// once with [`lithe_core_free_string`]. -/// -/// # Safety -/// -/// `workspace_root` must be null or point to a readable, NUL-terminated byte -/// string for the duration of this call. -#[no_mangle] -pub unsafe extern "C" fn lithe_core_lsp_provider_catalog_json( - workspace_root: *const c_char, -) -> *mut c_char { - let root = if workspace_root.is_null() { - None - } else { - let value = CStr::from_ptr(workspace_root).to_string_lossy(); - if value.trim().is_empty() { - None - } else { - Some(PathBuf::from(value.as_ref())) - } - }; - response_pointer(&crate::lsp::provider_catalog_json(root.as_deref())) -} - /// Requests cooperative cancellation of an in-flight operation. The call is /// thread-safe and returns 1 when an active operation was found. /// diff --git a/rust/lithe-core/src/tests/build_project_options.rs b/rust/lithe-core/src/tests/build_project_options.rs index 35370434..55cfb88f 100644 --- a/rust/lithe-core/src/tests/build_project_options.rs +++ b/rust/lithe-core/src/tests/build_project_options.rs @@ -156,11 +156,11 @@ fn native_build_options_validate_the_shared_cases_without_dropping_custom_values .unwrap(); let result = project_options_command(json!({"document":defaults})).unwrap(); assert_eq!( - result["document"]["systems"]["maven"]["savedChoices"]["profiles"], + result["document"]["systems"]["make"]["savedChoices"]["profiles"], json!(["ci", "manual"]) ); assert_eq!( - result["document"]["systems"]["maven"]["instances"]["services/a"]["offline"], + result["document"]["systems"]["make"]["instances"]["services/a"]["offline"], true ); } diff --git a/rust/lithe-core/src/tests/plugin_run_debug.rs b/rust/lithe-core/src/tests/plugin_run_debug.rs index a56c0608..f9af00b5 100644 --- a/rust/lithe-core/src/tests/plugin_run_debug.rs +++ b/rust/lithe-core/src/tests/plugin_run_debug.rs @@ -230,6 +230,42 @@ fn temporary_files_refuse_what_the_host_could_not_write_safely() { Some(absolute.as_str()), "temporaryFiles[0].contents", ), + ( + "a file with both text and bytes", + tool_run( + json!([]), + json!({}), + json!([{ "name": "a", "contents": "", "base64": "" }]), + ), + Some(absolute.as_str()), + "temporaryFiles[0]", + ), + ( + "a file with neither text nor bytes", + tool_run(json!([]), json!({}), json!([{ "name": "a" }])), + Some(absolute.as_str()), + "temporaryFiles[0]", + ), + ( + "bytes that are not standard base64", + tool_run( + json!([]), + json!({}), + json!([{ "name": "a", "base64": "not base64!" }]), + ), + Some(absolute.as_str()), + "temporaryFiles[0].base64", + ), + ( + "bytes beyond 4 MiB", + tool_run( + json!([]), + json!({}), + json!([{ "name": "a", "base64": "A".repeat((4 * 1024 * 1024 / 3 + 1) * 4) }]), + ), + Some(absolute.as_str()), + "temporaryFiles[0].base64", + ), ( "a reference to a file the start does not declare", tool_run(json!(["${temporaryFile:missing}"]), json!({}), json!([])), @@ -263,6 +299,34 @@ fn temporary_files_refuse_what_the_host_could_not_write_safely() { } } +/// Bytes pass through unchanged, so a plugin can write a file in the encoding its format +/// requires; Core never transcodes them. +#[test] +fn temporary_file_bytes_reach_the_host_as_given() { + let workspace = Workspace::new("run-start-bytes", &[]); + let directory = temporary_root("run-start-bytes-directory") + .to_string_lossy() + .into_owned(); + // 0x95 0x5C is a double-byte character whose trail byte is a backslash. + let outcome = start_run( + &workspace, + tool_run( + json!(["@${temporaryFile:args}"]), + json!({}), + json!([{ "name": "args", "base64": "lVwK" }]), + ), + Some(&directory), + ); + + assert_eq!(outcome["status"], "ok", "{outcome}"); + let path = format!("{directory}{}args", std::path::MAIN_SEPARATOR); + assert_eq!(outcome["start"]["arguments"], json!([format!("@{path}")])); + assert_eq!( + outcome["start"]["temporaryFiles"], + json!([{ "path": path, "base64": "lVwK" }]) + ); +} + #[test] fn a_start_with_four_temporary_files_is_accepted() { let workspace = Workspace::new("run-start-four-files", &[]); diff --git a/rust/lithe-core/src/tests/run_configuration.rs b/rust/lithe-core/src/tests/run_configuration.rs index 1a2ec741..746b71ab 100644 --- a/rust/lithe-core/src/tests/run_configuration.rs +++ b/rust/lithe-core/src/tests/run_configuration.rs @@ -38,9 +38,9 @@ fn generic_preference_edits_preserve_run_metadata_without_creating_legacy_defaul ); for original in [ serde_json::json!({"version":2,"configurations":[]}), - serde_json::json!({"version":1,"configurations":[{"id":"user:legacy","type":"java.main","mainClass":"fixture.Main","workingDirectory":".","future":"preserve"}]}), + serde_json::json!({"version":1,"configurations":[{"id":"user:legacy","type":"example.main","entry":"fixture.Main","workingDirectory":".","future":"preserve"}]}), serde_json::json!({"version":2,"configurations":[{"id":"user:task","env":{"TOKEN":"fixture-value"},"future":[1,2]}], - "futureMetadata":{"keep":true},"toolchain":{"java":{"homePath":"/fixture/legacy-jdk"},"futureToolchain":{"keep":true}}}), + "futureMetadata":{"keep":true},"toolchain":{"runtime":{"homePath":"/fixture/legacy-runtime"},"futureToolchain":{"keep":true}}}), ] { let result = call( "runConfig.updateToolPreferences", @@ -101,7 +101,7 @@ fn generic_preference_editor_rejects_invalid_documents_and_references_without_wr ), ( serde_json::json!({"version":2,"configurations":[]}), - serde_json::json!({"version":1,"workspace":{"tools":{"java":{"inherit":"workspace"}}}}), + serde_json::json!({"version":1,"workspace":{"tools":{"runtime":{"inherit":"workspace"}}}}), ), ] { let result: Value = serde_json::from_str(&execute_json( @@ -145,7 +145,7 @@ fn project_tool_preferences_preserve_symbolic_choices_across_legacy_editor_saves let changed = call( "runConfig.updateOptions", serde_json::json!({"root":root,"scope":"local","configurationId":"","localDocument":document, - "toolchain":{"javaHomePath":"/fixture/changed-jdk"}}), + "toolchain":{"legacyHomePath":"/fixture/changed-home"}}), ); assert_eq!(changed["ok"], true, "{changed}"); let saved: Value = serde_json::from_str(changed["data"]["document"].as_str().unwrap()).unwrap(); @@ -241,11 +241,11 @@ fn resolve_prefers_a_host_provided_local_document() { fs::create_dir_all(root.join(".lithe/run")).unwrap(); seed_generated( &root, - r#"{"version":2,"configurations":[{"id":"current-file","name":"Current File","provider":"java.current-file","execution":"application","toolchains":{"java":"project-jdk"}}]}"#, + r#"{"version":2,"configurations":[{"id":"current-file","name":"Current File","provider":"example.current-file","execution":"application","toolchains":{"runtime":"project-runtime"}}]}"#, ); fs::write( root.join(".lithe/run/local.json"), - r#"{"version":2,"configurations":[{"id":"current-file","name":"Project Local","provider":"java.current-file","cwd":"."}]}"#, + r#"{"version":2,"configurations":[{"id":"current-file","name":"Project Local","provider":"example.current-file","cwd":"."}]}"#, ) .unwrap(); @@ -260,7 +260,7 @@ fn resolve_prefers_a_host_provided_local_document() { "configurations": [{ "id": "current-file", "name": "This PC", - "provider": "java.current-file", + "provider": "example.current-file", "cwd": "." }] } @@ -291,7 +291,7 @@ fn resolve_prefers_a_host_provided_local_document() { "configurations": [{ "id": "current-file", "name": "Legacy This PC", - "type": "java.current-file", + "type": "example.current-file", "programArguments": ["--dev"] }] } @@ -450,15 +450,15 @@ fn run_configuration_inspect_reports_malformed_and_unsupported_documents() { fn run_configuration_inspection_summarizes_changed_inputs() { let root = temporary_root("run-config-input-summary"); fs::create_dir_all(root.join("src")).unwrap(); - fs::write(root.join("src/App.java"), "class App {}").unwrap(); + fs::write(root.join("src/app.txt"), "app").unwrap(); fs::write(root.join("package.json"), r#"{"name":"demo"}"#).unwrap(); - let generated = generate_run_configuration(&root, &["src/App.java"], None); - let generated_again = generate_run_configuration(&root, &["src/App.java"], None); + let generated = generate_run_configuration(&root, &["src/app.txt"]); + let generated_again = generate_run_configuration(&root, &["src/app.txt"]); assert_eq!(generated, generated_again); write_generated_run_document(&root, &generated); fs::write(root.join("package.json"), r#"{"name":"renamed"}"#).unwrap(); - let inspected = inspect_run_configuration(&root, None); + let inspected = inspect_run_configuration(&root); assert_eq!( inspected["diagnostics"][0]["message"], "Project inputs changed: 0 added, 0 removed, 1 modified" @@ -467,15 +467,8 @@ fn run_configuration_inspection_summarizes_changed_inputs() { fs::remove_dir_all(root).unwrap(); } -fn generate_run_configuration( - root: &std::path::Path, - paths: &[&str], - java_entrypoints: Option, -) -> Value { - let mut payload = serde_json::json!({ "root": root, "paths": paths, "modulePaths": [] }); - if let Some(entrypoints) = java_entrypoints { - payload["javaEntrypoints"] = entrypoints; - } +fn generate_run_configuration(root: &std::path::Path, paths: &[&str]) -> Value { + let payload = serde_json::json!({ "root": root, "paths": paths, "modulePaths": [] }); let response: Value = serde_json::from_str(&execute_json( &serde_json::json!({ "id": "generate", "command": "runConfig.generate", "payload": payload }) .to_string(), @@ -487,14 +480,11 @@ fn generate_run_configuration( fn write_generated_run_document(root: &std::path::Path, generated: &Value) { fs::create_dir_all(root.join(".lithe/run")).unwrap(); - seed_generated(&root, serde_json::to_string(generated).unwrap()); + seed_generated(root, serde_json::to_string(generated).unwrap()); } -fn inspect_run_configuration(root: &std::path::Path, java_entrypoints: Option) -> Value { - let mut payload = serde_json::json!({ "root": root }); - if let Some(entrypoints) = java_entrypoints { - payload["javaEntrypoints"] = entrypoints; - } +fn inspect_run_configuration(root: &std::path::Path) -> Value { + let payload = serde_json::json!({ "root": root }); let response: Value = serde_json::from_str(&execute_json( &serde_json::json!({ "id": "inspect", "command": "runConfig.inspect", "payload": payload }) .to_string(), @@ -511,11 +501,11 @@ fn run_configuration_resolve_matches_toolchains_and_rejects_unsafe_paths() { fs::create_dir_all(root.join(".lithe/toolchains")).unwrap(); seed_generated( &root, - r#"{"version":1,"configurations":[{"id":"current-file","name":"Current File","type":"java.current-file","toolchains":{"java":"project-jdk"}}]}"#, + r#"{"version":1,"configurations":[{"id":"current-file","name":"Current File","type":"example.current-file","toolchains":{"runtime":"project-runtime"}}]}"#, ); seed_requirements( &root, - r#"{"version":1,"toolchains":{"project-jdk":{"type":"java","minimumVersion":"21","preferredVendor":"temurin"}}}"#, + r#"{"version":1,"toolchains":{"project-runtime":{"type":"runtime","minimumVersion":"21","preferredVendor":"temurin"}}}"#, ); let resolve = |version: &str, vendor: &str| -> Value { @@ -526,8 +516,8 @@ fn run_configuration_resolve_matches_toolchains_and_rejects_unsafe_paths() { "payload": { "root": root, "toolchainCandidates": [{ - "id": "project-jdk", - "type": "java", + "id": "project-runtime", + "type": "runtime", "version": version, "vendor": vendor }] @@ -577,7 +567,7 @@ fn legacy_v2_runtime_requirements_are_reconciled_without_regeneration() { seed_generated( &root, r#"{"version":2,"configurations":[ - {"id":"spring","name":"backend","provider":"spring-boot.maven","execution":"service","cwd":".","toolchains":{"java":"project-jdk","maven":"project-maven"}}, + {"id":"service","name":"backend","provider":"build.service","execution":"service","cwd":".","toolchains":{"runtime":"project-runtime","build":"project-build"}}, {"id":"npm","name":"web","provider":"npm.script","execution":"service","command":"npm","args":["run","dev"],"cwd":"web","toolchains":{}}, {"id":"bun","name":"bun web","provider":"npm.script","execution":"service","command":"bun","args":["run","dev"],"cwd":"bun-web","toolchains":{"runtime":"project-node"}} ]}"#, @@ -585,8 +575,8 @@ fn legacy_v2_runtime_requirements_are_reconciled_without_regeneration() { seed_requirements( &root, r#"{"version":1,"toolchains":{ - "project-jdk":{"type":"java"}, - "project-maven":{"type":"maven","java":"project-jdk"}, + "project-runtime":{"type":"runtime"}, + "project-build":{"type":"build-tool"}, "project-node":{"type":"node","minimumVersion":"22"} }}"#, ); @@ -598,8 +588,8 @@ fn legacy_v2_runtime_requirements_are_reconciled_without_regeneration() { "payload": { "root": root, "toolchainCandidates": [ - {"id":"project-jdk","type":"java","version":"21","vendor":"Temurin"}, - {"id":"project-maven","type":"maven","version":"3.9.9","vendor":""} + {"id":"project-runtime","type":"runtime","version":"21","vendor":"Example"}, + {"id":"project-build","type":"build-tool","version":"3.9.9","vendor":""} ] } }) @@ -656,7 +646,7 @@ fn shared_run_configuration_fixtures_have_the_versioned_contract_shape() { #[test] fn shared_hybrid_fixture_executes_scoped_toolchain_expectations() { let fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../fixtures/run-configuration/hybrid-spring-vue.json"); + .join("../fixtures/run-configuration/hybrid-service-web.json"); let fixture: Value = serde_json::from_str(&fs::read_to_string(fixture_path).unwrap()).unwrap(); let root = temporary_root("run-config-hybrid-fixture"); fs::create_dir_all(root.join(".lithe/run")).unwrap(); @@ -674,8 +664,8 @@ fn shared_hybrid_fixture_executes_scoped_toolchain_expectations() { "payload": { "root": root, "toolchainCandidates": [ - {"id":"project-jdk","type":"java","version":"21","vendor":"Temurin"}, - {"id":"project-maven","type":"maven","version":"3.9.9","vendor":""} + {"id":"project-runtime","type":"runtime","version":"21","vendor":"Example"}, + {"id":"project-build","type":"build-tool","version":"3.9.9","vendor":""} ] } }) @@ -725,7 +715,7 @@ fn shared_editor_save_fixture_executes_the_document_contract() { fs::create_dir_all(root.join("backend")).unwrap(); seed_generated( &root, - r#"{"version":2,"configurations":[{"id":"spring","name":"Spring","provider":"spring-boot.maven","execution":"service","toolchains":{"java":"project-jdk","maven":"project-maven"},"extensions":{"maven":{"module":"."}}}]}"#, + r#"{"version":2,"configurations":[{"id":"server","name":"Server","provider":"build.service","execution":"service","toolchains":{"runtime":"project-runtime"},"extensions":{"build":{"module":"."}}}]}"#, ); let mut request = fixture["request"].clone(); @@ -753,7 +743,7 @@ fn migration_leaves_sidecar_documents_at_their_own_version() { fs::create_dir_all(root.join(".lithe/toolchains")).unwrap(); seed_generated( &root, - r#"{"version":1,"configurations":[{"id":"current-file","name":"Current File","type":"java.current-file"}]}"#, + r#"{"version":1,"configurations":[{"id":"current-file","name":"Current File","type":"example.current-file"}]}"#, ); fs::write( root.join(".lithe/project.json"), @@ -778,10 +768,10 @@ fn migration_leaves_sidecar_documents_at_their_own_version() { fs::remove_dir_all(root).unwrap(); } -/// A non-Java service must reach a launch plan without acquiring a Java -/// toolchain or a JAVA_HOME it has no use for. +/// A service that names no toolchain reaches a launch plan with its own +/// command and no toolchain binding. #[test] -fn process_configurations_launch_without_java_assumptions() { +fn process_configurations_launch_without_a_toolchain() { let root = temporary_root("run-config-process"); fs::create_dir_all(root.join(".lithe/run")).unwrap(); fs::create_dir_all(root.join("frontend")).unwrap(); @@ -816,7 +806,6 @@ fn process_configurations_launch_without_java_assumptions() { assert_eq!(plan["data"]["arguments"], serde_json::json!(["run", "dev"])); assert_eq!(plan["data"]["workingDirectory"], "frontend"); assert_eq!(plan["data"]["env"]["PORT"], "3000"); - assert!(plan["data"]["environment"]["JAVA_HOME"].is_null()); fs::remove_dir_all(root).unwrap(); } @@ -851,14 +840,13 @@ fn toolchain_backed_process_uses_the_generic_runtime_binding() { serde_json::json!(["run", "./cmd/api"]) ); assert_eq!(plan["data"]["env"]["APP_ENV"], "dev"); - assert!(plan["data"]["environment"]["JAVA_HOME"].is_null()); fs::remove_dir_all(root).unwrap(); } #[test] -fn pure_go_generation_does_not_require_java_or_add_java_current_file() { - let root = temporary_root("pure-go-no-jdk"); +fn pure_go_generation_adds_no_current_file_configuration() { + let root = temporary_root("pure-go-generation"); fs::create_dir_all(&root).unwrap(); fs::write(root.join("go.mod"), "module example.com/api\n\ngo 1.24\n").unwrap(); fs::write(root.join("main.go"), "package main\nfunc main() {}\n").unwrap(); @@ -882,7 +870,6 @@ fn pure_go_generation_does_not_require_java_or_add_java_current_file() { assert!(!configurations .iter() .any(|value| value["id"] == "current-file")); - assert!(generated["data"]["toolchainRequirements"]["toolchains"]["project-jdk"].is_null()); fs::remove_dir_all(root).unwrap(); } @@ -934,7 +921,6 @@ fn multi_language_generation_declares_only_consumed_runtime_requirements() { for id in ["project-go", "project-python", "project-cargo"] { assert!(requirements[id].is_null(), "{id}: {requirements}"); } - assert!(requirements["project-jdk"].is_null(), "{requirements}"); for provider in ["go.main", "python.script", "cargo.binary"] { let configuration = generated["data"]["generated"]["configurations"] .as_array() @@ -949,7 +935,7 @@ fn multi_language_generation_declares_only_consumed_runtime_requirements() { } #[test] -fn unknown_provider_without_an_executable_never_falls_into_maven() { +fn unknown_provider_without_an_executable_is_rejected() { let root = temporary_root("run-config-unknown-provider"); fs::create_dir_all(root.join(".lithe/run")).unwrap(); seed_generated( @@ -979,8 +965,8 @@ fn unknown_provider_without_an_executable_never_falls_into_maven() { } /// Generic editor options must patch the common process shape. Writing -/// them into extensions.maven makes the UI appear to save successfully -/// while Go/Python/Node launch plans continue using the old arguments. +/// them into a provider extension makes the UI appear to save successfully +/// while process launch plans continue using the old arguments. #[test] fn process_options_update_common_arguments_and_environment() { let root = temporary_root("run-config-process-options"); @@ -1023,7 +1009,7 @@ fn process_options_update_common_arguments_and_environment() { serde_json::json!(["app.py", "--port", "9000"]) ); assert_eq!(patch["env"]["APP_ENV"], "test"); - assert!(patch["extensions"]["maven"].is_null()); + assert!(patch.get("extensions").is_none(), "{patch}"); fs::write( root.join(".lithe/run/local.json"), @@ -1094,11 +1080,11 @@ fn resolve_chooses_detected_default_unless_the_project_names_one() { seed_generated( &root, serde_json::json!({"version": 2, "configurations": [ - {"id": "current-file", "name": "Current File", "provider": "java.current-file", + {"id": "current-file", "name": "Current File", "provider": "example.current-file", "execution": "application", "toolchains": {}}, - {"id": "java-main:demo.Tool", "name": "Tool", "provider": "java.main", + {"id": "example-main:demo.Tool", "name": "Tool", "provider": "example.main", "execution": "application", "toolchains": {}}, - {"id": "spring-boot.maven:.", "name": "app", "provider": "spring-boot.maven", + {"id": "build.service:.", "name": "app", "provider": "build.service", "execution": "service", "toolchains": {}} ]}), ); @@ -1106,19 +1092,19 @@ fn resolve_chooses_detected_default_unless_the_project_names_one() { assert_eq!(detected["ok"], true, "{detected}"); assert_eq!( detected["data"]["defaultRunConfiguration"], - "spring-boot.maven:." + "build.service:." ); fs::create_dir_all(root.join(".lithe")).unwrap(); fs::write( root.join(".lithe/project.json"), - r#"{"version":1,"defaultRunConfiguration":"java-main:demo.Tool"}"#, + r#"{"version":1,"defaultRunConfiguration":"example-main:demo.Tool"}"#, ) .unwrap(); let named = run_config_command("runConfig.resolve", serde_json::json!({ "root": root })); assert_eq!( named["data"]["defaultRunConfiguration"], - "java-main:demo.Tool" + "example-main:demo.Tool" ); } @@ -1197,24 +1183,6 @@ fn working_directory_override_must_name_an_existing_project_directory() { assert_eq!(document["configurations"][0]["cwd"], "backend"); } -#[test] -fn java_selection_rejects_ambiguous_or_invalid_host_snapshots() { - for payload in [ - serde_json::json!({"root":"/unused","requirements":{"version":1,"toolchains":{}},"candidates":[]}), - serde_json::json!({"requirements":{"version":999,"toolchains":{}},"candidates":[]}), - serde_json::json!({"requirements":{"toolchains":{}},"candidates":[]}), - ] { - let response: Value = serde_json::from_str(&execute_json( - &serde_json::json!({ - "id":"snapshot","command":"runConfig.selectJava","payload":payload - }) - .to_string(), - )) - .unwrap(); - assert_eq!(response["ok"], false, "{response}"); - } -} - #[test] fn shared_run_scan_excludes_caches_and_keeps_hidden_sources() { let root = temporary_root("shared-run-scan"); @@ -1400,7 +1368,7 @@ fn build_provider_configurations_persist_before_generation_and_reuse_identity() ("projectID", serde_json::json!("")), ("userArguments", serde_json::json!(["bad\u{0}"])), ("userArguments", serde_json::json!(vec!["a"; 65])), - ("command", serde_json::json!("java")), + ("command", serde_json::json!("runtime")), ] { let mut invalid = fixture["reference"].clone(); invalid[field] = value; @@ -1419,7 +1387,7 @@ fn build_provider_configurations_persist_before_generation_and_reuse_identity() serde_json::json!([]) ); let mut invalid = document.clone(); - invalid["configurations"][0]["command"] = serde_json::json!("java"); + invalid["configurations"][0]["command"] = serde_json::json!("runtime"); fs::write(&local, invalid.to_string()).unwrap(); assert_eq!( call("runConfig.resolve", serde_json::json!({"root":root}))["ok"], @@ -1757,7 +1725,7 @@ fn provider_run_options_reject_malformed_or_oversized_values() { } } -/// A non-Java provider owns discovery; regenerating labels must retain user choices, +/// A build provider owns discovery; regenerating labels must retain user choices, /// while withdrawing a target must not resurrect it from the previous detected layer. #[test] fn generic_provider_runs_regenerate_without_language_facts_and_keep_stable_overrides() { @@ -1796,7 +1764,14 @@ fn generic_provider_runs_regenerate_without_language_facts_and_keep_stable_overr let id = configuration["id"].clone(); assert_eq!(configuration["provider"], "build.provider"); assert!(configuration["toolchains"].as_object().unwrap().is_empty()); - assert!(configuration["extensions"].get("maven").is_none()); + assert!( + configuration["extensions"] + .as_object() + .unwrap() + .keys() + .all(|key| key == "build"), + "{configuration}" + ); fs::write( root.join(".lithe/run/local.json"), serde_json::json!({"version":2,"configurations":[ @@ -1859,7 +1834,7 @@ fn generic_provider_runs_regenerate_without_language_facts_and_keep_stable_overr assert!(!root.join(".lithe/run/generated.json").exists()); } -/// Providers receive saved typed choices; Core does not assemble a Maven goal or reread its POM. +/// Providers receive saved typed choices; Core does not assemble a build goal or reread a build file. #[test] fn provider_launch_inputs_preserve_document_layers_and_current_selector() { let root = temporary_root("provider-launch-inputs"); @@ -1873,7 +1848,11 @@ fn provider_launch_inputs_preserve_document_layers_and_current_selector() { } } let _cleanup = Cleanup(root.clone()); - fs::write(root.join("nested/pom.xml"), "deliberately not a POM").unwrap(); + fs::write( + root.join("nested/build.manifest"), + "deliberately not a manifest", + ) + .unwrap(); let generated = serde_json::json!({"version":2,"configurations":[{"id":"service","name":"Service","provider":"build.provider","execution":"service", "cwd":"nested","env":{},"extensions":{"build":{"pluginID":"dev.example.engine","buildSystemID":"engine","root":"nested","run":"serve", "projectID":"project","targetID":"target","execution":"service"},"build-run":{"runtimeArguments":["--message=base value"]}}}]}); @@ -1938,14 +1917,9 @@ fn provider_launch_inputs_require_a_typed_reference_after_domain_migration() { } } let _cleanup = Cleanup(root.clone()); - for provider in [ - "maven.module", - "spring-boot.maven", - "quarkus.maven", - "micronaut.maven", - ] { + for provider in ["legacy.module", "legacy.framework", "legacy.service"] { let generated = serde_json::json!({"version":2,"configurations":[{"id":"legacy","name":"Legacy", - "provider":provider,"execution":"task","extensions":{"maven":{"module":".","profiles":["saved"],"skipTests":false}}}]}); + "provider":provider,"execution":"task","extensions":{"legacy":{"module":".","profiles":["saved"],"skipTests":false}}}]}); seed_generated(&root, generated.to_string()); let reply: Value = serde_json::from_str(&execute_json( &serde_json::json!({"id":"unmigrated","command":"runConfig.providerLaunchInputs", diff --git a/rust/lithe-core/src/tests/toolchain_pins.rs b/rust/lithe-core/src/tests/toolchain_pins.rs index 6f1ceb8e..b66657be 100644 --- a/rust/lithe-core/src/tests/toolchain_pins.rs +++ b/rust/lithe-core/src/tests/toolchain_pins.rs @@ -56,7 +56,7 @@ fn toolchain_pins_do_not_match_sibling_prefixes_or_other_tools() { fn toolchain_pins_retain_dormant_namespaced_values_and_legacy_data() { let document = json!({ "version":1, - "toolchains":{"project-jdk":{"home":"relative legacy home","javaHome":"legacy JDK"},"custom old key":{"executable":"legacy-command"}}, + "toolchains":{"project-runtime":{"home":"relative legacy home","legacyHome":"legacy runtime"},"custom old key":{"executable":"legacy-command"}}, "pins":{".":{"dev.lithe.fixture.disabled:compiler":pin("/opt/lithe-fixtures/compiler")}} }); let before = document.clone(); @@ -192,7 +192,16 @@ fn toolchain_pins_reject_invalid_tools_providers_and_unknown_fields() { let mut document = local_pins(); document["pins"]["."]["java"]["extra"] = json!(true); assert!(pins::validate(&document).is_err()); + // Legacy entries are opaque string records: any key is kept, but a value must + // be a string and an entry holds at most 16 properties. let document = json!({"version":1,"toolchains":{"old":{"unknown":"path"}}}); + assert!(pins::validate(&document).is_ok()); + let document = json!({"version":1,"toolchains":{"old":{"unknown":true}}}); + assert!(pins::validate(&document).is_err()); + let crowded: serde_json::Map = (0..17) + .map(|index| (format!("key{index}"), json!("path"))) + .collect(); + let document = json!({"version":1,"toolchains":{"old":crowded}}); assert!(pins::validate(&document).is_err()); assert!(pins::resolve( &json!({"document":local_pins(),"directory":".","tool":"java","unknown":true}) diff --git a/rust/lithe-core/src/toolchain/pins.rs b/rust/lithe-core/src/toolchain/pins.rs index e7c7d8b9..46383d61 100644 --- a/rust/lithe-core/src/toolchain/pins.rs +++ b/rust/lithe-core/src/toolchain/pins.rs @@ -7,6 +7,7 @@ const MAX_DIRECTORIES: usize = 1024; const MAX_TOOLS_PER_DIRECTORY: usize = 64; const MAX_DIRECTORY_BYTES: usize = 2048; const MAX_PATH_BYTES: usize = 4096; +const MAX_LEGACY_PROPERTIES: usize = 16; /// Resolves a stored pin without granting execution authority or modifying the document. /// Callers must independently require an active definition and validate native files. @@ -143,10 +144,11 @@ pub(crate) fn validate(document: &Value) -> Validation<()> { for (id, value) in legacy { text(&Value::String(id.clone()), "document.toolchains", 192)?; let field = format!("document.toolchains.{id}"); - // The legacy key set mirrors toolchain-local-v1.schema.json so documents written - // by earlier releases keep validating. Legacy paths retain their original - // meaning; validation must not migrate or interpret them. - let entry = object(value, &field, &["home", "executable", "javaHome"])?; + // Legacy entries are opaque string records so documents written by earlier + // releases keep validating whichever keys a tool used. Core neither names + // nor interprets those keys; validation only bounds their count and size. + let entry = object(value, &field, &[])?; + count(entry.len(), MAX_LEGACY_PROPERTIES, &field)?; for (property, value) in entry { text(value, &format!("{field}.{property}"), MAX_PATH_BYTES)?; } diff --git a/rust/lithe-ide-host/src/catalog.rs b/rust/lithe-ide-host/src/catalog.rs index 4eef9285..8b0df5d1 100644 --- a/rust/lithe-ide-host/src/catalog.rs +++ b/rust/lithe-ide-host/src/catalog.rs @@ -98,12 +98,6 @@ fn schema(properties: Value, required: &[&str]) -> Value { pub fn tools() -> Vec { let definitions = vec![ ("lithe_project_inspect", "Inspect the connected IDE project, readiness and available permissions.", "read", schema(json!({}), &[])), - ("lithe_environment_inspect", "Discover Java and Maven installations and inspect saved and effective project environment settings. Does not install tools.", "read", schema(json!({}), &[])), - ("lithe_environment_configure", "Update selected project-local Java/Maven defaults using the IDE settings workflow. Empty paths restore automatic selection. Other settings are preserved. Requires configure permission.", "configure", schema(json!({"javaHomePath":string(),"mavenExecutablePath":string(),"mavenJavaHomePath":string(),"settingsPath":string(),"localRepositoryPath":string()}), &[])), - ("lithe_maven_inspect", "Read Maven modules, available/selected profiles, skip-tests setting and reload state.", "read", schema(json!({}), &[])), - ("lithe_maven_configure", "Save Maven profiles and skip-tests settings through the existing IDE workflow. Requires configure permission.", "configure", schema(json!({"profiles":{"type":"array","items":string(),"maxItems":64},"skipTests":{"type":"boolean"}}), &[])), - ("lithe_maven_reload", "Reload the Maven project and synchronize the existing Java language service. May resolve dependencies and execute project tooling. Requires execute permission.", "execute", schema(json!({}), &[])), - ("lithe_maven_execute", "Windows Maven execution (macOS uses lithe_build_execute). Execute Maven goals through the IDE, using saved profiles and toolchains. Returns operationID for output/status/stop. May execute project code and access the network. Requires execute permission.", "execute", schema(json!({"goals":{"type":"array","items":string(),"minItems":1,"maxItems":32},"modulePath":string()}), &["goals"])), ("lithe_build_inspect", "List detected build plugin instances, current project/target/model task identities and declared tasks. No tooling is executed.", "read", schema(json!({}), &[])), ("lithe_build_reload", "Ask a detected build plugin to load or reload its project model. May execute project tooling and access the network. Inspect the model state before selecting projects. Requires execute permission.", "execute", schema(json!({"instanceID":string()}), &["instanceID"])), ("lithe_build_configure", "Set one declared build option using the same instance editor and persistent store as the IDE. Read option IDs, kinds and effective values from build inspect. Null resets that option. Invalid values are refused. Native files use the explicit native configuration save workflow. Requires configure permission.", "configure", schema(json!({"instanceID":string(),"optionID":string(),"value":option_value()}), &["instanceID","optionID","value"])), @@ -187,39 +181,6 @@ fn validate_catalog( return Err(invalid()); } } - if let Some(profiles) = args.get("profiles").and_then(Value::as_array) { - if profiles.iter().any(|p| { - p.as_str() - .is_none_or(|s| s.trim().is_empty() || s.trim() != s || s.contains(',')) - }) { - return Err(invalid()); - } - } - if let Some(module) = args.get("modulePath").and_then(Value::as_str) { - if module.starts_with('/') - || module.contains(['\\', ':']) - || module.split('/').any(|s| s == "..") - { - return Err(invalid()); - } - } - // A goal is one Maven token. Shell strings, flags and implicit filesystem overrides - // belong to the IDE's settings/configuration workflow, not this execution surface. - if let Some(goals) = args.get("goals").and_then(Value::as_array) { - if goals.iter().any(|g| { - g.as_str().is_none_or(|s| { - s.starts_with('-') - || !s - .chars() - .all(|c| c.is_ascii_alphanumeric() || ":._-".contains(c)) - }) - }) { - return Err(failure( - "INVALID_ARGUMENTS", - "Use Maven goal names; configure profiles and paths through the settings tools", - )); - } - } Ok(()) } @@ -345,8 +306,8 @@ mod tests { ); assert_eq!( validate_exposed( - "lithe_maven_configure", - &json!({"profiles":["ci"]}), + "lithe_unknown_capability", + &json!({}), &permissions, &exposure ) @@ -355,7 +316,7 @@ mod tests { ); assert!(validate_exposed( "lithe_build_execute", - &json!({"instanceID":"maven:.","taskID":"compile"}), + &json!({"instanceID":"example:.","taskID":"compile"}), &Permissions::default(), &exposure ) @@ -404,37 +365,10 @@ mod tests { } } #[test] - fn authorization_and_argument_validation_cannot_be_bypassed() { - assert!(validate( - "lithe_maven_execute", - &json!({"goals":["verify"]}), - &Permissions::default() - ) - .is_err()); - let permissions = Permissions { - execute: true, - configure: true, - }; - for args in [ - json!({"goals":["verify"],"modulePath":"../other"}), - json!({"goals":["verify;curl"]}), - json!({"goals":["-f"]}), - json!({"goals":["verify"],"confirmed":true}), - ] { - assert!(validate("lithe_maven_execute", &args, &permissions).is_err()); - } - assert!(validate( - "lithe_maven_execute", - &json!({"goals":["clean","verify"],"modulePath":"module"}), - &permissions - ) - .is_ok()); - } - #[test] fn build_authorization_and_argument_validation_cannot_be_bypassed() { assert!(validate( "lithe_build_execute", - &json!({"instanceID":"maven@.","taskID":"verify"}), + &json!({"instanceID":"fixture@.","taskID":"verify"}), &Permissions::default() ) .is_err()); @@ -443,14 +377,14 @@ mod tests { configure: true, }; for args in [ - json!({"instanceID":"maven@."}), - json!({"instanceID":"maven@.","taskID":"verify","command":"mvn"}), - json!({"instanceID":"maven@.","taskID":"verify","userArguments":["bad\u{0000}argument"]}), - json!({"instanceID":"maven@.","taskID":"verify","confirmed":true}), + json!({"instanceID":"fixture@."}), + json!({"instanceID":"fixture@.","taskID":"verify","command":"fixture-tool"}), + json!({"instanceID":"fixture@.","taskID":"verify","userArguments":["bad\u{0000}argument"]}), + json!({"instanceID":"fixture@.","taskID":"verify","confirmed":true}), ] { assert!(validate("lithe_build_execute", &args, &permissions).is_err()); } - assert!(validate("lithe_build_execute", &json!({"instanceID":"maven@.","taskID":"custom","projectID":"api","userArguments":["clean","verify"]}), &permissions).is_ok()); + assert!(validate("lithe_build_execute", &json!({"instanceID":"fixture@.","taskID":"custom","projectID":"api","userArguments":["clean","verify"]}), &permissions).is_ok()); } #[test] fn build_options_require_configure_and_accept_only_bounded_declared_value_shapes() { diff --git a/rust/lithe-ide-host/src/lib.rs b/rust/lithe-ide-host/src/lib.rs index 8582ae13..f810cb8a 100644 --- a/rust/lithe-ide-host/src/lib.rs +++ b/rust/lithe-ide-host/src/lib.rs @@ -591,7 +591,7 @@ mod tests { let denied: Value = client .post(&connection.endpoint) .bearer_auth(&connection.token) - .json(&json!({"name":"lithe_build_execute","arguments":{"instanceID":"maven@.","taskID":"verify"}})) + .json(&json!({"name":"lithe_build_execute","arguments":{"instanceID":"fixture@.","taskID":"verify"}})) .send() .await .unwrap() @@ -691,13 +691,14 @@ mod tests { "../../fixtures/ide-api/host-tool-exposure.json" )) .unwrap(); - let (_fixture, broker, headers) = push_fixture_exposed(exposure["macos"].clone()); + let mut advertised = exposure["macos"].clone(); + advertised + .as_array_mut() + .unwrap() + .retain(|tool| tool["name"] != "lithe_run_list"); + let (_fixture, broker, headers) = push_fixture_exposed(advertised); for (name, arguments, code) in [ - ( - "lithe_maven_execute", - json!({"goals":["verify"]}), - "NOT_SUPPORTED", - ), + ("lithe_run_list", json!({}), "NOT_SUPPORTED"), ( "lithe_build_configure", json!({"instanceID":"example:.","optionID":"offline","value":true,"settingsPath":"/fixture/settings.xml"}), @@ -757,13 +758,7 @@ mod tests { configure: true, execute: true, }; - assert!(catalog::validate_served( - "lithe_maven_execute", - &json!({"goals":["verify"]}), - &permissions, - None - ) - .is_ok()); + assert!(catalog::validate_served("lithe_run_list", &json!({}), &permissions, None).is_ok()); } async fn notification(rx: &mut tokio::sync::mpsc::UnboundedReceiver) -> Option { diff --git a/shared/plugins/contract/v1/README.md b/shared/plugins/contract/v1/README.md index 177f83f4..a1af66c2 100644 --- a/shared/plugins/contract/v1/README.md +++ b/shared/plugins/contract/v1/README.md @@ -77,7 +77,7 @@ and resolution. A separate fresh resolution serves Build tools and run plans whose `toolchain` executable names a plugin-defined tool rather than a host toolchain; settings detections never grant launch authority. Saving pins, `toolchainConsumers`, generic runtime launches and live backend activation remain -unsupported, and Java and language-server consumers keep their previous adapters. Version-manager +unsupported, and language-server consumers keep their previous adapters. Version-manager implementations such as mise belong to their own plugin packages. See [the implemented slice](toolchains/README.md). @@ -294,7 +294,7 @@ activation event, and a plugin may consist of grammars alone. | `validationArguments` | Probe that must exit with status 0 before a candidate is used; empty means the host trusts the candidate and lets the LSP handshake decide. | | `runtimeDependencies` | Tools the host must discover and pass to the server, each a tool-definition ID that `requiresTools` lists (see [Tool references](#tool-references)). The host exports each path as `LITHE__PATH` (`LITHE_NODE_PATH`, `LITHE_GO_PATH`; a user-set value takes precedence) and prepends its directory to the server's `PATH`, because servers such as gopls run the tool themselves and an app opened from the Dock does not inherit the shell `PATH`. A project-local copy is never chosen, since servers start automatically when a file opens. | | `initializationOptions` | A JSON object sent as the LSP `initializationOptions`. Every string value inside it is a launch template. | -| `hostIntegration` | Any plugin may declare one. A named integration implemented in the host, with the package resources it reads; see [Language server host integrations](#language-server-host-integrations). | +| `hostIntegration` | Reserved. Core implements no host integration, so any ID is refused; see [Language server host integrations](#language-server-host-integrations). | | `requestMethods`, `commands`, `serializedCommands`, `forwardNotifications`, `state`, `remote` | What the plugin backend may ask of the running server and how the host keeps its state; see [Plugin-owned sessions](#plugin-owned-sessions). | The host owns the language-server process lifecycle, the LSP session, and @@ -306,59 +306,19 @@ the same meaning and validation; see [Build providers](#build-providers). #### Language server host integrations -Most of a language server is described by its launch. A few servers also need -behavior that data cannot express: a project model computed from build files, -virtual documents, test and debug wiring. That behavior is code in the host, -called a host integration. A server opts into one by name and tells it where -the files it needs are inside the plugin's own package: - -```json -"hostIntegration": { - "id": "jdt", - "capabilities": ["debug", "run", "testing"], - "resources": { "configuration": "language-server/configuration/${arch}", "lombokAgent": "…" } -} -``` - -The plugin owns the files; the host owns the behavior. The host enables an -integration only for the server that declares it and never matches on a -language ID or looks for the files anywhere else, so removing the plugin -removes the integration's inputs with it. Any plugin may declare one, whatever -its namespace. The integration is host code, but it reads only resources inside -the declaring plugin's own package and hands them to the language server or -helper process that same plugin starts (extension bundles, agents, a test -runner, debugging); it grants the plugin no permission beyond running its own -server process, which an installed plugin may already do once the user confirms -the install review. An ID the host does not implement is refused with -`plugin.integration.unsupported`. A build system's `hostIntegration` is a -different declaration and stays limited to official plugins; see +`hostIntegration` is reserved. Core implements no host integration and never +matches on a language ID, a server ID or a tool name: everything a language +needs beyond its launch comes from the plugin's own backend, providers and +declarations. Any `hostIntegration` ID is refused with +`plugin.integration.unsupported`, at `languageServers[].hostIntegration.id`, +so a manifest naming one fails validation instead of loading without it. A build +system's `hostIntegration` is a separate declaration with the same rule; see [Host integrations](#host-integrations) under [Build systems](#build-systems). -Each resource is a package-relative path, or a list of them, that may vary -only by `${arch}`. An integration defines which names it reads; a missing -required name, an unknown name, or the wrong shape is `plugin.manifest.invalid`. - A plugin that declares a language owns its process-backed features: the host stops running its built-in tools for that language, so a missing or disabled -plugin never falls back to them. `capabilities` is how the plugin hands a -feature back. It lists the features of the server's language that the host's -own implementation provides through the integration: `run`, `testing`, and -`debug`. A feature that is neither listed here nor supplied by one of the -plugin's `executionProviders` is unavailable for the language. Omitting the -list claims nothing. - -Each integration defines the capabilities it provides and the resources each -one reads. A capability the integration does not provide, a capability whose -resource is not declared, and `run` or `testing` listed for a language whose -execution provider already supplies it are all `plugin.manifest.invalid`. The -normalized manifest lists capabilities sorted and without duplicates. - -| Integration | Requires | Capabilities | Resources | -| --- | --- | --- | --- | -| `jdt` | A `runtime` launch with the `java` tool | `run`. `debug`: needs `extensionBundleDirectories`, which must contain the Java Debug Server bundle. `testing`: needs `extensionBundleDirectories` with the Java Test bundles, and `testRunner`. | `configuration` (required): the Equinox configuration directory, of which the host gives each launch a writable copy. `lombokAgent` (required): the Lombok agent JAR. `extensionBundleDirectories` (list): directories whose JAR files are loaded as JDT LS extension bundles, in declared order and then by file name. `testRunner`: the standalone Java test runner JAR. | - -The launch plan returns `hostIntegration` as `{id, resources}` with every -path expanded, and lists the paths in `requiredPackagePaths`. +plugin never falls back to them. A feature that none of the plugin's +`executionProviders` supplies is unavailable for the language. #### Workspace configuration @@ -1111,9 +1071,9 @@ back to Provider discovery. A host-managed `node` or `go` dependency may select one such candidate, while retaining Core's version probe. Any other dependency that declares no `executables` is already this candidate for the definition with its own ID, and -that definition checks `minimumVersion`. Maven declares its namespace-owned tool -and uses the short `java` definition for its Java prerequisite; no Maven-specific -JDK registry entry is added. Its backend bootstrap remains a separate consumer. +that definition checks `minimumVersion`. A tool that needs another tool as a +prerequisite names that tool's definition; Core adds no registry entry for any +particular tool. Passive import performs no version or runtime probes; a definition that requires verification is not reported as an already verified import executable. A toolchain that resolves to a project file remains project code, @@ -1411,8 +1371,8 @@ host change to add a build tool, and no host code branches on a build-system ID. One complete fixture covers each plugin-described model source: `build-static-file` reads a project file, `build-command-stdout` decodes a command's standard output, and `build-command-output-file` decodes a file the -command writes; the `hostIntegration` source, whose model a host integration -supplies, has no fixture. They use Cargo, Go, and Gradle as realistic inputs; +command writes; the reserved `hostIntegration` source has no fixture, because +Core implements no integration. They use Cargo, Go, and Gradle as realistic inputs; they are not those plugins. `build-provider` and `build-provider-language-server` show the two forms of a build provider, and `fixtures/build-systems/build-plans.json` is the golden list of plans and decoded outputs every host gets from them. @@ -1637,7 +1597,7 @@ Workspace-wide reports still require a current project model when the build system has one. Configuration validity is retained through diagnostic and report decoding; late results cannot update a changed workspace/configuration. -Java/Maven migration remains separate. No new resource/cache location is introduced. +No new resource/cache location is introduced. Canonical examples: `fixtures/build-providers/build-provider-tests.plugin.json` and `fixtures/build-providers/build-test-protocol.json`. @@ -1830,7 +1790,7 @@ Saved Run configurations keep their existing selectors and prepare again for Deb Fixtures: [native declaration](fixtures/build-providers/build-provider-debug-preparation.plugin.json) and [native result](fixtures/build-providers/build-debug-preparation.json). This does not provide -Java/JDT preparation, long-running services or cross-plugin collaboration. +preparation through a language server, long-running services or cross-plugin collaboration. The native Build tree re-resolves model IDs before preparation. Source/model, @@ -1991,8 +1951,8 @@ applications retain their existing native ownership. This facility never installs or activates a producer, invokes its private functions, grants permissions, leases a process or resolves a package version. There is no general plugin-dependency/export API. Backend authors must handle -unavailable and non-ready slots explicitly. Language integration and existing -Java/Maven business migration remain separate work. Shared fixtures: +unavailable and non-ready slots explicitly. Language integration remains +separate work. Shared fixtures: `fixtures/build-systems/build-model-producer.plugin.json` and `fixtures/build-systems/build-model-consumer.plugin.json`. @@ -2075,7 +2035,7 @@ Shared fixture: The build backend owns filtering, custom resource directories, profiles and reactor semantics. Hosts await successful completion, retain one operation identity through cancellation/configuration changes, and only then resolve and -launch Java. Per-run option/tool overrides are ephemeral host inputs to the +launch the program. Per-run option/tool overrides are ephemeral host inputs to the existing validated plan; they never mutate saved build defaults or claim that the persistent model reflects a different configuration. @@ -2170,12 +2130,12 @@ A host that declares `buildProviders` keeps providers apart: ### Host integrations -`hostIntegration` names host code that owns one concern while a built-in tool -migrates onto this model. Only official plugins may name one, and only -`maven` exists; a model, test-result, or dependency source of -`hostIntegration` needs it. Anything else is -`plugin.integration.unsupported`. New build systems use the declarative -contract or a canonical format instead. +`hostIntegration` is reserved. Core implements no build-system host +integration: every build tool is described by its plugin's data and backend, +and no host code branches on a build-system ID. Any `hostIntegration` name is +`plugin.integration.unsupported`, and a model, test-result, or dependency source +of `hostIntegration` is refused with it. Build systems use the declarative +contract, a canonical format, or a `provider` instead. ### Commands @@ -2553,8 +2513,8 @@ field, and `optional` only on a `text` or `tool` field. `maximumVersion` is a template over earlier fields naming the highest version the value may be, compared numerically by dotted components. It reads -`${field..major}` of a `tool` field: a target Java release that must not -exceed the selected JDK declares `"maximumVersion": "${field.jdk.major}"`. +`${field..major}` of a `tool` field: a target language level that must not +exceed the selected compiler declares `"maximumVersion": "${field.compiler.major}"`. While the bound expands to an empty string (no tool chosen yet) nothing is checked. @@ -2614,8 +2574,10 @@ directory). A condition on a hidden field is false. Strings in the manifest use `${…}`: `${project.name}`, `${field.}` (a text or option value, `true`/`false`, a comma-separated list, or the home directory of a tool), `${field..version}` and `${field..major}` (tool -fields only: the version of the chosen installation, and its major number, -`21` for `21.0.4` and `8` for `1.8.0_412`), and `${field..executable}` +fields only: the version of the chosen installation as its definition's +`version.pattern` captured it, and that version's first numeric component, +`21` for `21.0.4`; a definition whose tool uses another numbering, such as a +legacy `1.` prefix, normalizes it in the pattern), and `${field..executable}` (tool fields only: the path of the tool's executable in the chosen installation). The three suffixed variables are rejected when the manifest is validated if the field is not a `tool` field. Modifiers follow `|` and apply @@ -3322,9 +3284,14 @@ context it started in has closed. inside it) and the environment have the bounds of [run preparation](#provider-run-preparation). An unknown `process` field is refused. -- `temporaryFiles` is at most four `{name, contents}` files of up to 4 MiB of - text each. The host writes them into its own temporary directory before the - process starts, removes them when the run ends, and replaces +- `temporaryFiles` is at most four files of up to 4 MiB each. A file is + `{name, contents}`, text the host writes as UTF-8, or `{name, base64}`, + bytes in standard base64 the host writes unchanged; a file with both or + neither is refused at `temporaryFiles[]`. Bytes are for a format whose + encoding the plugin must choose, such as a launcher that reads its argument + file in the system code page: the plugin encodes the file and the host never + transcodes it. The host writes the files into its own temporary directory + before the process starts, removes them when the run ends, and replaces `${temporaryFile:}` in `process.arguments` and `process.environment` with the file's path. A name is unique, 1 to 64 letters, digits, `.`, `_` or `-`, and starts with a letter or digit. A plugin therefore never needs to @@ -4020,7 +3987,7 @@ command payload is a Core `invalid_request` failure instead. | `plugin.test.invalidIdentifier` | Core | Empty or multi-line test identifier | | `plugin.test.unsupportedProject` | Core | No project marker in the workspace | | `plugin.execution.unavailable` | Core | The plugin has no run/test provider for the language | -| `plugin.integration.unsupported` | Core | A host integration the host does not implement, or a build system's host integration requested by a plugin outside the official namespace | +| `plugin.integration.unsupported` | Core | A `hostIntegration` ID; Core implements none | | `plugin.launch.unavailable` | Core | The plugin has no language server for the language, no provider process for the build system, or no such VCS provider | | `plugin.build.unavailable` | Core | The plugin declares no such build system, or the build system does not declare the requested operation | | `plugin.build.invalidSelection` | Core | A build request selects a scope, target, filter, or argument the declaration does not allow, or misses a required path | diff --git a/shared/plugins/contract/v1/plugin-manifest.schema.json b/shared/plugins/contract/v1/plugin-manifest.schema.json index 50309a81..faba06b6 100644 --- a/shared/plugins/contract/v1/plugin-manifest.schema.json +++ b/shared/plugins/contract/v1/plugin-manifest.schema.json @@ -1454,14 +1454,14 @@ "enum": ["supported", "unsupported"] }, "hostIntegration": { - "description": "A named host integration and the package resources it reads.", + "description": "Reserved. Core implements no host integration, so any ID is refused with plugin.integration.unsupported.", "type": "object", "additionalProperties": false, "required": ["id"], "properties": { - "id": { "enum": ["jdt"] }, + "id": { "type": "string", "minLength": 1 }, "capabilities": { - "description": "Process-backed features of the language that the host's own implementation provides through the integration.", + "description": "Process-backed features of the language a host integration would provide.", "type": "array", "uniqueItems": true, "items": { "enum": ["debug", "run", "testing"] } @@ -2673,7 +2673,7 @@ "$ref": "#/$defs/buildVersionProbe" } }, - "description": "java, node, and go use host version probes. They may select one toolchain candidate with executables, or take only id and minimumVersion. Other runtimes declare their executables and version probe." + "description": "node and go use host version probes. They may select one toolchain candidate with executables, or take only id and minimumVersion. Other runtimes declare their executables and version probe." } }, "version": { @@ -2685,10 +2685,9 @@ } }, "hostIntegration": { - "description": "Official plugins only; the host must provide the named integration.", - "enum": [ - "maven" - ] + "description": "Reserved. Core implements no build-system host integration, so any name is refused with plugin.integration.unsupported.", + "type": "string", + "minLength": 1 }, "provider": { "$ref": "#/$defs/buildProvider" diff --git a/shared/plugins/contract/v1/toolchains/README.md b/shared/plugins/contract/v1/toolchains/README.md index f8adac31..0540dd7a 100644 --- a/shared/plugins/contract/v1/toolchains/README.md +++ b/shared/plugins/contract/v1/toolchains/README.md @@ -10,8 +10,8 @@ runs the definition's version probe on executables Core returns from `candidates`, and shows the result. Any change to a watched input reloads the whole snapshot. Nothing is saved by these pages. A separate fresh-resolution adapter serves macOS Build and Provider Run; settings detections are never used -as launch permits. Java and language-server consumers resolve their tools with -their own adapters. Windows has no +as launch permits. Language-server consumers resolve their tools with their +own adapters. Windows has no toolchain adapter and does not advertise `toolDefinitions` or `toolchainProviders`, so Core refuses packages that require them there. Consumer migration, the native query lifecycle and cache, pin saving and diff --git a/shared/plugins/guides/build-tool-integration.md b/shared/plugins/guides/build-tool-integration.md index dce41d5c..40dd078a 100644 --- a/shared/plugins/guides/build-tool-integration.md +++ b/shared/plugins/guides/build-tool-integration.md @@ -27,7 +27,6 @@ LSP 服务器负责语言分析,构建工具负责项目导入、编译、依 | 补全、跳转、诊断 | `languageServers` | 按 LSP 提供能力,不自动声明编译和测试 | | 运行文件或固定项目命令 | `executionProviders[].run` | 命令与 argv 模板,工作目录为工作区根;不提供通用目标选择或构建模型。桌面端不声明 `executionProviders` | | 运行整个工程、文件所在范围或指定测试 | `executionProviders[].test` | 测试项目标记、文件发现和命令规划;不是完整语义测试发现与结构化结果协议。桌面端不声明 `executionProviders` | -| Java 项目的运行/测试/调试 | 任何插件都可以声明 `languageServers[].hostIntegration`,例如 `jdt` | 使用宿主实现的集成;集成只读插件自己包里的资源,交给同一插件启动的语言服务器,不额外授予权限;宿主未实现的集成 ID 返回 `plugin.integration.unsupported`,见[契约](../contract/v1/README.md#language-server-host-integrations)。桌面端的声明说明没有列出语言服务器宿主集成,使用前先在桌面端验证 | | 通用 Build 工具窗口、工程模型、任务、依赖与同步 | `buildSystems`;模型需要程序计算时再加 `buildProviders` | Core 校验并规划,桌面端声明并呈现这两个能力,呈现范围见开头的宿主支持 | 当需求只是 `lua <当前文件>`、`go test ./...` 这类固定命令,契约提供执行提供者(`executionProviders`)。 @@ -187,17 +186,12 @@ Core 的计划接口本身不启动进程、不监视文件、不持久化选择 只说明声明和规划正确,仍要在真实宿主里验收。 不要把内部 dispatcher 命令写成社区插件可以调用的宿主脚本 API。 -`languageServers[].hostIntegration`(语言服务器侧)与构建系统 `model.source: "hostIntegration"` -是不同接口,开放范围也不同: - -- 语言服务器侧对所有插件开放。任何插件都可以声明宿主已实现的集成 ID(契约里的例子是 `jdt`); - 集成只读取声明插件自己包里的资源,交给同一插件启动的服务器或辅助进程,不额外授予权限。 - 宿主未实现的 ID 返回 `plugin.integration.unsupported`,见 - [契约](../contract/v1/README.md#language-server-host-integrations)。 -- 构建系统侧只限官方插件,目前只有 `maven`;官方命名空间以外的插件请求它同样得到 - `plugin.integration.unsupported`,见[契约](../contract/v1/README.md#host-integrations)。 - 不要复制官方 ID 或修改 `vendor` 来绕过这个限制。 - 社区构建插件用 `static`、`command` 或 `provider` 模型。 +`languageServers[].hostIntegration` 与构建系统 `model.source: "hostIntegration"` 都是保留字段。 +Core 不实现任何宿主集成,也不按语言、服务器或构建工具的 ID 走特殊逻辑,所以任何 ID 都返回 +`plugin.integration.unsupported`,见[语言服务器侧](../contract/v1/README.md#language-server-host-integrations) +与[构建系统侧](../contract/v1/README.md#host-integrations)的契约。语言的运行、测试与调试由插件自己的 +`executionProviders`、`buildSystems`、`buildProviders` 和后端提供;构建插件用 `static`、`command` 或 +`provider` 模型。 两侧都不是作者自己命名就能新增的机制:集成 ID 必须是宿主已经实现的。 @@ -254,7 +248,6 @@ Before publishing, still confirm that the target host declares every capability | Completion, navigation, diagnostics | `languageServers` | Features come from LSP; compile and test are not declared automatically | | Run a file or a fixed project command | `executionProviders[].run` | A command and argv template; the working directory is the workspace root; no general target selection or build model. The desktop does not declare `executionProviders` | | Run the whole project, a file's scope, or a named test | `executionProviders[].test` | Test-project markers, file discovery, and command planning; not a full semantic test-discovery or structured-results protocol. The desktop does not declare `executionProviders` | -| Run, test, and debug Java projects | Any plugin may declare `languageServers[].hostIntegration`, for example `jdt` | Uses an integration the host implements; the integration reads only resources inside the plugin's own package and hands them to the language server that same plugin starts, and grants no extra permission; an integration ID the host does not implement returns `plugin.integration.unsupported`; see the [contract](../contract/v1/README.md#language-server-host-integrations). The desktop's declarations do not list any language-server host integration, so verify on the desktop before relying on one | | A general Build tool window, project model, tasks, dependencies, and sync | `buildSystems`, plus `buildProviders` when a program must compute the model | Core validates and plans; the desktop declares and presents both capabilities, to the extent described in the host support note at the top | When all you need is a fixed command such as `lua ` or `go test ./...`, the contract offers @@ -425,19 +418,13 @@ A fixture that produces the right command only shows that the declaration and pl you still have to accept it in a real host. Do not present internal dispatcher commands as a host scripting API that community plugins can call. -`languageServers[].hostIntegration` (the language-server side) and the build-system -`model.source: "hostIntegration"` are different interfaces, and they are open to different plugins: - -- The language-server side is open to every plugin. Any plugin may declare an integration ID the host - already implements (the contract's example is `jdt`); the integration reads only - resources inside the declaring plugin's own package and hands them to the server or helper process - that same plugin starts, and grants no extra permission. An ID the host does not implement returns - `plugin.integration.unsupported`; see the [contract](../contract/v1/README.md#language-server-host-integrations). -- The build-system side is limited to official plugins, and currently has only `maven`; a plugin - outside the official namespace that asks for it also gets `plugin.integration.unsupported`; - see the [contract](../contract/v1/README.md#host-integrations). - Do not copy an official ID or change `vendor` to get around this limit. - Community build plugins use the `static`, `command`, or `provider` models. +`languageServers[].hostIntegration` and the build-system `model.source: "hostIntegration"` are both +reserved. Core implements no host integration and runs no special logic for a language, server, or +build-tool ID, so every ID returns `plugin.integration.unsupported`; see the contract for the +[language-server side](../contract/v1/README.md#language-server-host-integrations) and the +[build-system side](../contract/v1/README.md#host-integrations). A language's run, test, and debug +come from the plugin's own `executionProviders`, `buildSystems`, `buildProviders`, and backend; build +plugins use the `static`, `command`, or `provider` models. Neither side lets an author add an integration by picking a name: the ID must be one the host already implements. diff --git a/shared/plugins/reference/api-catalog.md b/shared/plugins/reference/api-catalog.md index 940bb246..efd35a78 100644 --- a/shared/plugins/reference/api-catalog.md +++ b/shared/plugins/reference/api-catalog.md @@ -165,7 +165,7 @@ Lithe 的插件公共 API 按 [API 实施计划](https://github.com/1lck/Lithe-I | 编辑器 Provider 单次调用时限 | hover、completion 400 ms,definition 1500 ms,其余类型 2000 ms;迟到的答复直接丢弃,注册不能自定义时限 | | `languageServer/request` | `timeoutMilliseconds` 1–300000 ms,默认 60000;每个会话最多 128 个等待 gate 打开的请求,第 129 个返回 `plugin.host.busy` | | `languageServer/setStatus` | `message` 按字符边界截到 1024 字节 | -| `run/start` | `label` 1–256 字符;`temporaryFiles` 最多 4 个,每个文本 ≤4 MiB;`rerun.data` ≤64 KiB | +| `run/start` | `label` 1–256 字符;`temporaryFiles` 最多 4 个,每个文本或 base64 字节 ≤4 MiB;`rerun.data` ≤64 KiB | | `debug/start` | `configuration` ≤256 KiB | | 内容提供者 | 提供的内容 ≤1 MiB,宿主等待 30 秒 | | `functions/invoke` | `timeoutMilliseconds` 默认 60000 ms,上限 600000 ms;`parentOperationID` 1–128 字节 | @@ -337,7 +337,7 @@ Notifications the host sends to a backend (the backend answers none), also prefi | Editor provider call budget | 400 ms for hover and completion, 1500 ms for definition, 2000 ms for the other types; a late answer is dropped, and a registration cannot set its own budget | | `languageServer/request` | `timeoutMilliseconds` 1–300000 ms, default 60000; at most 128 requests per session waiting for a gate to open, and the 129th returns `plugin.host.busy` | | `languageServer/setStatus` | `message` is cut to 1024 bytes on a character boundary | -| `run/start` | `label` 1–256 characters; `temporaryFiles` at most 4, each up to 4 MiB of text; `rerun.data` up to 64 KiB | +| `run/start` | `label` 1–256 characters; `temporaryFiles` at most 4, each up to 4 MiB of text or base64 bytes; `rerun.data` up to 64 KiB | | `debug/start` | `configuration` up to 256 KiB | | Content providers | Provided content up to 1 MiB; the host waits 30 seconds | | `functions/invoke` | `timeoutMilliseconds` defaults to 60000 ms, capped at 600000 ms; `parentOperationID` is 1–128 bytes | diff --git a/shared/plugins/reference/domains/backend.md b/shared/plugins/reference/domains/backend.md index 501c8f39..94bf3bfc 100644 --- a/shared/plugins/reference/domains/backend.md +++ b/shared/plugins/reference/domains/backend.md @@ -148,7 +148,7 @@ Core 把中转的调用以 `lithe/function/invoke` 转给被调方。和任何 - `label`:1–256 个字符,不含控制字符,作为 Run 窗口条目的标题。 - `process`:`{executable, arguments, workingDirectory, environment}`,四个字段都必填,未知字段被拒绝。`executable` 是 `{kind: "tool", tool}`(`tool` 是清单 `requiresTools` 列出的工具定义 ID,宿主按[工具链](toolchains.md)的分层解析)或 `{kind: "packaged", path}`(插件包内文件的规范化相对路径)。参数、工作目录(相对工作区根、且是其中已存在的目录)和环境的上限同[运行准备](../../contract/v1/README.md#provider-run-preparation)。 -- `temporaryFiles`:最多 4 个 `{name, contents}`,每个文本最多 4 MiB。宿主在自己的临时目录写入,运行结束时删除,并把 `process.arguments` 和 `process.environment` 里的 `${temporaryFile:}` 替换成文件路径,插件不需要知道临时目录在哪。`name` 唯一,1–64 个字母、数字、`.`、`_`、`-`,以字母或数字开头。 +- `temporaryFiles`:最多 4 个文件,每个最多 4 MiB。`{name, contents}` 是文本,宿主按 UTF-8 写入;`{name, base64}` 是标准 base64 字节,宿主原样写入,不转码,用于编码必须由插件决定的格式(例如按系统代码页读取参数文件的启动器)。两者都有或都没有时在 `temporaryFiles[]` 被拒绝。宿主在自己的临时目录写入,运行结束时删除,并把 `process.arguments` 和 `process.environment` 里的 `${temporaryFile:}` 替换成文件路径,插件不需要知道临时目录在哪。`name` 唯一,1–64 个字母、数字、`.`、`_`、`-`,以字母或数字开头。 - `debug`:`{configurationType, target}`,让这次运行成为调试会话,见[调试适配器](debug-adapters.md#由插件后端发起调试会话)。`configurationType` 是公开的调试适配器别名;`target` 是 `{kind: "connection", port}`(进程在 `port`,1–65535,监听调试器,由后端写进参数,宿主等待并附加适配器)或 `{kind: "launch"}`(适配器自己启动进程,按 `targetBindings` 从 `process` 取程序、参数、目录和环境)。 - `rerun`:`{function, data}`,插件已注册的函数和它的参数(最多 64 KiB 的 JSON 对象)。用户选“再次运行”时,宿主以 `data` 为参数、当前 `context` 调用该函数,后端再回答一次新的 `run/start`,所以重启由后端准备;没有 `rerun` 的条目不能再次运行。 @@ -368,7 +368,7 @@ Notification (the backend does not reply): `lithe/host/run/didExit` with `{runID - `label`: 1 to 256 characters without control characters; it titles the Run window entry. - `process`: `{executable, arguments, workingDirectory, environment}`, all four required; an unknown field is refused. `executable` is `{kind: "tool", tool}` (`tool` is a tool-definition ID that the manifest lists in `requiresTools`, which the host resolves through the layers described in [Toolchains](toolchains.md)) or `{kind: "packaged", path}` (a normalized relative path of a file inside the plugin package). Arguments, the working directory (relative to the workspace root and an existing directory inside it), and the environment have the bounds of [run preparation](../../contract/v1/README.md#provider-run-preparation). -- `temporaryFiles`: at most four `{name, contents}` files of up to 4 MiB of text each. The host writes them into its own temporary directory, removes them when the run ends, and replaces `${temporaryFile:}` in `process.arguments` and `process.environment` with the file's path, so the plugin never needs to know where the host keeps temporary files. A `name` is unique, 1 to 64 letters, digits, `.`, `_`, or `-`, and starts with a letter or digit. +- `temporaryFiles`: at most four files of up to 4 MiB each. `{name, contents}` is text the host writes as UTF-8; `{name, base64}` is bytes in standard base64 the host writes unchanged, without transcoding, for a format whose encoding the plugin must choose (such as a launcher that reads its argument file in the system code page). A file with both or neither is refused at `temporaryFiles[]`. The host writes them into its own temporary directory, removes them when the run ends, and replaces `${temporaryFile:}` in `process.arguments` and `process.environment` with the file's path, so the plugin never needs to know where the host keeps temporary files. A `name` is unique, 1 to 64 letters, digits, `.`, `_`, or `-`, and starts with a letter or digit. - `debug`: `{configurationType, target}`, which makes the run a debug session; see [Debug adapters](debug-adapters.md#starting-a-debug-session-from-a-plugin-backend). `configurationType` is a public debug adapter alias; `target` is `{kind: "connection", port}` (the process listens for a debugger on `port`, 1 to 65535, which the backend puts into the arguments; the host waits for it and attaches the adapter) or `{kind: "launch"}` (the adapter starts the process itself, taking program, arguments, directory, and environment from `process` through its `targetBindings`). - `rerun`: `{function, data}`, one of the plugin's registered functions and its params (a JSON object of up to 64 KiB). When the user chooses Run again, the host invokes the function with `data` as the params and the current `context`, and the backend answers with a new `run/start`, so the backend prepares the restart; without `rerun` the entry cannot be rerun. diff --git a/shared/plugins/reference/domains/build-systems.md b/shared/plugins/reference/domains/build-systems.md index e78592dc..5934223c 100644 --- a/shared/plugins/reference/domains/build-systems.md +++ b/shared/plugins/reference/domains/build-systems.md @@ -45,11 +45,11 @@ | `command`(标准输出) | 运行命令,解码标准输出(`jsonStream` 或规范格式) | `build-command-stdout`(Go) | | `command`(输出文件) | 运行命令,解码它写到 `${outputFile}` 的文件 | `build-command-output-file`(Gradle) | | `provider` | 由插件的构建提供者回答 `lithe/build/model` | [构建提供者](build-providers.md) | -| `hostIntegration` | 交给宿主集成:只限官方插件,目前只有 `maven`,其他是 `plugin.integration.unsupported`。新的构建系统用声明式契约或规范格式,不用它 | — | +| `hostIntegration` | 保留字段:任何 ID 都返回 `plugin.integration.unsupported`。构建系统用声明式契约或规范格式 | — | `command` 来源要声明 `executesProjectCode` 和 `mayUseNetwork`。命令只在宿主按请求启动时运行,静态文件在检测时读取。`map` 只负责选取值,不是转换语言:map 表达不了的(其他文件格式、从工具读出的任务、显式的父子关系、计算出的标签、依赖树)由插件自己的命令或构建提供者输出规范格式 `litheBuildModel`、`litheDependencies`、`litheTestResults` 或 `litheDiagnostics`(`version` 都是 `0`)。不符合解码器的输出是 `plugin.build.invalidOutput`。 -构建系统的 `hostIntegration` 和语言服务器的 `hostIntegration` 是两个不同的声明:前者只限官方插件;后者任何插件都可以声明,见[语言服务器的宿主集成](../../contract/v1/README.md#language-server-host-integrations)。 +构建系统的 `hostIntegration` 和语言服务器的 `hostIntegration` 都是保留字段:Core 不实现任何宿主集成,任何 ID 都返回 `plugin.integration.unsupported`,见[语言服务器的宿主集成](../../contract/v1/README.md#language-server-host-integrations)。 ### 选项、可执行文件与准备任务 @@ -70,7 +70,7 @@ | [Optional read-only model inputs](../../contract/v1/README.md#optional-read-only-model-inputs) | 插件之间共享只读模型 | | [Declarative project preparation](../../contract/v1/README.md#declarative-project-preparation) | 运行前准备任务 | | [Executable resolution](../../contract/v1/README.md#executable-resolution) | 可执行文件候选、排序、严格选择 | -| [Host integrations](../../contract/v1/README.md#host-integrations) | 构建系统的宿主集成(只限官方插件) | +| [Host integrations](../../contract/v1/README.md#host-integrations) | 构建系统的宿主集成(保留字段,Core 不实现) | | [Commands](../../contract/v1/README.md#commands) | `build.*` Core 命令:检测、规划、解码、同步指纹 | ### 示例与用例 @@ -142,11 +142,11 @@ IDs of build systems, tasks, and run entries start with a lowercase letter and u | `command` (stdout) | Run a command and decode its standard output (`jsonStream` or a canonical format) | `build-command-stdout` (Go) | | `command` (output file) | Run a command and decode the file it writes to `${outputFile}` | `build-command-output-file` (Gradle) | | `provider` | The plugin's build provider answers `lithe/build/model` | [Build providers](build-providers.md) | -| `hostIntegration` | Handed to a host integration: official plugins only, currently only `maven`, otherwise `plugin.integration.unsupported`. New build systems use the declarative contract or a canonical format instead | — | +| `hostIntegration` | Reserved: every ID returns `plugin.integration.unsupported`. Build systems use the declarative contract or a canonical format | — | A `command` source declares `executesProjectCode` and `mayUseNetwork`. A command runs only when the host starts it on request; a static file is read during detection. A `map` only selects values and is not a transformation language: whatever a map cannot express (other file formats, tasks read from the tool, explicit parent links, computed tags, dependency trees) is the plugin's own command or build provider printing the canonical `litheBuildModel`, `litheDependencies`, `litheTestResults`, or `litheDiagnostics` (all `version` `0`). Output that does not fit its decoder is `plugin.build.invalidOutput`. -A build system's `hostIntegration` and a language server's `hostIntegration` are two different declarations: the first is limited to official plugins; any plugin may declare the second, see [language server host integrations](../../contract/v1/README.md#language-server-host-integrations). +A build system's `hostIntegration` and a language server's `hostIntegration` are both reserved: Core implements no host integration, and every ID returns `plugin.integration.unsupported`; see [language server host integrations](../../contract/v1/README.md#language-server-host-integrations). ### Options, executables, and preparation tasks @@ -167,7 +167,7 @@ A build system's `hostIntegration` and a language server's `hostIntegration` are | [Optional read-only model inputs](../../contract/v1/README.md#optional-read-only-model-inputs) | Sharing read-only models between plugins | | [Declarative project preparation](../../contract/v1/README.md#declarative-project-preparation) | Tasks that run before a launch | | [Executable resolution](../../contract/v1/README.md#executable-resolution) | Executable candidates, ordering, strict selection | -| [Host integrations](../../contract/v1/README.md#host-integrations) | Host integrations for build systems (official plugins only) | +| [Host integrations](../../contract/v1/README.md#host-integrations) | Host integrations for build systems (reserved; Core implements none) | | [Commands](../../contract/v1/README.md#commands) | `build.*` Core commands: detection, plans, decoding, sync fingerprints | ### Examples and cases diff --git a/shared/plugins/reference/domains/languages.md b/shared/plugins/reference/domains/languages.md index 18eab641..26603a47 100644 --- a/shared/plugins/reference/domains/languages.md +++ b/shared/plugins/reference/domains/languages.md @@ -56,28 +56,16 @@ | `validationArguments` | 候选在使用前必须以状态 0 退出的探针;为空时宿主信任候选,由 LSP 握手决定。 | | `runtimeDependencies` | 宿主要发现并传给服务器的工具,每一项是 `requiresTools` 列出的工具定义 ID。宿主把每个路径导出为 `LITHE__PATH`(用户设置的值优先),并把其目录加到服务器的 `PATH` 前面。项目本地副本不会被选中。 | | `initializationOptions` | 作为 LSP `initializationOptions` 发送的 JSON 对象;其中每个字符串值都是启动模板。 | -| `hostIntegration` | 任何插件都可以声明,见下一节。 | +| `hostIntegration` | 保留字段,见下一节。 | | `requestMethods`、`commands`、`serializedCommands`、`forwardNotifications`、`state`、`remote` | 后端可以向运行中的服务器发什么,以及宿主怎样保存它的状态,见“插件自有会话”。 | 宿主拥有语言服务器进程的生命周期、LSP 会话,以及在提供者被停用时停止进程。 #### 宿主集成(`hostIntegration`) -有些服务器需要数据表达不了的行为,例如从构建文件算出的项目模型、虚拟文档、测试和调试的接线。这些行为是宿主代码,称为宿主集成。服务器按名称选择一个集成,并说明它需要的文件在插件自己包里的哪个位置: +`hostIntegration` 是保留字段。Core 不实现任何宿主集成,也不按语言 ID、服务器 ID 或工具名走特殊逻辑;语言在启动之外需要的一切,都由插件自己的后端、提供者和声明给出。声明任何 `hostIntegration` ID 都返回 `plugin.integration.unsupported`,字段为 `languageServers[].hostIntegration.id`。 -```json -"hostIntegration": { - "id": "jdt", - "capabilities": ["debug", "run", "testing"], - "resources": { "configuration": "language-server/configuration/${arch}", "lombokAgent": "…" } -} -``` - -- 插件拥有文件,宿主拥有行为。宿主只对声明它的服务器启用集成,从不按语言 ID 匹配,也不在包外查找文件。 -- 集成是宿主代码,但只读取声明插件自己包里的资源,交给同一个插件启动的语言服务器或辅助进程。它不授予插件运行自己服务器进程之外的权限。 -- `capabilities` 列出语言服务器的哪些功能由集成提供,取值是 `run`、`testing`、`debug`。没有列出、又没有执行提供者提供的功能,对这种语言不可用。 -- 每个集成定义自己提供的能力和读取的资源。能力未提供、资源未声明、或在执行提供者已经提供的语言上又列出 `run` 或 `testing`,都是 `plugin.manifest.invalid`。 -- 契约目前只定义了 `jdt` 这一个集成 ID:它要求 `runtime` 启动(`java` 工具),`configuration`、`lombokAgent` 为必需资源。宿主没有实现的 ID 返回 `plugin.integration.unsupported`。桌面端源码中目前没有找到 `jdt` 的实现,因此声明它会被拒绝(见“桌面端现状”)。 +声明了某种语言的插件拥有这种语言依赖进程的功能:宿主不再为它运行内置工具,插件缺失或被停用时也不会回退到内置实现。插件的 `executionProviders` 没有提供的功能,对这种语言不可用。 #### 启动模板 @@ -219,7 +207,6 @@ | 语言服务器启动 | 已支持 `packaged`、包内运行时、`systemCommand`(只在 PATH 上查找,依赖项目工具链或用户覆盖的启动计划会被拒绝)和后端应答的命令行。后端应答 `{kind: "tool"}` 在启动时被拒绝,因为工具还没有经过工具链注册表解析 | | 语言服务器呈现 | 只呈现诊断。补全、悬停、跳转、重命名、格式化没有编辑器界面,插件即使提供了也不会显示 | | 插件自有会话与 `languageServer/*` 方法 | 已接通,策略由 Core 执行 | -| `jdt` 宿主集成 | 桌面端源码中未找到实现,声明会收到 `plugin.integration.unsupported` | | 执行提供者(`run`、`test`、`executionProviders`) | 未声明。需要它的插件不会被提供安装 | | 输出链接 | 已支持。Build、Run、Test 控制台的文字变成链接,带 `resolve` 的匹配器在点击时调用后端 | | 内容提供者 | 已支持 `documents/registerContentProvider` | @@ -231,7 +218,7 @@ | [Languages](../../contract/v1/README.md#languages) | 语言 ID、扩展名、文件名、项目标记、依赖路径 | | [Grammars and editor configuration](../../contract/v1/README.md#grammars-and-editor-configuration) | TextMate 语法的上限和拒绝规则、注释与括号配置、在后台运行 | | [Language servers](../../contract/v1/README.md#language-servers) | 启动方式(含 `backend`)、参数、探测参数、运行时依赖 | -| [Language server host integrations](../../contract/v1/README.md#language-server-host-integrations) | 宿主集成与 `jdt` | +| [Language server host integrations](../../contract/v1/README.md#language-server-host-integrations) | 宿主集成(保留字段,Core 不实现) | | [Workspace configuration](../../contract/v1/README.md#workspace-configuration) | `workspaceConfiguration` 的展开与推送 | | [Plugin-owned sessions](../../contract/v1/README.md#plugin-owned-sessions) | 会话字段、会话策略、gate、状态目录与回收、准备状态 | | [Runtime launch](../../contract/v1/README.md#runtime-launch) | 用 `java` 或 `node` 等工具启动包内入口文件 | @@ -319,28 +306,16 @@ The rules for `id`, `fileExtensions`, `fileNames`, `projectFileNames`, and `depe | `validationArguments` | A probe that must exit with status 0 before a candidate is used; empty means the host trusts the candidate and lets the LSP handshake decide. | | `runtimeDependencies` | Tools the host discovers and passes to the server, each a tool-definition ID that `requiresTools` lists. The host exports each path as `LITHE__PATH` (a user-set value takes precedence) and prepends its directory to the server's `PATH`. A project-local copy is never chosen. | | `initializationOptions` | A JSON object sent as the LSP `initializationOptions`. Every string value inside it is a launch template. | -| `hostIntegration` | Any plugin may declare one; see the next section. | +| `hostIntegration` | Reserved; see the next section. | | `requestMethods`, `commands`, `serializedCommands`, `forwardNotifications`, `state`, `remote` | What the backend may ask of the running server and how the host keeps its state; see "Plugin-owned sessions". | The host owns the language server process lifecycle, the LSP session, and stopping the process when the provider is disabled. #### Host integrations (`hostIntegration`) -Some servers need behavior data cannot express: a project model computed from build files, virtual documents, test and debug wiring. That behavior is host code, called a host integration. A server selects one by name and says where the files it needs are inside the plugin's own package: - -```json -"hostIntegration": { - "id": "jdt", - "capabilities": ["debug", "run", "testing"], - "resources": { "configuration": "language-server/configuration/${arch}", "lombokAgent": "…" } -} -``` +`hostIntegration` is reserved. Core implements no host integration and runs no special logic for a language ID, a server ID, or a tool name; everything a language needs beyond its launch comes from the plugin's own backend, providers, and declarations. Any `hostIntegration` ID returns `plugin.integration.unsupported`, at `languageServers[].hostIntegration.id`. -- The plugin owns the files; the host owns the behavior. The host enables an integration only for the server that declares it, never matches on a language ID, and never looks for files outside the package. -- The integration is host code, but it reads only resources inside the declaring plugin's own package and hands them to the language server or helper process that same plugin starts. It grants no permission beyond running the plugin's own server process. -- `capabilities` lists which features the integration provides for the language: `run`, `testing`, or `debug`. A feature that is neither listed nor supplied by an execution provider is unavailable for the language. -- Each integration defines the capabilities it provides and the resources each one reads. A capability it does not provide, a capability whose resource is not declared, or `run` or `testing` for a language whose execution provider already supplies it, is `plugin.manifest.invalid`. -- The contract defines one integration ID, `jdt`: it requires a `runtime` launch with the `java` tool, and `configuration` and `lombokAgent` are required resources. An ID the host does not implement returns `plugin.integration.unsupported`. No implementation of `jdt` was found in the desktop source, so declaring it is refused (see "Desktop status"). +A plugin that declares a language owns its process-backed features: the host stops running its built-in tools for that language, so a missing or disabled plugin never falls back to them. A feature that none of the plugin's `executionProviders` supplies is unavailable for the language. #### Launch templates @@ -482,7 +457,6 @@ Discovery returns `{id: ":workspace", kind: "workspace"}` followed by | Language server launch | Supported for `packaged`, a packaged runtime, `systemCommand` (PATH only; a plan that depends on a project toolchain or a user override is refused), and a backend-answered command line. A backend answer of `{kind: "tool"}` is refused at start, because the tool is not yet resolved through the toolchain registry | | Language server presentation | Only diagnostics are shown. Completion, hover, navigation, rename, and formatting have no editor surface; a plugin that provides them sees no effect | | Plugin-owned sessions and `languageServer/*` methods | Wired; Core enforces the policy | -| `jdt` host integration | No implementation found in the desktop source; declaring it is refused with `plugin.integration.unsupported` | | Execution providers (`run`, `test`, `executionProviders`) | Not declared. A plugin that needs them is not offered for installation | | Output links | Supported. Console text in Build, Run, and Test becomes links; a matcher with `resolve` calls the backend on click | | Content providers | `documents/registerContentProvider` supported | @@ -494,7 +468,7 @@ Discovery returns `{id: ":workspace", kind: "workspace"}` followed by | [Languages](../../contract/v1/README.md#languages) | Language IDs, extensions, file names, project markers, dependency paths | | [Grammars and editor configuration](../../contract/v1/README.md#grammars-and-editor-configuration) | TextMate grammar limits and refusal rules, comment and bracket configuration, background execution | | [Language servers](../../contract/v1/README.md#language-servers) | Launch kinds (including `backend`), arguments, probe arguments, runtime dependencies | -| [Language server host integrations](../../contract/v1/README.md#language-server-host-integrations) | Host integrations and `jdt` | +| [Language server host integrations](../../contract/v1/README.md#language-server-host-integrations) | Host integrations (reserved; Core implements none) | | [Workspace configuration](../../contract/v1/README.md#workspace-configuration) | Expanding and pushing `workspaceConfiguration` | | [Plugin-owned sessions](../../contract/v1/README.md#plugin-owned-sessions) | Session fields, session policy, gates, state directories and pruning, preparation status | | [Runtime launch](../../contract/v1/README.md#runtime-launch) | Starting a package entry file with a tool such as `java` or `node` | diff --git a/shared/plugins/reference/domains/manifest.md b/shared/plugins/reference/domains/manifest.md index 1571fcdf..f800e0ec 100644 --- a/shared/plugins/reference/domains/manifest.md +++ b/shared/plugins/reference/domains/manifest.md @@ -27,7 +27,7 @@ | `ui` | `ui` | [界面](ui.md) | | `buildSystems`(不含 `provider`) | `buildSystems`;`modelSharing` 与 `modelInputs` 还需要 `buildModelInputs`(桌面端未声明) | [声明式构建](build-systems.md) | | `buildSystems[].provider` | `buildProviders` | [构建提供者](build-providers.md) | -| `buildSystems` 里的 `hostIntegration`(模型、测试结果、依赖来源) | `buildSystems`;只限官方插件,目前只有 `maven` | [声明式构建](build-systems.md) | +| `buildSystems` 里的 `hostIntegration` | 保留字段:任何 ID 都返回 `plugin.integration.unsupported` | [声明式构建](build-systems.md) | | `vcsProviders` | `vcsProviders`(桌面端未声明) | [VCS](vcs.md) | | `backend` | `backend` | [插件后端](backend.md) | | `pluginFunctions` | `pluginFunctions`、`backend`;不授予权限 | [插件后端](backend.md) | @@ -41,7 +41,7 @@ `requires` 必须写,并且恰好列出清单用到的能力:漏列或多列都是 `plugin.manifest.invalid`。能力名不是契约定义的,或版本不是 `0`,是 `plugin.api.unsupported`,`field` 为 `requires.<能力名>`。 -`hostIntegration` 在两处是不同的声明。语言服务器的 `hostIntegration` 对所有插件开放,只读取声明插件自己包里的资源,不授予额外权限(见[语言](languages.md))。构建系统的 `hostIntegration` 仍只限官方插件,目前只有 `maven`(见[声明式构建](build-systems.md))。宿主没有实现的 ID 一律返回 `plugin.integration.unsupported`。 +语言服务器和构建系统都有 `hostIntegration` 字段,两处都是保留字段:Core 不实现任何宿主集成,声明任何 ID 都返回 `plugin.integration.unsupported`(见[语言](languages.md)和[声明式构建](build-systems.md))。语言或构建工具需要的一切,都由插件自己的后端、提供者和声明给出。 ### 能力与宿主 @@ -139,7 +139,7 @@ Every plugin package has a `plugin.json` at its root. It says who the plugin is, | `ui` | `ui` | [UI](ui.md) | | `buildSystems` (without `provider`) | `buildSystems`; `modelSharing` and `modelInputs` also need `buildModelInputs` (not declared by the desktop host) | [Declarative build systems](build-systems.md) | | `buildSystems[].provider` | `buildProviders` | [Build providers](build-providers.md) | -| `hostIntegration` inside `buildSystems` (model, test-result, or dependency source) | `buildSystems`; official plugins only, and only `maven` exists | [Declarative build systems](build-systems.md) | +| `hostIntegration` inside `buildSystems` | Reserved: every ID returns `plugin.integration.unsupported` | [Declarative build systems](build-systems.md) | | `vcsProviders` | `vcsProviders` (not declared by the desktop host) | [VCS](vcs.md) | | `backend` | `backend` | [Plugin backend](backend.md) | | `pluginFunctions` | `pluginFunctions`, `backend`; grants no permission | [Plugin backend](backend.md) | @@ -153,7 +153,7 @@ Every plugin package has a `plugin.json` at its root. It says who the plugin is, `requires` is required and must list **exactly** the capabilities the manifest uses: a missing or unused one is `plugin.manifest.invalid`. A capability name the contract does not define, or a version other than `0`, is `plugin.api.unsupported` with `field` set to `requires.`. -`hostIntegration` is two different declarations. A language server's `hostIntegration` is open to every plugin; it reads only resources inside the declaring plugin's own package and grants no extra permission (see [Languages](languages.md)). A build system's `hostIntegration` is still limited to official plugins, and only `maven` exists (see [Declarative build systems](build-systems.md)). An ID the host does not implement is always `plugin.integration.unsupported`. +Language servers and build systems each have a `hostIntegration` field, and both are reserved: Core implements no host integration, and declaring any ID returns `plugin.integration.unsupported` (see [Languages](languages.md) and [Declarative build systems](build-systems.md)). Everything a language or build tool needs comes from the plugin's own backend, providers, and declarations. ### Capabilities and host diff --git a/shared/plugins/reference/protocol-changelog.md b/shared/plugins/reference/protocol-changelog.md index b0c1dab3..f982681c 100644 --- a/shared/plugins/reference/protocol-changelog.md +++ b/shared/plugins/reference/protocol-changelog.md @@ -125,9 +125,16 @@ | API-35 | `functions/invoke` | 0 | 2026-11-11 | [#17](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/17) | | — | `keymapImport/listInputs` | 0 | 2026-11-11 | [#17](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/17) | | — | `keymapImport/readChunk` | 0 | 2026-11-11 | [#17](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/17) | +| API-23 | `run/start`:`temporaryFiles` 增加 `{name, base64}` 字节文件 | 0 | 2026-11-13 | 待补(本 PR) | ## 时间线(新的在上) +### 2026-11-13 + +| PR | 修改人 | API 编号 | 协议 / 能力 | 改了什么 | +| --- | --- | --- | --- | --- | +| 待补(本 PR) | [@xiaoyumuxi](https://github.com/xiaoyumuxi) | API-23 | `pluginAPIVersion` 0 内的补充 | `run/start` 的 `temporaryFiles` 可写 `{name, base64}` 字节文件,宿主原样写入、不转码,供编码必须由插件决定的格式使用(例如按系统代码页读取的参数文件);`hostIntegration` 明确为保留字段:Core 不实现任何宿主集成,任何 ID 都以 `plugin.integration.unsupported` 拒绝,清单 schema 去掉只含 `jdt`、`maven` 的枚举;项目生成器的 `${field..major}` 取版本的第一个数字段,不再把 `1.8.0_412` 特判成 `8`;契约去掉 Java、Maven、JDT 专属说明。 | + ### 2026-11-11 | PR | 修改人 | API 编号 | 协议 / 能力 | 改了什么 | From 159e0f6b40a8d4ac299938801851e86f4ef1d388 Mon Sep 17 00:00:00 2001 From: Yao Jingxi <23722032@bjtu.edu.cn> Date: Fri, 13 Nov 2026 23:33:11 +0800 Subject: [PATCH 2/3] =?UTF-8?q?docs(plugins):=20=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AE=B0=E5=BD=95=E8=A1=A5=E4=B8=8A=20#23?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shared/plugins/reference/protocol-changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/plugins/reference/protocol-changelog.md b/shared/plugins/reference/protocol-changelog.md index f982681c..1c44dd0a 100644 --- a/shared/plugins/reference/protocol-changelog.md +++ b/shared/plugins/reference/protocol-changelog.md @@ -125,7 +125,7 @@ | API-35 | `functions/invoke` | 0 | 2026-11-11 | [#17](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/17) | | — | `keymapImport/listInputs` | 0 | 2026-11-11 | [#17](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/17) | | — | `keymapImport/readChunk` | 0 | 2026-11-11 | [#17](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/17) | -| API-23 | `run/start`:`temporaryFiles` 增加 `{name, base64}` 字节文件 | 0 | 2026-11-13 | 待补(本 PR) | +| API-23 | `run/start`:`temporaryFiles` 增加 `{name, base64}` 字节文件 | 0 | 2026-11-13 | [#23](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/23) | ## 时间线(新的在上) @@ -133,7 +133,7 @@ | PR | 修改人 | API 编号 | 协议 / 能力 | 改了什么 | | --- | --- | --- | --- | --- | -| 待补(本 PR) | [@xiaoyumuxi](https://github.com/xiaoyumuxi) | API-23 | `pluginAPIVersion` 0 内的补充 | `run/start` 的 `temporaryFiles` 可写 `{name, base64}` 字节文件,宿主原样写入、不转码,供编码必须由插件决定的格式使用(例如按系统代码页读取的参数文件);`hostIntegration` 明确为保留字段:Core 不实现任何宿主集成,任何 ID 都以 `plugin.integration.unsupported` 拒绝,清单 schema 去掉只含 `jdt`、`maven` 的枚举;项目生成器的 `${field..major}` 取版本的第一个数字段,不再把 `1.8.0_412` 特判成 `8`;契约去掉 Java、Maven、JDT 专属说明。 | +| [#23](https://github.com/Lithe-IDEA/Lithe-core-rust/pull/23) | [@xiaoyumuxi](https://github.com/xiaoyumuxi) | API-23 | `pluginAPIVersion` 0 内的补充 | `run/start` 的 `temporaryFiles` 可写 `{name, base64}` 字节文件,宿主原样写入、不转码,供编码必须由插件决定的格式使用(例如按系统代码页读取的参数文件);`hostIntegration` 明确为保留字段:Core 不实现任何宿主集成,任何 ID 都以 `plugin.integration.unsupported` 拒绝,清单 schema 去掉只含 `jdt`、`maven` 的枚举;项目生成器的 `${field..major}` 取版本的第一个数字段,不再把 `1.8.0_412` 特判成 `8`;契约去掉 Java、Maven、JDT 专属说明。 | ### 2026-11-11 From 5a8e67d4b73f5e633e67dc09e722c3b22b47b280 Mon Sep 17 00:00:00 2001 From: Yao Jingxi <23722032@bjtu.edu.cn> Date: Fri, 13 Nov 2026 23:39:39 +0800 Subject: [PATCH 3/3] =?UTF-8?q?test(run):=20=E5=A5=91=E7=BA=A6=20fixture?= =?UTF-8?q?=20=E8=A6=86=E7=9B=96=20temporaryFiles=20base64=20=E7=9A=84?= =?UTF-8?q?=E6=8B=92=E7=BB=9D=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run-start.json 增加三条拒绝用例:同时给出 contents 和 base64、两者都不给、 base64 不是标准编码。Core 的 fixture 回放测试改为传入临时目录, 让这些用例走到逐项校验,而不是停在缺少临时目录。 --- rust/lithe-core/src/tests/plugin_run_debug.rs | 4 ++ .../contract/v1/fixtures/run/run-start.json | 44 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/rust/lithe-core/src/tests/plugin_run_debug.rs b/rust/lithe-core/src/tests/plugin_run_debug.rs index f9af00b5..02bbe5a7 100644 --- a/rust/lithe-core/src/tests/plugin_run_debug.rs +++ b/rust/lithe-core/src/tests/plugin_run_debug.rs @@ -112,12 +112,16 @@ fn run_start_fixture_cases_decode_the_same_in_every_host() { .map(|directory| directory.as_str().unwrap()) .collect(); let workspace = Workspace::new("run-start-fixture", &directories); + // Every host supplies its temporary directory, so the temporary-file refusals of the + // fixture are reached instead of stopping at a missing directory. + let temporary = Workspace::new("run-start-fixture-files", &[]); let manifest = named_manifest(&fixture); for case in fixture["cases"].as_array().unwrap() { let name = case["name"].as_str().unwrap(); let outcome = run_decode_start_command(json!({ "manifest": manifest, "workspaceRoot": workspace.root(), + "temporaryDirectory": temporary.root(), "params": case["params"], })) .expect("payload is well formed"); diff --git a/shared/plugins/contract/v1/fixtures/run/run-start.json b/shared/plugins/contract/v1/fixtures/run/run-start.json index 50228afe..a779efb5 100644 --- a/shared/plugins/contract/v1/fixtures/run/run-start.json +++ b/shared/plugins/contract/v1/fixtures/run/run-start.json @@ -1,5 +1,5 @@ { - "description": "Parameters of lithe/host/run/start and what run.decodeStart returns. The host passes the plugin's manifest (`manifest`, a file in this directory), a workspace root that holds the directories in `workspaceDirectories`, and the request's `params`. `start` is the normalized start; `error` is the rejection every host reports. Cases with temporary files depend on the host's temporary directory and are covered by Core's own tests.", + "description": "Parameters of lithe/host/run/start and what run.decodeStart returns. The host passes the plugin's manifest (`manifest`, a file in this directory), a workspace root that holds the directories in `workspaceDirectories`, and the request's `params`. `start` is the normalized start; `error` is the rejection every host reports. The host supplies an absolute temporary directory (`temporaryDirectory`) with every case. Accepted starts with temporary files return paths inside that directory, so only their refusals are listed here; the accepted ones are covered by Core's own tests.", "manifest": "tool-reference-launch.plugin.json", "workspaceDirectories": ["app", "app/tools"], "cases": [ @@ -266,6 +266,48 @@ }, "error": { "code": "plugin.run.invalidStart", "field": "temporaryFiles" } }, + { + "name": "a temporary file with both text and bytes", + "params": { + "label": "Run", + "process": { + "executable": { "kind": "tool", "tool": "interpreter" }, + "arguments": [], + "workingDirectory": ".", + "environment": {} + }, + "temporaryFiles": [{ "name": "args", "contents": "", "base64": "" }] + }, + "error": { "code": "plugin.run.invalidStart", "field": "temporaryFiles[0]" } + }, + { + "name": "a temporary file with neither text nor bytes", + "params": { + "label": "Run", + "process": { + "executable": { "kind": "tool", "tool": "interpreter" }, + "arguments": [], + "workingDirectory": ".", + "environment": {} + }, + "temporaryFiles": [{ "name": "args" }] + }, + "error": { "code": "plugin.run.invalidStart", "field": "temporaryFiles[0]" } + }, + { + "name": "temporary file bytes that are not standard base64", + "params": { + "label": "Run", + "process": { + "executable": { "kind": "tool", "tool": "interpreter" }, + "arguments": [], + "workingDirectory": ".", + "environment": {} + }, + "temporaryFiles": [{ "name": "args", "base64": "not base64!" }] + }, + "error": { "code": "plugin.run.invalidStart", "field": "temporaryFiles[0].base64" } + }, { "name": "an unknown start parameter", "params": {