Enable a way to provide custom opencode config on starting devenv

This commit is contained in:
Andrey Antukh 2026-08-31 16:31:24 +02:00
parent fc207a1768
commit 73d3d63616
9 changed files with 276 additions and 116 deletions

View File

@ -3,11 +3,8 @@ description: Execute a ready plan end-to-end — create a GitHub issue, branch i
agent: build
---
# Implement Plan
This command is run once a plan is ready (for example, from plan mode). Execute
the plan already prepared in the current session context — it does not take
extra arguments. Follow these steps in order.
the plan already prepared in the current session context. Follow these steps in order.
## 1. Create the issue

View File

@ -1,25 +0,0 @@
Act as a senior software engineer and perform a thorough review.
## Instructions
1. **Determine what is being reviewed** from the provided context:
- **If it is a plan** (implementation plan, design document, task breakdown) → load the **`plan-review`** skill.
- **If it is code** (diff, PR, code change) → load the **`code-review`** skill.
2. Read `AGENTS.md` and follow its instructions for finding and reading all related testing documentation from memories before reviewing.
3. **Skip generated files, lockfile-only changes, and unrelated modifications** unless they introduce security risks.
4. Follow the loaded skill's process and produce its output format.
## Strong Rules
1. Do not invent problems. Every finding must be real and actionable.
2. Do not modify any code and do not create a commit — this command only reviews.
3. Be specific and constructive. "This could be better" is not helpful — explain why and how.
4. Prioritize by impact. One structural issue outweighs ten nits.
5. Missing tests are an issue, not a suggestion. Report as a severity-tagged finding — never as a recommendation.
## Context
$ARGUMENTS

View File

@ -106,6 +106,8 @@ For detailed security guidance, see `security-and-hardening`.
| **Low:** | Minor, optional | Author may ignore — formatting, style preferences |
| **Suggestion:** | Worth considering | Not required, but improves the code |
**Unique finding IDs.** Assign every finding a stable identifier: `F1`, `F2`, `F3`, … numbered in order of severity (Critical first, then High, Medium, Low, Suggestion). Use the ID everywhere the finding is mentioned — in section headers, in the verdict, in follow-up discussion. Never renumber within a review. Example: `**F3 (High)** — `app/validate.cljs:42` — duplicate branch logic…`.
For each finding, describe the circumstances under which it could fail: specific inputs, load conditions, timing, or user actions that trigger the problem. "This crashes when input is null" is actionable; "this might crash" is not.
Lead with what matters: correctness and security first, then structural issues, then everything else. A few high-conviction comments beat a long list.
@ -122,11 +124,11 @@ Briefly explain what the code does and give an overall assessment.
### Critical and High-Priority Issues
List problems that could cause security incidents, data loss, crashes, incorrect behavior, or major performance degradation. For each: state the severity, identify the file/function/code section, explain why it's a problem, describe failure circumstances, and provide a concrete improvement with corrected code when useful.
List problems that could cause security incidents, data loss, crashes, incorrect behavior, or major performance degradation. Each finding gets its unique ID (`F1`, `F2`, …). For each: state the severity, identify the file/function/code section, explain why it's a problem, describe failure circumstances, and provide a concrete improvement with corrected code when useful.
### Other Findings
List medium- and low-priority issues, including maintainability and design concerns.
List medium- and low-priority issues, including maintainability and design concerns. Continue the ID sequence started above (`F3`, `F4`, …).
### Suggested Refactoring
@ -148,6 +150,8 @@ Choose one:
- **Approve with minor changes** — Good to merge after addressing low/medium issues
- **Request changes** — Critical or high issues must be resolved before merge
List the finding IDs the verdict depends on (e.g. "Request changes: F1, F4").
## Change Sizing
Small, focused changes are easier to review, faster to merge, and safer to deploy.
@ -231,25 +235,13 @@ For supply-chain risk triage, follow the `security-and-hardening` skill.
## Verification
After review is complete:
Before emitting the verdict, verify the change as it stands. This is the reviewer's own due diligence — it covers the state of the code at review time, not the later resolution of findings (fixing findings is the author's job; confirming them is a new review):
- [ ] All Critical issues are resolved
- [ ] All Required (no-prefix) changes are resolved or explicitly deferred with justification
- [ ] Tests pass
- [ ] Tests pass — run them yourself, don't trust the claim
- [ ] Build succeeds
- [ ] The verification story is documented (what changed, how it was verified)
- [ ] Dependency upgrades reviewed against changelog, isolated per package, verified by green suite
## Multi-Model Review Pattern
Use different models for different review perspectives:
```
Model A writes the code → Model B reviews → Model A addresses feedback → Human makes the final call
```
Different models have different blind spots.
## See Also
- For detailed security review guidance, see `security-and-hardening`

View File

@ -1,13 +1,13 @@
---
name: planner
description: Read-only planning and architecture analysis for Penpot — produce a structured implementation plan (Context, Affected modules, Approach, Risks, Testing). Always output to the user; additionally save to .opencode/plans/YYYY-MM-DD-<title>.md.
description: Read-only planning and architecture analysis for Penpot — produce a structured implementation plan with task breakdown, acceptance criteria, sizing, and checkpoints. Always output to the user and save to .opencode/plans/YYYY-MM-DD-<title>.md.
---
# Planner
Read-only senior software architect role for Penpot. Produces structured
implementation plans that engineers or other agents can execute. Never writes
or modifies code.
implementation plans with task breakdowns that engineers or other agents can
execute. Never writes or modifies code.
## When to Use
@ -18,24 +18,29 @@ or modifies code.
- The user asks "how would I implement X?" or "what's involved in fixing Y?".
- The user is about to start non-trivial work and wants a bite-sized task
breakdown.
- A task feels too large or vague to start.
- Work needs to be parallelized across multiple agents or sessions.
Do **not** use this skill to actually implement anything — it is read-only.
**When NOT to use:** Single-file changes with obvious scope, or when the spec
already contains well-defined tasks.
## Role
You are a Senior Software Architect working on Penpot, an open-source design
tool. Your sole responsibility is planning and analysis — you do NOT write or
modify code.
You help users understand the Penpot codebase, design solutions, and produce
implementation plans that other agents or developers can execute. The plan
tells them what to build and how to verify it, task by task.
You help users understand the codebase, design solutions, and create detailed
implementation plans that other agents or developers can execute. Document
everything they need to know: which files to touch for each task, code patterns,
tests, and how to verify correctness. Apply DRY and KISS principles.
The implementer reads the project's agent docs (`AGENTS.md`, project memories
such as `mem:critical-info`, `mem:testing`, and each module's core memory)
before working. Reference those memories instead of re-explaining tooling,
conventions, or test design — explain in the plan only what they do not cover.
Do **not** suggest commit messages or commit names anywhere in your plans or
responses — committing is the developer's responsibility.
responses — committing is the implementer's responsibility.
## Required Reading Before Planning
## CRITICAL: Required Reading Before Planning
Before drafting any plan, work through the project's own guidance:
@ -50,6 +55,8 @@ Before drafting any plan, work through the project's own guidance:
Skipping this step is the #1 cause of incorrect or incomplete plans.
---
## The Planning Process
### Phase 1: Architecture Analysis
@ -64,16 +71,42 @@ Skipping this step is the #1 cause of incorrect or incomplete plans.
### Phase 2: Task Breakdown
Implementation order follows the monorepo's dependency graph:
`frontend -> common`, `backend -> common`, `exporter -> common`,
`frontend -> render-wasm`. Build shared foundations first, then layer
consumers on top.
#### Identify the Dependency Graph
Map what depends on what, following the monorepo's module dependency graph:
```
common (shared types, schemas — no deps)
├── backend (depends common)
│ ├── RPC handlers
│ └── persistence / migrations
├── frontend (depends common, render-wasm)
│ ├── UI components
│ └── state / API integration
├── exporter (depends common)
└── render-wasm (consumed by frontend)
```
Implementation order follows the dependency graph bottom-up: build shared
foundations first, then layer consumers on top.
#### Slice Vertically
Instead of building all of common, then all of backend, then all of frontend —
build one complete feature path at a time:
**Bad (horizontal slicing):**
```
Task 1: Build all common types
Task 2: Build all backend handlers
Task 3: Build all frontend components
```
**Good (vertical slicing):**
```
Task 1: common data types + schema ← foundation
Task 2: backend RPC handler + persistence
@ -89,39 +122,58 @@ Each task follows this structure:
```markdown
## Task [N]: [Short descriptive title]
**Description:** One paragraph explaining what this task accomplishes.
**Description:** One or two paragraphs explaining what this task accomplishes.
Should be clear and concise.
**Rationale:** Why this task exists and why this approach over the obvious
alternatives — design decisions, trade-offs, constraints discovered during
analysis. One or two sentences; skip only if genuinely trivial.
**Code sketch (optional):** Signature-, type-, or shape-level example when the
intended interface is non-obvious. Keep it short — a skeleton that fixes the
contract (function signature, model fields, error shape), never a full
implementation. Omit when the task is mechanical.
**Acceptance criteria:**
- [ ] [Specific, testable condition]
- [ ] [Specific, testable condition]
**Verification:**
- [ ] Tests pass (module-specific test command)
- [ ] Lint/formatter passes (module-specific check command)
- [ ] Relevant tests pass (module-specific test command).
- [ ] Lint/formatter passes (module-specific check command), if applicable.
- [ ] The core flow works end-to-end, if applicable.
**Dependencies:** [Task numbers this depends on, or "None"]
**Files likely touched:**
- `path/to/file.clj`
- `path/to/file_test.clj`
**Estimated scope:** [XS: 1 file | S: 1-2 files | M: 3-5 files | L: 5+ files]
```
Replace "module-specific test command" with the actual commands for the module
(e.g. `clojure -M:dev:test` for backend/common, `npx shadow-cljs compile test && npx karma start` for frontend,
or the commands noted in the module's core memory).
(e.g. `clojure -M:dev:test` for backend/common,
`npx shadow-cljs compile test && npx karma start` for frontend, or the
commands noted in the module's core memory).
When possible, design each task with TDD in mind: acceptance criteria double
as a test list, and the natural first step of the task is writing those tests
before the implementation. Some tasks resist this (config, migrations, pure
wiring) — for those, keep the usual verification steps.
#### Estimate Scope
| Size | Files | Scope |
|------|-------|-------|
| **XS** | 1 | Single function, config change, or schema tweak |
| **S** | 1-2 | One handler or component method |
| **M** | 3-5 | One vertical feature slice |
| **L** | 5-8 | Multi-component feature |
| **XL** | 8+ | **Too large — break it down further** |
| Size | Files | Scope | Example |
|------|-------|-------|---------|
| **XS** | 1 | Single function, config change, or schema tweak | Add a validation rule |
| **S** | 1-2 | One handler or component method | Add a new RPC endpoint |
| **M** | 3-5 | One vertical feature slice | Bookmark CRUD with tests |
| **L** | 5-8 | Multi-component feature | Search with filtering and pagination |
| **XL** | 8+ | **Too large — break it down further** | — |
If a task is L or larger, break it into smaller tasks. Agents perform best on
S and M tasks.
If a task is XL, it should be broken into smaller tasks. Agents perform best
on S and M tasks.
**When to break a task down further:**
- It would take more than one focused session
@ -141,11 +193,11 @@ Arrange tasks so that:
Add explicit checkpoints with the relevant module commands:
```markdown
## Checkpoint: After Tasks 1-3
- [ ] All tests pass (module-specific command)
- [ ] Lint/format passes (module-specific command)
- [ ] Core flow works end-to-end
- [ ] Review with human before proceeding
### Checkpoint: After Tasks 1-3
- [ ] Relevant tests pass (module-specific command).
- [ ] The relevant build or compilation passes, if applicable.
- [ ] The core flow works end-to-end.
- [ ] Review with human before proceeding.
```
## Requirements
@ -159,7 +211,7 @@ Add explicit checkpoints with the relevant module commands:
- Apply DRY and KISS principles to the proposed implementation.
- Define a testing strategy aligned with each affected module's tooling.
- Every task must have acceptance criteria and verification steps.
- Checkpoints must exist between major phases.
- Checkpoints must exist after every 2-3 tasks.
## Constraints
@ -168,7 +220,8 @@ Add explicit checkpoints with the relevant module commands:
`.opencode/plans/`.
- You do **not** run builds, tests, linters, or any commands that modify state.
- You do **not** create git commits or interact with version control.
- You do **not** execute shell commands beyond read-only searches.
- You do **not** execute shell commands beyond read-only searches (`rg`, `ls`,
`find`, `cat`, `bat`).
- Your output is a structured plan or analysis, ready for handoff to an
engineer agent or developer.
@ -188,8 +241,9 @@ slug is lowercase, hyphen-separated, and a short summary of the task
(e.g. `add-batch-get-profiles-for-file-comments`). Create the
`.opencode/plans/` directory if it does not exist.
Always attempt the write. If the user explicitly provides a target file path,
use that path instead of the default.
IMPORTANT: The plan agent has write permission specifically for
`.opencode/plans/` — always attempt the write. If the user explicitly provides
a target file path, use that path instead of the default.
### Plan Document Template
@ -212,41 +266,75 @@ use that path instead of the default.
security implications.]
## Approach
[Step-by-step implementation plan with file paths, function names, and code
shape where applicable. Group steps into atomic, ordered tasks.]
[A short strategy summary: 3-5 sentences describing the overall approach and
the shape of the dependency graph (what depends on what, what gets built
first). High-level only — the task-by-task detail lives in the Task List.]
## Task List
### Phase 1: Foundation
- [ ] Task 1: ...
- [ ] Task 2: ...
Each task uses the full task structure defined in
[Write Tasks](#write-tasks) — description, rationale, acceptance criteria,
verification, dependencies, files, estimated scope, and optional code sketch.
Never reduce a task to a one-line checkbox; the plan must be self-contained
and executable without other context.
### Checkpoint: Phase 1
- [ ] Tests pass, lint/formatter clean (module-specific commands)
Tasks are a flat, ordered list — a plan is not a roadmap. Do not group tasks
into phases, milestones, or sprints; ordering and dependencies are already
captured per task. Insert a checkpoint after every 2-3 tasks.
### Phase 2: Core Features
- [ ] Task 3: ...
- [ ] Task 4: ...
## Task 1: [Short descriptive title]
### Checkpoint: Phase 2
- [ ] End-to-end flow works
**Description:** [What this task accomplishes.]
### Phase 3: Polish
- [ ] Task 5: ...
- [ ] Task 6: ...
**Rationale:** [Why this approach over the alternatives.]
### Checkpoint: Complete
- [ ] All acceptance criteria met
- [ ] Ready for review
**Acceptance criteria:**
- [ ] [Specific, testable condition]
## Testing Strategy
[How to verify: which test commands to run per module, what cases to cover,
manual verification steps, lint/format checks. Consult each module's core
memory for the exact commands.]
**Verification:**
- [ ] Relevant tests pass (module-specific command).
**Dependencies:** None
**Files likely touched:**
- `path/to/file`
**Estimated scope:** [XS: 1 file | S: 1-2 files | M: 3-5 files | L: 5+ files]
**Code sketch (optional):** [Short contract-level example, only if the shape
is non-obvious.]
## Task 2: [Short descriptive title]
[Same structure as Task 1.]
## Task 3: [Short descriptive title]
[Same structure as Task 1.]
### Checkpoint: After Tasks 1-3
- [ ] Relevant tests pass (module-specific command).
- [ ] The relevant build or compilation passes, if applicable.
- [ ] The core flow works end-to-end.
- [ ] Review with human before proceeding.
## Task 4: [Short descriptive title]
[Same structure as Task 1.]
## Task 5: [Short descriptive title]
[Same structure as Task 1.]
## Verification & Testing
[How to verify each task and the whole plan: the project's real test, lint,
build, and run commands (extracted during Required Reading), coverage
expectations, and manual checks. Consult each module's core memory for the
exact commands.]
## Parallelization Opportunities
- **Safe to parallelize:** Independent feature slices across separate
modules, tests for already-implemented features
modules, tests for already-implemented features, documentation
- **Must be sequential:** Shared common schema changes, database migrations
- **Needs coordination:** Features that share a contract (define the contract
first, then parallelize)
@ -259,13 +347,31 @@ When the plan is purely analytical (e.g. a code review or feasibility study
with no implementation), skip the **Approach** and **Task List** sections and
lead with **Findings** instead, keeping the rest of the structure.
## Common Rationalizations
| Rationalization | Reality |
|---|---|
| "I'll figure it out as I go" | That's how you end up with a tangled mess and rework. 10 minutes of planning saves hours. |
| "The tasks are obvious" | Write them down anyway. Explicit tasks surface hidden dependencies and forgotten edge cases. |
| "Planning is overhead" | Planning is the task. Implementation without a plan is just typing. |
| "I can hold it all in my head" | Context windows are finite. Written plans survive session boundaries and compaction. |
## Red Flags
- Delivering prose without a task breakdown
- Tasks that say "implement the feature" without acceptance criteria
- No verification steps in the plan
- All tasks are XL-sized
- No checkpoints between tasks
- Dependency order isn't considered
## Verification Checklist
Before starting implementation, confirm:
Before delivering the plan, confirm:
- [ ] Every task has acceptance criteria
- [ ] Every task has a verification step
- [ ] Task dependencies are identified and ordered correctly
- [ ] No task touches more than ~5 files
- [ ] Checkpoints exist between major phases
- [ ] The human has reviewed and approved the plan
- [ ] No task is XL or larger — break it down instead
- [ ] Checkpoints exist after every 2-3 tasks
- [ ] The plan is ready for human review

View File

@ -6,6 +6,7 @@ Compose-based dev environment under `docker/devenv/`, driven by `manage.sh`. Par
- `penpotdev-infra`: shared `postgres`, `minio`, `minio-setup`, `mailer`, `ldap`. File: `docker-compose.infra.yml`.
- `penpotdev-wsN` (N=0,1,…): per-instance `main` + `redis` (Valkey). File: `docker-compose.main.yml`. ws0 (a.k.a. `main`) binds `$PWD`; ws1+ bind clones at `${PENPOT_WORKSPACES_DIR}/wsN/` (default `~/.penpot/penpot_workspaces/`), maintained by the developer.
- Optional overlay `docker-compose.opencode.yml`: added by `instance-compose` as an extra `-f` only when `PENPOT_OPENCODE_CONFIG_DIR` is set (i.e. `run-devenv --opencode-config-dir DIR` ran in this process). Bind-mounts the host dir at `/home/penpot/.config/opencode` (`:z`). Flag-only, per-call; not read from ambient env. Parser `parse-opencode-config-dir` absolutizes (`~`, realpath) because compose resolves relative bind sources against the compose file's dir. Only instances brought up with the flag get the mount.
- All projects join external network `penpot_shared`. Created idempotently by `ensure-devenv-network`, never removed by lifecycle commands.
## Source-of-truth files
@ -65,7 +66,7 @@ No `--delete` on the working-tree pass: gitignored caches in the workspace survi
## CLI surface
- `run-devenv --agentic [--ws main|0|wsN|N] [--sync] [--serena-context CTX]`: bring one instance up. Agentic only — MCP and Serena windows are always created. Default target main. Errors out if the target is already running. `--sync` is rejected on main; on ws1+ it's optional (forced only when the workspace dir does not exist yet).
- `run-devenv --agentic [--ws main|0|wsN|N] [--sync] [--serena-context CTX] [--opencode-config-dir DIR]`: bring one instance up. Agentic only — MCP and Serena windows are always created. Default target main. Errors out if the target is already running. `--sync` is rejected on main; on ws1+ it's optional (forced only when the workspace dir does not exist yet). `--opencode-config-dir DIR` bind-mounts DIR at `~/.config/opencode` in-container via the optional overlay above; mount applies at container creation, so changing it requires stop + re-run.
- `stop-devenv [--ws main|0|wsN|N] [--all]`: stop instances. Flags mutually exclusive. `--ws N` stops just that workspace. `--ws 0` or no flag stops ws0; shared infra shuts down only if no other instances remain. `--all` stops every ws highest-first then ws0, then infra.
- `run-devenv`: legacy alias, ws0 non-agentic attached.
- `attach-devenv [--ws main|0|wsN|N]`: pure attach. Fails fast if instance/session missing.

View File

@ -0,0 +1,10 @@
# Optional compose overlay, included by manage.sh's instance-compose ONLY
# when PENPOT_OPENCODE_CONFIG_DIR is set (run-devenv --opencode-config-dir
# DIR). Bind-mounts a host directory over the container's opencode global
# config dir (~/.config/opencode) so personal agents/prompts/skills kept in
# a separate repo are available inside the devenv without committing them
# here. Without the flag this file is never referenced.
services:
main:
volumes:
- "${PENPOT_OPENCODE_CONFIG_DIR}:/home/penpot/.config/opencode:z"

View File

@ -148,10 +148,15 @@ automatically, so regular users never run this.
```bash
./manage.sh run-devenv --agentic \
[--ws N] [--sync] [--serena-context CTX] \
[--opencode-config-dir DIR] \
[--git-user-name NAME] [--git-user-email EMAIL]
```
Brings one agentic instance up. Errors out if the target is already running.
`--opencode-config-dir DIR` bind-mounts DIR over the container's
`~/.config/opencode` so personal agents/prompts/skills kept in a separate
repository are available to the coding agent; see the
[Dev environment guide](./devenv.md#personal-opencode-config-inside-the-container).
`--ws N` (N ≥ 1) brings that workspace up independently — workspaces can be
started and stopped in any order. Per-instance ports

View File

@ -139,6 +139,27 @@ until you set an identity. The values are applied every time
`run-devenv` brings an instance up (idempotent), so re-running
with different flags is the way to change the in-container identity.
### Personal opencode config inside the container
`run-devenv --opencode-config-dir DIR` bind-mounts a host directory over the
container's `~/.config/opencode` (opencode's global config dir). This is how
you keep personal agents, prompts, and skills in a separate repository and
use them inside the devenv without committing them here or leaving untracked
files in the repo:
```bash
./manage.sh run-devenv --agentic --opencode-config-dir ../penpot-opencode
```
The path must be an existing directory; `~` is expanded and the value is
resolved to an absolute path automatically. The mount is applied at container
creation, so changing it requires stopping and re-running `run-devenv` for
that instance, and it applies only to instances brought up with the flag —
other workspaces mount nothing. The directory is shared read-write with the
container (same UID mapping as the source tree). Opencode's own state
(sessions, `auth.json`) lives in `~/.local/share/opencode`, which stays in
the container's data volume regardless of this flag.
### Shared state and workers
All instances share one Penpot database and one MinIO bucket; users, teams,

View File

@ -223,8 +223,10 @@ function ensure-devenv-network {
# those stale values would leak into substitution. And because Docker Compose
# gives shell-env precedence over --env-file, the re-injected per-instance
# overrides cleanly override the defaults.env baseline. Re-injected: HOME/PATH
# (tooling), CURRENT_USER_ID/PENPOT_SOURCE_PATH (always per-call), and the
# instance-env-overrides block.
# (tooling), CURRENT_USER_ID/PENPOT_SOURCE_PATH (always per-call), the
# optional PENPOT_OPENCODE_CONFIG_DIR (set only by run-devenv's
# --opencode-config-dir within this process), and the instance-env-overrides
# block.
function infra-compose {
env -i HOME="$HOME" PATH="$PATH" PWD="$PWD" \
docker compose -p penpotdev-infra \
@ -245,14 +247,26 @@ function instance-compose {
# Per-instance overrides apply to all workspaces uniformly.
mapfile -t overrides < <(instance-env-overrides "$instance")
# Optional personal-opencode-config overlay: the extra -f and variable
# are only present when run-devenv --opencode-config-dir resolved a host
# directory into PENPOT_OPENCODE_CONFIG_DIR; when unset neither the file
# nor the variable is referenced, so default behaviour is unchanged.
local -a compose_files=(-f docker/devenv/docker-compose.main.yml)
local -a opencode_env=()
if [[ -n "${PENPOT_OPENCODE_CONFIG_DIR:-}" ]]; then
compose_files+=(-f docker/devenv/docker-compose.opencode.yml)
opencode_env=("PENPOT_OPENCODE_CONFIG_DIR=${PENPOT_OPENCODE_CONFIG_DIR}")
fi
env -i HOME="$HOME" PATH="$PATH" PWD="$PWD" \
CURRENT_USER_ID="${CURRENT_USER_ID:-$(id -u)}" \
PENPOT_SOURCE_PATH="$source_path" \
DEVENV_TAG="$DEVENV_TAG" \
"${opencode_env[@]}" \
"${overrides[@]}" \
docker compose -p "penpotdev-${instance}" \
--env-file "$DEVENV_DEFAULTS_FILE" \
-f docker/devenv/docker-compose.main.yml \
"${compose_files[@]}" \
"$@"
}
@ -658,6 +672,26 @@ function parse-ws-integer {
echo "ws$raw"
}
# Strict parser for --opencode-config-dir. Resolves the value to an absolute
# host directory (docker compose resolves relative bind-mount sources against
# the compose file's directory, not $PWD, so relative values would be
# misinterpreted) and verifies it exists. Echoes the absolute path; anything
# else fails fast.
function parse-opencode-config-dir {
local raw="$1"
if [[ -z "$raw" ]]; then
echo "Invalid --opencode-config-dir: value is empty." >&2
return 1
fi
raw="${raw/#\~/$HOME}"
local abs
if ! abs=$(realpath -e "$raw" 2>/dev/null) || [[ ! -d "$abs" ]]; then
echo "Invalid --opencode-config-dir: '$raw' is not an existing directory." >&2
return 1
fi
echo "$abs"
}
# Bring a single instance up: compose up + detached tmux start. When agentic
# is true (the default) the tmux session gets MCP + Serena enabled; when false
@ -764,6 +798,7 @@ function run-devenv {
local serena_context="desktop-app"
local git_user_name=""
local git_user_email=""
local opencode_config_dir=""
local -a extra_env_args=()
while [[ $# -gt 0 ]]; do
@ -778,6 +813,8 @@ function run-devenv {
do_attach=true; shift;;
--serena-context)
serena_context="$2"; shift 2;;
--opencode-config-dir)
opencode_config_dir="$(parse-opencode-config-dir "$2")" || return 1; shift 2;;
--git-user-name)
git_user_name="$2"; shift 2;;
--git-user-email)
@ -787,13 +824,16 @@ function run-devenv {
-e*)
extra_env_args+=(-e "${1#-e}"); shift;;
-h|--help)
echo "Usage: run-devenv [--ws N] [--sync] [--attach] [--agentic] [--serena-context CTX] [--git-user-name NAME] [--git-user-email EMAIL] [-e KEY=VAL]"
echo "Usage: run-devenv [--ws N] [--sync] [--attach] [--agentic] [--serena-context CTX] [--opencode-config-dir DIR] [--git-user-name NAME] [--git-user-email EMAIL] [-e KEY=VAL]"
echo " Bring a single workspace up."
echo " --ws N target workspace (default: 0)."
echo " --sync re-seed the wsN clone from the live repo (forbidden on ws0)."
echo " --attach attach to the tmux session after startup."
echo " --agentic enable MCP + Serena (AI-agent mode)."
echo " --serena-context CTX context passed to Serena (default: desktop-app)."
echo " --opencode-config-dir DIR bind-mount DIR at ~/.config/opencode inside the"
echo " container (personal agents/prompts/skills kept in a"
echo " separate repo). Applied at container creation."
echo " --git-user-name NAME git author name inside the container (default: host git config)."
echo " --git-user-email EMAIL git author email inside the container."
echo " -e KEY=VAL forward env var to docker exec on attach."
@ -863,6 +903,14 @@ function run-devenv {
write-instance-mcp-configs "$target"
fi
# Scope the personal opencode config to this process only: instance-compose
# includes the overlay compose file and the variable just when it is set,
# so instances brought up without the flag mount nothing.
if [[ -n "$opencode_config_dir" ]]; then
echo "[$target] mounting personal opencode config: $opencode_config_dir -> ~/.config/opencode"
export PENPOT_OPENCODE_CONFIG_DIR="$opencode_config_dir"
fi
echo "Starting $target..."
start-instance "$target" "$serena_context" "$git_user_name" "$git_user_email" "$agentic"
print-instance-info "$target"
@ -1338,6 +1386,11 @@ function usage {
echo " --attach attach to the tmux session after startup."
echo " --agentic enable MCP + Serena (AI-agent mode)."
echo " --serena-context CTX passed to Serena (default: desktop-app)."
echo " --opencode-config-dir DIR"
echo " bind-mount DIR over the container's"
echo " ~/.config/opencode (personal opencode"
echo " agents/prompts/skills kept outside this"
echo " repo; applied at container creation)."
echo " -e KEY=VAL forwarded to 'docker exec' on attach."
echo " --git-user-name NAME / --git-user-email EMAIL"
echo " identity wired into the container's git config"