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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cursor/rules
32 changes: 26 additions & 6 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
{
"name": "CodeCollection DevTools",
"image": "ghcr.io/runwhen-contrib/codecollection-devtools:latest",
// Pull from GHCR / uncomment if not using docker compose
// "image": "ghcr.io/runwhen-contrib/codecollection-devtools:latest",
// Local testing with Docker Compose (personal mounts via override file):
"dockerComposeFile": ["../docker-compose.yaml", "../docker-compose.override.yaml"],
"service": "devtools",
"workspaceFolder": "/workspaces",
"remoteUser": "runwhen",
"updateRemoteUserUID": false,
"overrideCommand": false,

"forwardPorts": [3000],
"portsAttributes": {
Expand All @@ -15,12 +19,20 @@

"containerEnv": {
"RW_MODE": "dev",
"ROBOT_LOG_DIR": "/robot_logs",
"GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}"
"ROBOT_LOG_DIR": "/robot_logs"
},

"postCreateCommand": "chmod 755 /home/runwhen && mkdir -p /home/runwhen/.ssh && chmod 700 /home/runwhen/.ssh && touch /home/runwhen/.ssh/authorized_keys && chmod 600 /home/runwhen/.ssh/authorized_keys && gh auth setup-git 2>/dev/null || true",
"postStartCommand": "python -m http.server --bind 0.0.0.0 --directory /robot_logs 3000 &",
"postCreateCommand": "init-ide-tools && mkdir -p /home/runwhen/.local/share/opencode && chmod 755 /home/runwhen && sudo chmod 777 /tmp && echo 'cd /workspaces/codecollection-devtools' >> ~/.bashrc && mkdir -p /home/runwhen/.ssh && chmod 700 /home/runwhen/.ssh && touch /home/runwhen/.ssh/authorized_keys && chmod 600 /home/runwhen/.ssh/authorized_keys && gh auth setup-git 2>/dev/null || true",
"postStartCommand": "nohup python -m http.server --bind 0.0.0.0 --directory /robot_logs 3000 &",

// To mount host IDE configs (e.g. ~/.opencode, ~/.claude) into the container:
// Option A — docker-compose.override.yaml (simpler, ~ expands correctly):
// services.devtools.volumes: ["~/.opencode:/home/runwhen/.opencode"]
// Option B — mounts in this file (requires env vars to be set in IDE process):
// "initializeCommand": "mkdir -p ${localEnv:HOME}/.opencode ${localEnv:HOME}/.claude",
// "mounts": [
// "source=${localEnv:HOME}/.opencode,target=/home/runwhen/.opencode,type=bind,consistency=cached"
// ]

"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
Expand Down Expand Up @@ -67,6 +79,14 @@
},
"codespaces": {
"openFiles": ["README.md"]
},
"zed": {
"extensions": [
"robocorp.robotframework-lsp",
"ms-python.python",
"ms-python.pylint",
"ms-python.black-formatter"
]
}
}
}
11 changes: 7 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ __pycache__
*robot_logs*
.python-version
.vscode/settings.json
# Generated by `task install-skills` (Cursor rules); do not commit
.cursor/rules/
# Agent rules (generated by `task install-skills`); do not commit
.agents/*.mdc

# Personal docker-compose overrides (mounts host paths)
docker-compose.override.yaml
node_modules/
package-lock.json
package.json
.scratch/*
.opencode/
AGENTS.md
.opencode/*
.omo/*
48 changes: 48 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Agent Rules

This repository provides agent-agnostic rules for AI coding assistants. All
skill files live in `.agents/` and are symlinked into agent-specific directories
at setup time.

## Directory layout

```
.agents/ # Canonical rule files (agent-agnostic)
├── *.mdc # Generated from skills/ by `task install-skills`
│ # (gitignored by root .gitignore: .agents/*.mdc)

.cursor/rules -> ../.agents # Symlink for Cursor IDE
```

## Adding a new agent

To add rules support for another IDE or AI agent:

```bash
# Create a symlink from the agent's expected rules directory to .agents/
ln -s ../.agents .your-agent/rules
```

Common agent rule directories:

| Agent / IDE | Rules path | Symlink command |
|-------------|-----------|-----------------|
| Cursor | `.cursor/rules/` | `ln -s ../.agents .cursor/rules` |
| Windsurf | `.windsurf/rules/` | `ln -s ../.agents .windsurf/rules` |
| Cline / Roo Code | `.clinerules/` | *(flat file, see below)* |

> **Note:** Some agents (like Cline) use flat files rather than directories.
> For these, concatenate the relevant `.mdc` files into the agent's expected
> format rather than symlinking.

## Source of truth

Rule files are authored as Markdown in `skills/` and installed into `.agents/`
by `task install-skills` (part of `task setup`). The `.mdc` files in `.agents/`
are generated and should not be committed.

To refresh rules after updating skills:

```bash
task install-skills
```
11 changes: 7 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,15 @@ RUN mkdir -p $ROBOT_LOG_DIR && \

COPY --chown=runwhen:0 .pylintrc.google LICENSE ro requirements.txt .
COPY --chown=runwhen:0 .devcontainer/ .devcontainer/
RUN mkdir -p auth .ssh && \
chown -R runwhen:0 ${RUNWHEN_HOME}/.devcontainer ${RUNWHEN_HOME}/auth ${RUNWHEN_HOME}/.ssh && \
chmod -R 0775 ${RUNWHEN_HOME}/ro ${RUNWHEN_HOME}/auth ${RUNWHEN_HOME}/.devcontainer && \
COPY scripts/init-ide-tools.sh /usr/local/bin/init-ide-tools
RUN chmod +x /usr/local/bin/init-ide-tools && \
mkdir -p auth .ssh .ide-tools && \
chown -R runwhen:0 ${RUNWHEN_HOME}/.devcontainer ${RUNWHEN_HOME}/auth ${RUNWHEN_HOME}/.ssh ${RUNWHEN_HOME}/.ide-tools && \
chmod -R 0775 ${RUNWHEN_HOME}/ro ${RUNWHEN_HOME}/auth ${RUNWHEN_HOME}/.devcontainer ${RUNWHEN_HOME}/.ide-tools && \
chmod 755 ${RUNWHEN_HOME} && \
chmod 700 ${RUNWHEN_HOME}/.ssh && \
chmod 777 /tmp
chmod 777 /tmp && \
ln -sf ${RUNWHEN_HOME}/ro /usr/local/bin/ro

USER runwhen
ENV USER="runwhen"
Expand Down
101 changes: 88 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
- **PR review ready** — set `PR_NUMBER` and the environment checks out the PR branch for you.
- **Multi-arch** — pre-built for both `linux/amd64` (Codespaces, CI) and `linux/arm64` (Apple Silicon).
- **Batteries included** — Robot Framework, `ro` test runner, kubectl, Helm, AWS CLI, Azure CLI, gcloud, Terraform, gh CLI, and more.
- **Works everywhere** — GitHub Codespaces, VS Code devcontainers (local), or plain `docker run`.
- **Agent-ready** — Skills ship as agent rules in `.agents/`, symlinked for Cursor and any AI coding tool.
- **Works everywhere** — GitHub Codespaces, VS Code devcontainers (local), Zed, or plain `docker run`.

## Requirements

Expand Down Expand Up @@ -104,6 +105,7 @@ These are set in the container automatically:
|----------|---------|-------------|
| `GITHUB_TOKEN` | *(injected by Codespaces)* | GitHub token for `gh` CLI auth. Codespaces provides this automatically. |
| `RW_MODE` | `dev` | Set to `dev` for local development behavior (handled by `rw-core-keywords`). |
| `RW_IDE_TOOLS` | `claude,opencode,zed` | Comma-separated tool names. Creates `~/.{tool}/` config directories at container start. Add any IDE or AI agent — no rebuild needed. |

---

Expand Down Expand Up @@ -148,12 +150,79 @@ Mount or copy credentials into the `auth/` directory:

---

## IDE & AI Agent Support

The devcontainer auto-initializes config directories for your IDE or AI coding
agent. Define the tools you use via `RW_IDE_TOOLS` — no image rebuild needed.

```bash
# Built-in defaults (always available)
RW_IDE_TOOLS=claude,opencode,zed

# Add Cursor, Windsurf, or any other tool
RW_IDE_TOOLS=claude,opencode,zed,cursor,windsurf
```

At container start, `init-ide-tools` creates `~/.{tool}/` for each entry with
correct permissions. Existing directories are left untouched.

**Supported IDEs & agents (built-in):**

| IDE / Agent | Config directory | API key env var |
|-------------|-----------------|-----------------|
| Claude Code | `~/.claude/` | `ANTHROPIC_API_KEY` |
| OpenCode | `~/.opencode/` | `OPENAI_API_KEY` |
| Zed | `~/.zed/` | — |

**Adding your own:** Set `RW_IDE_TOOLS` to include any tool name. The
container creates an empty `~/.{tool}/` directory for you.

### Mounting your host IDE configs

The container creates empty directories — to bring in your existing configs
(API keys, settings, history), mount them from your host machine.

**Option A: devcontainer.json** (works in Codespaces too)

Add a `mounts` array to `.devcontainer/devcontainer.json`. You'll also need
`initializeCommand` to make sure the source directories exist on the host
before the container starts:

```jsonc
// .devcontainer/devcontainer.json
"initializeCommand": "mkdir -p ${localEnv:HOME}/.opencode ${localEnv:HOME}/.claude",
"mounts": [
"source=${localEnv:HOME}/.opencode,target=/home/runwhen/.opencode,type=bind,consistency=cached",
"source=${localEnv:HOME}/.claude,target=/home/runwhen/.claude,type=bind,consistency=cached"
]
```

**Option B: docker-compose.override.yaml** (local devcontainer only)

Create `docker-compose.override.yaml` alongside the existing
`docker-compose.yaml`. Docker Compose merges it automatically:

```yaml
# docker-compose.override.yaml
services:
devtools:
volumes:
- ~/.opencode:/home/runwhen/.opencode
- ~/.claude:/home/runwhen/.claude
```

> **Tip for Codespaces users:** Set `RW_IDE_TOOLS` as a Codespaces secret
> to apply across all your codespaces automatically. Use Option A above
> to mount your configs — Codespaces supports `mounts` in devcontainer.json.

---

## CodeBundle authoring skills

The `skills/` directory contains platform-specific authoring guidance that is
automatically installed as [Cursor rules](https://docs.cursor.com/context/rules)
during `task setup`. These give AI assistants (and human authors) context about
generation rules, SLI patterns, and test infrastructure conventions.
The `skills/` directory contains platform-specific authoring guidance, installed
as agent rules into `.agents/` during `task setup`. A symlink at `.cursor/rules`
points to `.agents/` for Cursor IDE compatibility. To add rules for other agents,
symlink their rules directory to `.agents/` — see [AGENTS.md](AGENTS.md) for details.

| Skill | Covers |
|-------|--------|
Expand All @@ -167,9 +236,10 @@ generation rules, SLI patterns, and test infrastructure conventions.
| `test-infra-azure-devops.md` | DevOps projects, pipelines, agent pools via Terraform |
| `test-infra-cloud.md` | Shared conventions across all cloud platforms |

Skills are copied to `.cursor/rules/*.mdc` (the workspace root) at setup time. A
`.gitignore` is placed in that directory to prevent accidental commits. To
re-install after an update, run:
Skills are installed into `.agents/` (the workspace root) at setup time. A
`.gitignore` in that directory prevents accidental commits. Cursor, Windsurf, and
other agents can find rules by symlinking their rules directory to `.agents/`.
To re-install after an update, run:

```bash
task install-skills
Expand Down Expand Up @@ -211,10 +281,15 @@ codecollection-devtools/
│ └── workflows/
│ ├── build-push.yaml # CI: multi-arch build → GHCR + GCP Artifact Registry
│ └── pypi.yaml # publish rw-devtools to PyPI (deprecated)
├── skills/ # CodeBundle authoring skills (installed as Cursor rules)
│ ├── generation-rules-*.md # Platform-specific generation rule guides
│ ├── sli-authoring.md # SLI design and implementation guide
│ └── test-infra-*.md # Test infrastructure patterns per platform
├── .agents/ # Agent rules (generated from skills/ by task install-skills)
│ ├── *.mdc # Agent-agnostic rule files
│ └── .gitignore
├── skills/ # Source skill docs (installed as agent rules in .agents/)
│ ├── generation-rules-*.md
│ ├── sli-authoring.md
│ └── test-infra-*.md
├── scripts/
│ └── init-ide-tools.sh # Runtime IDE config init (driven by RW_IDE_TOOLS)
├── Taskfile.yml # task setup, task verify, task install-skills, task clean
├── Dockerfile # image definition (built by CI, not locally)
├── ro # Robot Framework test runner wrapper
Expand Down Expand Up @@ -245,7 +320,7 @@ All image builds happen in **GitHub Actions** — never locally:
```
devcontainer opens
→ pulls pre-built image from GHCR
→ workspace root is /workspaces/codecollection-devtools/ (the repo mount)
→ workspace root is /workspaces/ (the repo mount)
→ starts log HTTP server on port 3000
→ user runs: task setup REPO=org/repo PR=123
1. clones repo into /home/runwhen/codecollection/
Expand Down
17 changes: 10 additions & 7 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,26 +107,29 @@ tasks:
- mkdir -p "{{.RUNWHEN_HOME}}/auth"

install-skills:
desc: Install CodeBundle authoring skills as Cursor rules
desc: Install CodeBundle authoring skills as agent rules
cmds:
- |
if [ ! -d "{{.SKILLS_SRC}}" ]; then
echo "→ Skills directory not found, skipping."
exit 0
fi
RULES_DIR="{{.TASKFILE_DIR}}/.cursor/rules"
mkdir -p "$RULES_DIR"
AGENTS_DIR="{{.TASKFILE_DIR}}/.agents"
mkdir -p "$AGENTS_DIR"
count=0
for skill in "{{.SKILLS_SRC}}"/*.md; do
[ -f "$skill" ] || continue
base=$(basename "$skill" .md)
cp "$skill" "$RULES_DIR/${base}.mdc"
cp "$skill" "$AGENTS_DIR/${base}.mdc"
count=$((count + 1))
done
if [ ! -f "$RULES_DIR/.gitignore" ]; then
printf '# Injected by codecollection-devtools -- do not commit\n*.mdc\n' > "$RULES_DIR/.gitignore"
# Ensure Cursor symlink exists for backward compatibility
CURSOR_LINK="{{.TASKFILE_DIR}}/.cursor/rules"
if [ ! -L "$CURSOR_LINK" ]; then
rm -rf "$CURSOR_LINK"
ln -s ../.agents "$CURSOR_LINK"
fi
echo "→ Installed ${count} Cursor rules to .cursor/rules/"
echo "→ Installed ${count} agent rules to .agents/"

verify:
desc: Check that key tools are available
Expand Down
22 changes: 22 additions & 0 deletions docker-compose.override.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copy this file to docker-compose.override.yaml and adjust paths for your machine.
# Docker Compose auto-merges override files — no changes to devcontainer.json needed.
#
# docker-compose.override.yaml is gitignored, so your personal mounts stay local.

services:
devtools:
volumes:
# OpenCode config (model preferences, MCP servers, agent rules):
- ~/.opencode:/home/runwhen/.opencode
- ~/.config/opencode:/home/runwhen/.config/opencode

# OpenCode auth (API key for OpenRouter / other providers):
# Mount the single auth.json file — no database conflicts.
- ~/.local/share/opencode/auth.json:/home/runwhen/.local/share/opencode/auth.json

# Claude Code:
- ~/.claude:/home/runwhen/.claude

# Other tools (uncomment as needed):
# - ~/.cursor:/home/runwhen/.cursor
# - ~/.zed:/home/runwhen/.zed
19 changes: 12 additions & 7 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
version: '3.9'
services:
devtools:
container_name: devtools
user: runwhen
build:
context: ./
dockerfile: ./Dockerfile
# Use the upstream
image: ghcr.io/runwhen-contrib/codecollection-devtools:latest
# Build from the local Dockerfile
# build:
# context: ./
# dockerfile: ./Dockerfile
env_file:
- path: .env
required: false
environment:
- RW_MODE=dev
- RW_IDE_TOOLS=${RW_IDE_TOOLS:-claude,opencode,zed}
- ROBOT_LOG_DIR=/robot_logs
volumes:
- .:/home/runwhen
- .:/workspaces/codecollection-devtools
ports:
- 3000:3000
Loading
Loading