diff --git a/.machine_readable/dev-toolchain.a2ml b/.machine_readable/dev-toolchain.a2ml new file mode 100644 index 00000000..c6837481 --- /dev/null +++ b/.machine_readable/dev-toolchain.a2ml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: PMPL-1.0-or-later +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# BoJ Server — Permanent Dev-Toolchain Manifest +# +# Single source of truth for the standing local dev loop so it NEVER has to be +# re-assembled per session. Paths are the canonical /dev/tools toolchain and +# are verified-present (do not list a tool that is not actually installed — +# an unresolved entry is worse than none). +# +# Wired NOW: the LSP row is live via `cartridges/lsp-mcp/presets.json` +# (lsp_start {"preset":"rust"} → rust-analyzer, no per-use wiring). +# Adoption path: bsp-mcp (build), dap-mcp (debug) and a lint surface should +# read this same manifest for their Rust/Zig/Julia/Idris presets — same +# pattern as lsp-mcp. Declared here so the contract exists before the wiring. + +@canonical-location: `.machine_readable/dev-toolchain.a2ml` + +[authorization] +standing = true +note = "Owner standing authorization (2026-05-18): use these boj dev-loop capabilities on demand without asking for permission." + +[rust] +lsp = "/home/hyperpolymath/dev/tools/languages/rust/cargo-home/bin/rust-analyzer" # verified: rust-analyzer 1.95.0 (rustup component) +cargo = "/home/hyperpolymath/dev/tools/languages/rust/cargo-home/bin/cargo" # verified: cargo 1.95.0 +build = "cargo build" +check = "cargo check" +lint = "cargo clippy --all-targets -- -D warnings" +test = "cargo test" +debug = "rust-gdb / lldb via dap-mcp" # dap-mcp adoption pending +env = { CARGO_HOME = "/home/hyperpolymath/dev/tools/languages/rust/cargo-home", RUSTUP_HOME = "/home/hyperpolymath/dev/tools/languages/rust/rustup-home" } +wired_lsp = true # cartridges/lsp-mcp/presets.json -> preset "rust" + +[zig] +toolchain = "zig 0.15.2" # verified on PATH +build = "zig build" +test = "zig build test" +wired_lsp = false # zls not installed; do NOT add an lsp preset until it is + +[julia] +launcher = "/home/hyperpolymath/dev/tools/languages/julia/juliaup-home/bin/julia" # verified: 1.12.6 (release) / 1.10.11 (lts) +test = "julia --project=. -e 'using Pkg; Pkg.test()'" +instantiate= "julia --project=. -e 'using Pkg; Pkg.instantiate()'" +quality = "Aqua.test_all + JET (when wired in a package's test deps)" +wired_lsp = false # no julia LSP installed yet + +[idris2] +check = "/home/hyperpolymath/dev/tools/provers/idris2/0.8.0/bin/idris2 --check" # verified: Idris2 0.8.0 +wired_lsp = false # idris2-lsp not installed yet diff --git a/cartridges/lsp-mcp/cartridge.json b/cartridges/lsp-mcp/cartridge.json index 3217c6d9..0258f334 100644 --- a/cartridges/lsp-mcp/cartridge.json +++ b/cartridges/lsp-mcp/cartridge.json @@ -29,23 +29,25 @@ "properties": { "command": { "type": "string", - "description": "Language server executable (e.g. 'rust-analyzer', 'affinescript server --stdio', 'clangd')" + "description": "Language server executable (e.g. 'rust-analyzer', 'affinescript server --stdio', 'clangd'). Optional when `preset` is given." + }, + "preset": { + "type": "string", + "description": "Permanent pinned toolchain preset from presets.json (e.g. 'rust' → the canonical rust-analyzer at /dev/tools). Use this instead of re-specifying the command/path every session. One of `command` or `preset` is required; an explicit `command` overrides the preset." }, "args": { "type": "array", "items": { "type": "string" }, - "description": "Additional command arguments" + "description": "Additional command arguments (appended after any preset args)" }, "workspace_root": { "type": "string", "description": "Workspace root directory path" } }, - "required": [ - "command" - ] + "required": [] } }, { diff --git a/cartridges/lsp-mcp/mod.js b/cartridges/lsp-mcp/mod.js index 8893ab8b..936fbebe 100644 --- a/cartridges/lsp-mcp/mod.js +++ b/cartridges/lsp-mcp/mod.js @@ -159,6 +159,21 @@ function getSession(session_id) { return entry; } +// Permanent pinned toolchain presets (presets.json, co-located). Lets callers +// pass {preset:"rust"} instead of re-specifying the rust-analyzer path every +// time. Loaded once, lazily; missing/invalid file degrades to "no presets". +let _presets = null; +async function loadPresets() { + if (_presets) return _presets; + try { + const txt = await Deno.readTextFile(new URL("./presets.json", import.meta.url)); + _presets = JSON.parse(txt).presets ?? {}; + } catch { + _presets = {}; + } + return _presets; +} + // --------------------------------------------------------------------------- // Tool handlers // --------------------------------------------------------------------------- @@ -168,8 +183,21 @@ export async function handleTool(toolName, args) { // -- lsp_start ----------------------------------------------------------- case "lsp_start": { - const { command, args: extraArgs = [], workspace_root } = args; - if (!command) return { status: 400, data: { error: "command is required" } }; + let { command, preset, args: extraArgs = [], workspace_root } = args; + + // Resolve a permanent pinned preset (e.g. "rust" → rust-analyzer) so + // callers never re-assemble the toolchain. Explicit `command` still + // wins and stays fully backward-compatible. + if (!command && preset) { + const presets = await loadPresets(); + const p = presets[preset]; + if (!p) { + return { status: 400, data: { error: `Unknown preset '${preset}'. Available: ${Object.keys(presets).join(", ") || "(none)"}` } }; + } + command = p.command; + if (Array.isArray(p.args) && p.args.length) extraArgs = [...p.args, ...extraArgs]; + } + if (!command) return { status: 400, data: { error: "command or preset is required" } }; const cmdParts = command.trim().split(/\s+/); const proc = new Deno.Command(cmdParts[0], { diff --git a/cartridges/lsp-mcp/presets.json b/cartridges/lsp-mcp/presets.json new file mode 100644 index 00000000..fbde2cf5 --- /dev/null +++ b/cartridges/lsp-mcp/presets.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://boj.dev/schemas/lsp-presets/v1.json", + "spdx": "PMPL-1.0-or-later", + "copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) ", + "_doc": "Permanent, pinned LSP server presets. lsp_start accepts {\"preset\":\"rust\"} instead of re-specifying the command/path every time. Absolute paths point at the canonical /dev/tools toolchain. Only presets whose server binary is actually installed and verified are listed here — do NOT add a preset for a server that is not present (an unresolved preset is worse than no preset).", + "presets": { + "rust": { + "command": "/home/hyperpolymath/dev/tools/languages/rust/cargo-home/bin/rust-analyzer", + "args": [], + "language_id": "rust", + "verified": "rust-analyzer 1.95.0 (rustup component, stable toolchain)" + } + } +}