Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .machine_readable/dev-toolchain.a2ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# SPDX-License-Identifier: PMPL-1.0-or-later
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# 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
12 changes: 7 additions & 5 deletions cartridges/lsp-mcp/cartridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
}
},
{
Expand Down
32 changes: 30 additions & 2 deletions cartridges/lsp-mcp/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ---------------------------------------------------------------------------
Expand All @@ -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], {
Expand Down
14 changes: 14 additions & 0 deletions cartridges/lsp-mcp/presets.json
Original file line number Diff line number Diff line change
@@ -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) <j.d.a.jewell@open.ac.uk>",
"_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)"
}
}
}
Loading