From fccec1924316edddf95b1091be0fd5207ac3e9df Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Wed, 27 May 2026 12:57:01 +0200 Subject: [PATCH] :sparkles: Document and stabilise the parallel-workspace CLI; wire AI agents Improve parallel-workspaces developer CLI, and add an opt-in layer that lets four AI coding agents (Claude Code, opencode, VS Code Copilot, OpenAI Codex CLI) drive a specific workspace through a single launcher command. Parallel-workspace semantics ---------------------------- each run-devenv-agentic call brings up one wsN; --ws N (integer; default 0) targets a specific workspace and auto-starts ws0 first when N>=1 so the worker invariant holds. --sync is forbidden on ws0 and re-seeds the workspace from the live repo for ws1+. Stop semantics mirror the start invariant -- ws0 is the last to stop, shared infra stops with it, --all walks every instance highest-first. The worker policy section explains why workers run only on ws0 (Postgres FOR UPDATE SKIP LOCKED is safe across many workers but the cron dedup primitive is best-effort, and :telemetry / :audit-log-archive are not idempotent). Per-instance Valkey Pub/Sub isolation, msgbus topology, and the "async task notifications miss ws1+ tabs" caveat are stated explicitly. The mem:prod-infra/core memory captures the same external-services and task-queue / Pub-Sub topology in agent-readable form, and mem:backend/core and mem:critical-info now cross-link it so backend work surfaces the horizontal-scaling constraints from the start. AI coding agent integration --------------------------- New top-level .devenv/ directory holds committed templates (templates/{claude-code,opencode,vscode}.json and templates/codex.toml, each with \${PENPOT_MCP_PORT} and \${SERENA_MCP_PORT} placeholders) plus committed shared entries (matching shared/* files for Playwright, the only workspace-independent server we ship today). ./manage.sh start-coding-agent [--ws N] launches the chosen client against one workspace. It cd's into the target's directory (the live repo for ws0; workspace-path "wsN" for ws1+) and refuses to launch unless (a) the binary is on PATH, (b) the workspace directory exists for ws1+, and (c) the instance is up (devenv-main-running) -- the MCP servers only exist while the devenv is running. The agentic-devenv guide is restructured around this Quick start path, with a per-client table and a Manual configuration fallback for clients we don't cover. Co-Authored-By: Codex Co-Authored-By: Claude Opus 4.7 (1M context) --- .devenv/README.md | 109 ++++ .devenv/scripts/merge-mcp-config.py | 113 ++++ .devenv/shared/claude-code.json | 8 + .devenv/shared/codex.toml | 8 + .devenv/shared/opencode.json | 9 + .devenv/shared/vscode.json | 9 + .devenv/templates/claude-code.json | 12 + .devenv/templates/codex.toml | 10 + .devenv/templates/opencode.json | 14 + .devenv/templates/vscode.json | 12 + .gitignore | 3 + .serena/memories/backend/core.md | 1 + .serena/memories/critical-info.md | 2 +- .serena/memories/devenv/core.md | 24 +- .serena/memories/prod-infra/core.md | 33 ++ docker/devenv/defaults.env | 16 +- .../developer/agentic-devenv.md | 175 ++++-- docs/technical-guide/developer/devenv.md | 78 ++- manage.sh | 546 ++++++++++++++---- 19 files changed, 995 insertions(+), 187 deletions(-) create mode 100644 .devenv/README.md create mode 100755 .devenv/scripts/merge-mcp-config.py create mode 100644 .devenv/shared/claude-code.json create mode 100644 .devenv/shared/codex.toml create mode 100644 .devenv/shared/opencode.json create mode 100644 .devenv/shared/vscode.json create mode 100644 .devenv/templates/claude-code.json create mode 100644 .devenv/templates/codex.toml create mode 100644 .devenv/templates/opencode.json create mode 100644 .devenv/templates/vscode.json create mode 100644 .serena/memories/prod-infra/core.md diff --git a/.devenv/README.md b/.devenv/README.md new file mode 100644 index 0000000000..7a9656919d --- /dev/null +++ b/.devenv/README.md @@ -0,0 +1,109 @@ +# `.devenv/` — Per-Workspace AI-Client MCP Configs + +This directory carries the pieces needed to point an AI coding agent +(currently Claude Code, opencode, VS Code Copilot, and the OpenAI Codex CLI) +at the MCP servers running inside the parallel devenv instance the developer +is currently working in. Every parallel workspace (`ws0`, `ws1`, …) has its +own copy because the Penpot MCP and Serena MCP host ports are +workspace-specific. + +## Layout + +``` +.devenv/ + README.md + scripts/ + merge-mcp-config.py # generator helper invoked by manage.sh + shared/ # committed; workspace-independent entries + claude-code.json # Playwright — same for every workspace + opencode.json + vscode.json + codex.toml + templates/ # committed; entries with ${...} port placeholders + claude-code.json # Penpot MCP, Serena MCP — port is the only diff + opencode.json + vscode.json + codex.toml + mcp/ # gitignored; written by manage.sh per workspace + claude-code.json # loaded via Claude Code's --mcp-config flag + opencode.json # loaded via OPENCODE_CONFIG env var +``` + +Two more generated files live outside `.devenv/`, in the directories the +clients themselves auto-discover (both gitignored): + +``` +.vscode/mcp.json # auto-loaded by GitHub Copilot in VS Code +.codex/config.toml # auto-loaded by Codex CLI; "trusted project" required +``` + +* **`shared/`** holds MCP entries that don't depend on the workspace — the + browser-driving Playwright server today, plus any other workspace-independent + servers we add later. Same content in every workspace, so it's a static + checked-in file. +* **`templates/`** holds the workspace-specific entries (Penpot MCP, Serena + MCP) with `${PENPOT_MCP_PORT}` and `${SERENA_MCP_PORT}` placeholders. The + placeholders are resolved per-workspace from the port-base constants in + `manage.sh`. +* **`mcp/`** plus the two tool-expected paths (`.vscode/mcp.json`, + `.codex/config.toml`) are the result of merging `shared/` with the + port-substituted `templates/`. `manage.sh` writes them on every + `run-devenv-agentic` pass. Gitignored — never edit by hand, your edits will + be overwritten on the next reconcile. +* **`scripts/merge-mcp-config.py`** is the generator that does the merge. + `manage.sh`'s `_merge-mcp-config-{json,toml}` helpers are thin shims over + it. Run `python3 .devenv/scripts/merge-mcp-config.py --help` for the CLI; + edit the script if you need to change merge semantics, add a new format, + or support a new template shape. + +## Launching a coding agent + +The easiest path is the wrapper command, which knows the right flags per +client, `cd`'s into the target workspace, and refuses to launch unless the +target instance is running and its MCP config has been generated: + +```bash +# Default target is ws0 (the live repo). +./manage.sh start-coding-agent claude [...args to forward] +./manage.sh start-coding-agent opencode [...args to forward] +./manage.sh start-coding-agent vscode [...args to forward to 'code'] +./manage.sh start-coding-agent codex [...args to forward] + +# Target a parallel workspace with --ws N. N is an integer (non-negative); +# 'main', 'ws1' and similar spellings are rejected. +./manage.sh start-coding-agent claude --ws 1 +./manage.sh start-coding-agent opencode --ws 2 +``` + +Equivalents by hand (run from inside the workspace directory): + +```bash +claude --mcp-config .devenv/mcp/claude-code.json +OPENCODE_CONFIG=.devenv/mcp/opencode.json opencode +code "$PWD" # VS Code auto-discovers .vscode/mcp.json +codex # Codex auto-discovers .codex/config.toml +``` + +The first `codex` invocation in a workspace will prompt you to **trust the +project** — Codex only loads `.codex/config.toml` from trusted projects. + +## Overriding our entries + +Both the auto-discovered configs and the launcher-loaded configs sit *on top +of* the developer's global config (with varying precedence rules). All four +clients offer escape hatches for shadowing entries we ship: + +* **Claude Code** — `claude mcp add --scope local …` installs a private entry + that overrides the one in `mcp/claude-code.json`. Local scope wins. +* **opencode** — drop an `opencode.json` at the repo root with the override + entries you need. opencode's precedence chain is *global → `OPENCODE_CONFIG` + → project*, so the project file always wins. The root `opencode.json` is + gitignored on purpose, since these overrides are personal. +* **VS Code Copilot** — VS Code's user-profile MCP config and `.vscode/mcp.json` + are both loaded; same-name entries can be shadowed in the user profile. +* **Codex CLI** — entries in `~/.codex/config.toml` override the project file + for the same `[mcp_servers.]` table. + +See `docs/technical-guide/developer/agentic-devenv.md` for the broader +client-configuration story (browser remote debugging, AI-client config +schemas, manual setup for unsupported clients). diff --git a/.devenv/scripts/merge-mcp-config.py b/.devenv/scripts/merge-mcp-config.py new file mode 100755 index 0000000000..a204e271ef --- /dev/null +++ b/.devenv/scripts/merge-mcp-config.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Merge a shared MCP-server config with a port-substituted template into one +output file for one AI coding-agent client. + +Invoked per workspace by manage.sh's `write-instance-mcp-configs`. Each +supported client (Claude Code, opencode, VS Code Copilot, OpenAI Codex CLI) +ships a `.devenv/shared/.{json,toml}` (workspace-independent entries, +e.g. Playwright) and a `.devenv/templates/.{json,toml}` (per-workspace +entries with `${PENPOT_MCP_PORT}` / `${SERENA_MCP_PORT}` placeholders). This +script combines the two into the final config that the client loads. + +Two formats are supported: + + json Deep-merge two JSON documents under a configurable top-level key + (`mcpServers` for Claude Code, `mcp` for opencode, `servers` for VS + Code Copilot). Same-name entries in the template override entries + in shared. + toml Concatenate two TOML chunks. Codex's `[mcp_servers.]` layout + puts every server in its own top-level table, so a textual concat + is equivalent to a structural merge -- and avoids pulling in a + third-party TOML writer. + +In both modes, `${VAR}` placeholders inside *either* chunk are resolved +from the current environment (only template chunks carry placeholders in +practice, but the substitution is uniform either way) using Python's +`os.path.expandvars`. Undefined placeholders are left as `${VAR}` literal +text -- callers (i.e. manage.sh) are responsible for exporting the +variables before invoking the script. + +Usage: + merge-mcp-config.py --format json --key