From 9c92c2d9dab0c59e2b4a26608b9c7f228ffb157a Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 26 May 2026 13:29:54 +0000 Subject: [PATCH 1/5] feat(opencode): enable LSP servers by default via opencode.json Add logic to install.sh to create/update opencode.json with an empty 'lsp' config block, enabling all built-in LSP servers globally. - Add jq to prerequisites for safe JSON manipulation - If opencode.json exists without 'lsp' key, add it without touching other config - If it doesn't exist, create it with '' and 'lsp': {} - Add LSP validation checks to all 5 scenario tests --- src/opencode/install.sh | 27 +++++++++++++++++++++++++++ test/opencode/autoupdate_disabled.sh | 6 ++++++ test/opencode/autoupdate_enabled.sh | 6 ++++++ test/opencode/custom_username.sh | 6 ++++++ test/opencode/default.sh | 6 ++++++ test/opencode/specific_version.sh | 6 ++++++ test/opencode/test.sh | 8 ++++++++ 7 files changed, 65 insertions(+) diff --git a/src/opencode/install.sh b/src/opencode/install.sh index 353c301..b5aea6f 100644 --- a/src/opencode/install.sh +++ b/src/opencode/install.sh @@ -54,6 +54,10 @@ ensure_prerequisites() { missing="${missing} curl" fi + if ! command -v jq >/dev/null 2>&1; then + missing="${missing} jq" + fi + if [ ! -f /etc/ssl/certs/ca-certificates.crt ]; then missing="${missing} ca-certificates" fi @@ -185,6 +189,29 @@ chown -R "${USERNAME}:${USERNAME}" \ "${USER_HOME}/.local/share/opencode" \ "${USER_HOME}/.local/state/opencode" +log "Configuring LSP in opencode.json..." + +opencode_config="${USER_HOME}/.config/opencode/opencode.json" + +if [ -f "$opencode_config" ]; then + if ! jq -e '.lsp' "$opencode_config" >/dev/null 2>&1; then + jq '. + {"lsp": {}}' "$opencode_config" > "${opencode_config}.tmp" && mv "${opencode_config}.tmp" "$opencode_config" + log "Added 'lsp' key to existing opencode.json" + else + log "'lsp' key already exists in opencode.json" + fi +else + cat > "$opencode_config" <<'EOF' +{ + "$schema": "https://opencode.ai/config.json", + "lsp": {} +} +EOF + log "Created opencode.json with LSP enabled" +fi + +chown "${USERNAME}:${USERNAME}" "$opencode_config" + cat > /usr/local/bin/opencode-fix-permissions < Date: Tue, 26 May 2026 13:30:20 +0000 Subject: [PATCH 2/5] docs: refine core principles and selection rules in AGENTS.md for clarity and specificity --- AGENTS.md | 76 +++++++++++++++++-------------------------------------- 1 file changed, 23 insertions(+), 53 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 90440ca..0647d99 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,11 +15,10 @@ Default behavior: ## Core Principles -- `specialist-first` is mandatory -- generalist is fallback, not peer -- `delegate` loads before substantive work - specialist-first evaluation is mandatory for every task before proceeding -- discovery and selection happen before execution +- `delegate` loads before substantive work +- agent main context stays clean — research, tool execution, logs, and low-level output belong to subagents; the main agent owns planning, delegation, and synthesis only +- generalist is fallback, not peer - capability alone is never sufficient when a better specialist exists - delegation distributes work, not accountability - fallback must be brief, explicit, and defensible @@ -51,35 +50,11 @@ Choose specialists by semantic fit, not convenience. Selection policy: - select for the immediate task, not the broadest surrounding program of work -- match by description, specialization, scope, and constraints -- prefer decomposing broad tasks into specialist-owned scopes before delegation -- do not hand a mixed multi-domain task to one agent when it can be cleanly split -- if subtasks require materially different specialist expertise and can be assigned without overlapping ownership, splitting is required -- early decomposition in this phase is structural for selection and handoff, not substantive execution or broad local research -- minimal local context gathering is allowed when needed to scope selection and handoff -- prefer the most semantically specific eligible specialist -- use stable documented tie-break rules when candidates are similarly suitable +- match by domain, role, work type, and scope — never narrow the match by requiring a specific technology stack mentioned in the task - do not use a generalist if an eligible specialist exists - do not bypass a specialist because the current agent could also do the work -Eligibility minimum: -- immediate task = the next concrete unit of work to assign, not the broader surrounding initiative -- explicit domain or task match in the description -- no conflict with stated constraints -- scope compatible with the immediate task -- no clearly better eligible specialist - -Tie-break rules: -- first: higher semantic specificity -- second: stable documented precedence in the current environment -- third: split across specialists when domains or scopes are naturally distinct - -Invalid reasons to choose a generalist over an eligible specialist include: -- broader autonomy -- owning the whole rollout -- fewer handoffs -- convenience -- the current agent or chosen agent can probably do it +Detailed selection criteria, including how to avoid the "specificity trap" (over-narrowing by technology stack), tie-break rules, and invalid selection reasons, are fully defined in the `delegate` skill. **You must load and follow the `delegate` skill for complete selection rules.** --- @@ -105,22 +80,12 @@ Required behavior: Generalist execution is an exception path. Fallback is allowed only when: -- no eligible specialist exists +- no discovered specialist is a defensible direct or adjacent fit for the immediate task - all discovered candidates are unsuitable for stated constraints - delegation is explicitly constrained by the user -- delegation failed after a valid handoff, re-evaluation, and no viable specialist path remains under the current task constraints - -Fallback requirements: -- explain briefly why delegation did not apply -- keep the explanation factual and specific -- identify the failed discovery, selection, or constraint condition -- under discovery limits, state why no known eligible specialist fits better under the current constraints -- do not treat "I can do it" as sufficient justification -- do not silently fall back +- delegation failed after a valid handoff, re-evaluation, and no viable specialist path remains -Fallback mode: -- prefer delegating to a generalist agent when the environment supports it -- direct execution by the current agent is allowed only when delegation is constrained, unavailable, or already failed under the current task constraints +Detailed fallback requirements, invalid justifications (such as missing technology keywords), and fallback execution modes are defined in the `delegate` skill. **You must load and follow the `delegate` skill to validate any fallback decision.** --- @@ -186,16 +151,19 @@ Provide enough explicit context to avoid avoidable clarification loops. 6. Discover eligible specialists. 7. Select the best specialist or specialists. -Hard-gate before execution: -- [ ] `delegate` activated before substantive work -- [ ] specialist-first evaluation completed -- [ ] immediate task identified before agent selection -- [ ] done criteria defined or clarified -- [ ] `wiki/index.md` consulted before broad workspace exploration when available -- [ ] discovery completed -- [ ] eligible candidates assessed -- [ ] delegation decision made under this policy -- [ ] any fallback explicitly justified +## Before Any Execution + +**STOP. Do not proceed with any substantive work until ALL conditions below are confirmed true.** + +1. **`delegate` activated** — `skill('delegate')` has been loaded and evaluated. No exceptions. +2. **Specialist-first evaluation completed** — you have inspected available agents and selected by semantic fit. +3. **Immediate task identified** — the concrete next unit of work is named, not the broader initiative. +4. **Done criteria defined** — success is unambiguous; if unclear, stop and clarify. +5. **`wiki/index.md` consulted** — read it if it exists before any broad exploration. +6. **Discovery completed** — eligible candidates were gathered from all observable sources. +7. **Delegation decision made** — you chose under this policy, with explicit justification if fallback. + +If ANY condition is false: **STOP. Go back to Phase 1.** ### Phase 2: Execute @@ -204,6 +172,8 @@ Hard-gate before execution: 3. Allow subdelegation when it improves specialization or decomposition. 4. Review and synthesize before returning results upward. +**Agent Main Rule:** Do not execute tools directly unless task constraints explicitly require main-agent execution. All research, tool invocation, log collection, and low-level work belongs to subagents. The main agent context stays clean for planning, delegation, and synthesis. + ### Phase 3: After Task 1. Load `wiki`. From 42c6866255c582412f0c2153f8a38a1af09c8d39 Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 26 May 2026 13:29:54 +0000 Subject: [PATCH 3/5] feat(opencode): enable LSP servers by default via opencode.json Add logic to install.sh to create/update opencode.json with an empty 'lsp' config block, enabling all built-in LSP servers globally. - Add jq to prerequisites for safe JSON manipulation - If opencode.json exists without 'lsp' key, add it without touching other config - If it doesn't exist, create it with '' and 'lsp': {} - Add LSP validation checks to all 5 scenario tests --- src/opencode/devcontainer-feature.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opencode/devcontainer-feature.json b/src/opencode/devcontainer-feature.json index 3b0cc39..e71927d 100644 --- a/src/opencode/devcontainer-feature.json +++ b/src/opencode/devcontainer-feature.json @@ -1,7 +1,7 @@ { "name": "opencode CLI", "id": "opencode", - "version": "0.3.5", + "version": "0.3.6", "description": "Installs the opencode AI coding agent CLI and ensures volume-mounted data directories are owned by the correct user.", "documentationURL": "https://opencode.ai", "options": { From d4de19fbb6eb20228aa6d5b41248cbe067db24f4 Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 26 May 2026 13:29:54 +0000 Subject: [PATCH 4/5] feat(opencode): enable LSP servers by default via opencode.json Add logic to install.sh to create/update opencode.json with an empty 'lsp' config block, enabling all built-in LSP servers globally. - Add jq to prerequisites for safe JSON manipulation - If opencode.json exists without 'lsp' key, add it without touching other config - If it doesn't exist, create it with '' and 'lsp': {} - Add LSP validation checks to all 5 scenario tests --- test/opencode/autoupdate_disabled.sh | 10 +++++++--- test/opencode/autoupdate_enabled.sh | 10 +++++++--- test/opencode/custom_username.sh | 10 +++++++--- test/opencode/default.sh | 10 +++++++--- test/opencode/specific_version.sh | 10 +++++++--- test/opencode/test.sh | 12 +++++++----- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/test/opencode/autoupdate_disabled.sh b/test/opencode/autoupdate_disabled.sh index ba24686..4a0fc3b 100644 --- a/test/opencode/autoupdate_disabled.sh +++ b/test/opencode/autoupdate_disabled.sh @@ -3,12 +3,16 @@ set -e source dev-container-features-test-lib +OPENCODE_CONFIG="" +[ -f /root/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/root/.config/opencode/opencode.json +[ -f /home/vscode/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/home/vscode/.config/opencode/opencode.json + check "opencode binary exists" test -f /usr/local/bin/opencode -check "opencode.json exists" test -f /home/vscode/.config/opencode/opencode.json +check "opencode.json exists" test -n "$OPENCODE_CONFIG" -check "opencode.json has lsp key" jq -e '.lsp' /home/vscode/.config/opencode/opencode.json +check "opencode.json has lsp key" jq -e '.lsp' "$OPENCODE_CONFIG" -check "opencode.json lsp is empty" test "$(jq '.lsp' /home/vscode/.config/opencode/opencode.json)" = "{}" +check "opencode.json lsp is empty" test "$(jq '.lsp | length' "$OPENCODE_CONFIG")" -eq 0 reportResults \ No newline at end of file diff --git a/test/opencode/autoupdate_enabled.sh b/test/opencode/autoupdate_enabled.sh index 50e1d6a..3b27772 100644 --- a/test/opencode/autoupdate_enabled.sh +++ b/test/opencode/autoupdate_enabled.sh @@ -3,14 +3,18 @@ set -e source dev-container-features-test-lib +OPENCODE_CONFIG="" +[ -f /root/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/root/.config/opencode/opencode.json +[ -f /home/vscode/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/home/vscode/.config/opencode/opencode.json + check "opencode binary exists" test -f /usr/local/bin/opencode check "opencode is installed in PATH" command -v opencode -check "opencode.json exists" test -f /home/vscode/.config/opencode/opencode.json +check "opencode.json exists" test -n "$OPENCODE_CONFIG" -check "opencode.json has lsp key" jq -e '.lsp' /home/vscode/.config/opencode/opencode.json +check "opencode.json has lsp key" jq -e '.lsp' "$OPENCODE_CONFIG" -check "opencode.json lsp is empty" test "$(jq '.lsp' /home/vscode/.config/opencode/opencode.json)" = "{}" +check "opencode.json lsp is empty" test "$(jq '.lsp | length' "$OPENCODE_CONFIG")" -eq 0 reportResults \ No newline at end of file diff --git a/test/opencode/custom_username.sh b/test/opencode/custom_username.sh index 310a0d9..d249541 100755 --- a/test/opencode/custom_username.sh +++ b/test/opencode/custom_username.sh @@ -3,14 +3,18 @@ set -e source dev-container-features-test-lib +OPENCODE_CONFIG="" +[ -f /root/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/root/.config/opencode/opencode.json +[ -f /home/vscode/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/home/vscode/.config/opencode/opencode.json + check "opencode binary exists" test -f /usr/local/bin/opencode check "opencode-fix-permissions script exists" test -f /usr/local/bin/opencode-fix-permissions -check "opencode.json exists" test -f /home/vscode/.config/opencode/opencode.json +check "opencode.json exists" test -n "$OPENCODE_CONFIG" -check "opencode.json has lsp key" jq -e '.lsp' /home/vscode/.config/opencode/opencode.json +check "opencode.json has lsp key" jq -e '.lsp' "$OPENCODE_CONFIG" -check "opencode.json lsp is empty" test "$(jq '.lsp' /home/vscode/.config/opencode/opencode.json)" = "{}" +check "opencode.json lsp is empty" test "$(jq '.lsp | length' "$OPENCODE_CONFIG")" -eq 0 reportResults diff --git a/test/opencode/default.sh b/test/opencode/default.sh index 310a0d9..d249541 100755 --- a/test/opencode/default.sh +++ b/test/opencode/default.sh @@ -3,14 +3,18 @@ set -e source dev-container-features-test-lib +OPENCODE_CONFIG="" +[ -f /root/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/root/.config/opencode/opencode.json +[ -f /home/vscode/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/home/vscode/.config/opencode/opencode.json + check "opencode binary exists" test -f /usr/local/bin/opencode check "opencode-fix-permissions script exists" test -f /usr/local/bin/opencode-fix-permissions -check "opencode.json exists" test -f /home/vscode/.config/opencode/opencode.json +check "opencode.json exists" test -n "$OPENCODE_CONFIG" -check "opencode.json has lsp key" jq -e '.lsp' /home/vscode/.config/opencode/opencode.json +check "opencode.json has lsp key" jq -e '.lsp' "$OPENCODE_CONFIG" -check "opencode.json lsp is empty" test "$(jq '.lsp' /home/vscode/.config/opencode/opencode.json)" = "{}" +check "opencode.json lsp is empty" test "$(jq '.lsp | length' "$OPENCODE_CONFIG")" -eq 0 reportResults diff --git a/test/opencode/specific_version.sh b/test/opencode/specific_version.sh index 768e431..872d49c 100755 --- a/test/opencode/specific_version.sh +++ b/test/opencode/specific_version.sh @@ -3,6 +3,10 @@ set -e source dev-container-features-test-lib +OPENCODE_CONFIG="" +[ -f /root/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/root/.config/opencode/opencode.json +[ -f /home/vscode/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/home/vscode/.config/opencode/opencode.json + check "opencode binary exists" test -f /usr/local/bin/opencode check "opencode-fix-permissions script exists" test -f /usr/local/bin/opencode-fix-permissions @@ -11,10 +15,10 @@ check "marker file exists for specific version 1.3.17" test -f /usr/local/share/ check "installed version is 1.3.17" bash -c "opencode --version | grep -q '1.3.17'" -check "opencode.json exists" test -f /home/vscode/.config/opencode/opencode.json +check "opencode.json exists" test -n "$OPENCODE_CONFIG" -check "opencode.json has lsp key" jq -e '.lsp' /home/vscode/.config/opencode/opencode.json +check "opencode.json has lsp key" jq -e '.lsp' "$OPENCODE_CONFIG" -check "opencode.json lsp is empty" test "$(jq '.lsp' /home/vscode/.config/opencode/opencode.json)" = "{}" +check "opencode.json lsp is empty" test "$(jq '.lsp | length' "$OPENCODE_CONFIG")" -eq 0 reportResults diff --git a/test/opencode/test.sh b/test/opencode/test.sh index c65cb49..5c30acb 100755 --- a/test/opencode/test.sh +++ b/test/opencode/test.sh @@ -3,18 +3,20 @@ set -e source dev-container-features-test-lib +OPENCODE_CONFIG="" +[ -f /root/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/root/.config/opencode/opencode.json +[ -f /home/vscode/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/home/vscode/.config/opencode/opencode.json + check "opencode binary exists" test -f /usr/local/bin/opencode check "opencode-fix-permissions script exists" test -f /usr/local/bin/opencode-fix-permissions check "opencode postStartCommand script exists for autoupdate" test -f /usr/local/share/devcontainer-features/opencode-postStartCommand.sh -check "opencode.json exists" test -f /home/vscode/.config/opencode/opencode.json - -check "opencode.json has lsp key" jq -e '.lsp' /home/vscode/.config/opencode/opencode.json +check "opencode.json exists" test -n "$OPENCODE_CONFIG" -check "opencode.json lsp is an object" test "$(jq -r '.lsp | type' /home/vscode/.config/opencode/opencode.json)" = "object" +check "opencode.json has lsp key" jq -e '.lsp' "$OPENCODE_CONFIG" -check "opencode.json lsp is empty" test "$(jq '.lsp' /home/vscode/.config/opencode/opencode.json)" = "{}" +check "opencode.json lsp is empty" test "$(jq '.lsp | length' "$OPENCODE_CONFIG")" -eq 0 reportResults From 81179e71c633be5ef1267dd8109cfc1ecc6e1f1e Mon Sep 17 00:00:00 2001 From: Test Date: Tue, 26 May 2026 13:40:40 +0000 Subject: [PATCH 5/5] docs(wiki): document test execution modes and user-home file path pattern --- wiki/conventions.md | 9 ++++++++- wiki/testing.md | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/wiki/conventions.md b/wiki/conventions.md index f04f874..c9246cc 100644 --- a/wiki/conventions.md +++ b/wiki/conventions.md @@ -15,4 +15,11 @@ Options from `devcontainer-feature.json` are exported as environment variables ( Tests live in `test//`: - `default.sh` / `default_auto.sh` — default behavior - `scenarios.json` — scenario definitions -- `custom_*.sh` — specific option combinations \ No newline at end of file +- `custom_*.sh` — specific option combinations + +### User-Home File Paths in Tests + +When testing files inside user home directories (e.g. `~/.config/opencode/opencode.json`), +the path depends on the base image. Autogenerated tests use plain images (`debian:latest`, +`ubuntu:latest`) where user is `root` (`/root/`). Scenario tests use `devcontainers/base` +images where user is `vscode` (`/home/vscode/`). Always probe both paths dynamically. \ No newline at end of file diff --git a/wiki/testing.md b/wiki/testing.md index 5b86553..4fafaa5 100644 --- a/wiki/testing.md +++ b/wiki/testing.md @@ -16,6 +16,21 @@ devcontainer features test --skip-scenarios -f opencode . devcontainer features test --skip-autogenerated --skip-duplicated -f opencode . ``` +## Test Execution + +There are two test modes with different behaviors: + +- **Autogenerated** (`--skip-scenarios`): builds fresh from a base image, runs `test//test.sh` as the test suite. Use for testing against plain images like `debian:latest` or `ubuntu:latest` where the user is `root`. +- **Scenario** (`--skip-autogenerated`): builds from predefined scenarios in `test//scenarios.json`, runs **only** the scenario-specific `.sh` file (e.g. `default.sh`). The generic `test.sh` is **not** executed here. + +When writing tests that check user-home files (like `~/.config/opencode/opencode.json`), both user contexts must be supported: + +```bash +OPENCODE_CONFIG="" +[ -f /root/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/root/.config/opencode/opencode.json +[ -f /home/vscode/.config/opencode/opencode.json ] && OPENCODE_CONFIG=/home/vscode/.config/opencode/opencode.json +``` + ## Available Features | Feature | Description | Autoupdate |