diff --git a/.opencode/commands/implement-plan.md b/.opencode/commands/implement-plan.md index 20eedb3fe5..8ecd1bd537 100644 --- a/.opencode/commands/implement-plan.md +++ b/.opencode/commands/implement-plan.md @@ -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 diff --git a/.opencode/commands/review.md b/.opencode/commands/review.md deleted file mode 100644 index 70bd89ac90..0000000000 --- a/.opencode/commands/review.md +++ /dev/null @@ -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 diff --git a/.opencode/skills/code-review/SKILL.md b/.opencode/skills/code-review/SKILL.md index 7f06d90b59..7fa581efa0 100644 --- a/.opencode/skills/code-review/SKILL.md +++ b/.opencode/skills/code-review/SKILL.md @@ -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` diff --git a/.opencode/skills/planner/SKILL.md b/.opencode/skills/planner/SKILL.md index d1802652e2..3598a0dc16 100644 --- a/.opencode/skills/planner/SKILL.md +++ b/.opencode/skills/planner/SKILL.md @@ -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-.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 diff --git a/.serena/memories/devenv/core.md b/.serena/memories/devenv/core.md index 0651f872fe..db46542c99 100644 --- a/.serena/memories/devenv/core.md +++ b/.serena/memories/devenv/core.md @@ -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. diff --git a/docker/devenv/docker-compose.opencode.yml b/docker/devenv/docker-compose.opencode.yml new file mode 100644 index 0000000000..1af29e3749 --- /dev/null +++ b/docker/devenv/docker-compose.opencode.yml @@ -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" diff --git a/docs/technical-guide/developer/agentic-devenv.md b/docs/technical-guide/developer/agentic-devenv.md index 55b03c1492..932f555d6f 100644 --- a/docs/technical-guide/developer/agentic-devenv.md +++ b/docs/technical-guide/developer/agentic-devenv.md @@ -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 diff --git a/docs/technical-guide/developer/devenv.md b/docs/technical-guide/developer/devenv.md index 922ed24f1d..d6cc65e89a 100644 --- a/docs/technical-guide/developer/devenv.md +++ b/docs/technical-guide/developer/devenv.md @@ -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, diff --git a/manage.sh b/manage.sh index be6cc078b1..efa4da7601 100755 --- a/manage.sh +++ b/manage.sh @@ -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"