Skip to content

Commit efe8619

Browse files
chore: release v0.5.0
2 parents 08b98a1 + c686f42 commit efe8619

306 files changed

Lines changed: 24508 additions & 8351 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ node_modules/
44
.pnpm-store/
55
.DS_Store
66
graph/
7+
.forge-tmp/
8+
.tmp/

‎AGENTS.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@ The following skills are bundled with the plugin and auto-installed to `~/.confi
2020

2121
Source files are in `skills/` directory.
2222

23+
## Bundled Prompts
24+
25+
Agent and command prompts are bundled in `src/prompts/` and auto-installed to `~/.config/opencode/forge/prompts/` on first run. User edits take precedence and are preserved; unedited bundled files are refreshed when the bundled content changes (tracked via a content-hash manifest under `~/.config/opencode/forge/manifests/`). Section-summary markers in `agents/auditor-loop-addendum.md` must match `SECTION_SUMMARY_START_MARKER`/`SECTION_SUMMARY_END_MARKER` in `src/utils/section-summary.ts`.
26+
2327
## Project Conventions
2428

2529
- All commits must be meaningful and follow conventional commit standards
2630
- No emojis in commit messages
2731
- Always check for existing patterns before adding new code
2832
- Functions should be single responsibility and reusable
2933
- Remove dead code to keep the codebase clean
34+
- `src/dashboard/app/` is the source of truth for the dashboard UI; `src/dashboard/app-bundle.ts` is an auto-generated artifact regenerated by `pnpm build` (do not hand-edit). A source-hash drift test (`test/dashboard/app-bundle.test.ts`) guards against committing stale bundles.
35+
- The dashboard app uses `solid-js/html` (buildless). Gotchas, enforced by `test/dashboard/app-dom.test.ts` (happy-dom): never use `<${Show}>`/`<${For}>` component syntax (it mis-parses closing tags — use ternary thunks + `createMemo` + `.map()`); every `html` template needs a real root element (a body that is only `${...}` emits invalid code); reactive regions must be `${() => ...}` inside an element, and the root component returns a single wrapper element. DOM tests run in the `dom` project of `vitest.workspace.ts` (happy-dom + browser resolve conditions); the rest run in the `node` project.

‎README.md‎

Lines changed: 131 additions & 231 deletions
Large diffs are not rendered by default.

‎bunfig.toml‎

Lines changed: 0 additions & 50 deletions
This file was deleted.

‎container/.dockerignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!dind-entrypoint.sh

‎container/Dockerfile‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
FROM debian:bookworm-slim
2+
3+
RUN apt-get update && apt-get install -y --no-install-recommends \
4+
ca-certificates \
5+
curl \
6+
git \
7+
jq \
8+
python3 \
9+
ripgrep \
10+
unzip \
11+
&& rm -rf /var/lib/apt/lists/*
12+
13+
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
14+
&& apt-get install -y --no-install-recommends nodejs \
15+
&& rm -rf /var/lib/apt/lists/*
16+
17+
# pnpm via corepack, pinned and pre-activated into a world-readable/writable
18+
# cache so the container can run as an arbitrary host UID without downloading
19+
# pnpm at runtime.
20+
ENV COREPACK_HOME=/opt/corepack \
21+
COREPACK_ENABLE_DOWNLOAD_PROMPT=0
22+
RUN corepack enable \
23+
&& corepack prepare pnpm@10.28.1 --activate \
24+
&& chmod -R 0777 /opt/corepack
25+
26+
# bun installed to a world-accessible location. The default installer targets
27+
# /root/.bun, which is unreadable (mode 0700) when the container runs as a
28+
# non-root host UID.
29+
ENV BUN_INSTALL=/opt/bun
30+
RUN curl -fsSL https://bun.sh/install | bash \
31+
&& ln -sf /opt/bun/bin/bun /usr/local/bin/bun \
32+
&& ln -sf /opt/bun/bin/bun /usr/local/bin/bunx \
33+
&& chmod -R a+rX /opt/bun
34+
35+
# uv installed to a world-accessible location for the same reason as bun.
36+
ENV UV_INSTALL_DIR=/usr/local/bin
37+
RUN curl -fsSL https://astral.sh/uv/install.sh | bash \
38+
&& chmod a+rx /usr/local/bin/uv /usr/local/bin/uvx
39+
40+
# The container runs as root (required by the nested Docker daemon), but HOME and every
41+
# tool cache/store are pinned to fixed, world-writable paths so the layout is stable
42+
# regardless of the executing UID.
43+
#
44+
# npm_config_store_dir pins pnpm's content-addressable store to a container-internal
45+
# path. Without it, pnpm places the store on the project filesystem (the bind-mounted
46+
# /workspace), which floods the host file watcher and is slow over the macOS bind mount.
47+
ENV HOME=/home/forge \
48+
XDG_CACHE_HOME=/home/forge/.cache \
49+
XDG_DATA_HOME=/home/forge/.local/share \
50+
PNPM_HOME=/home/forge/.local/share/pnpm \
51+
npm_config_cache=/home/forge/.npm \
52+
npm_config_store_dir=/home/forge/.local/share/pnpm/store
53+
RUN useradd -m -u 1000 forge \
54+
&& mkdir -p /home/forge/.cache /home/forge/.local/share/pnpm/store /home/forge/.npm \
55+
&& chmod -R 0777 /home/forge
56+
57+
# fallow CLI — dead-code analysis tool — installed globally with the same pnpm
58+
# setup used by the rest of the sandbox image. Binaries are linked into
59+
# /usr/local/bin so they are on PATH for arbitrary container UIDs.
60+
#
61+
# The trailing chmod is load-bearing: this global install runs as root and populates the
62+
# pnpm store (store/v10/{files,index,projects}) with root-owned 0755 dirs, AFTER the earlier
63+
# `chmod -R 0777 /home/forge`. Because the container runs as root but agent commands run as the
64+
# host UID via `docker exec --user`, that UID could not write into the root-owned store and
65+
# `pnpm install` failed with EACCES when registering the project — which previously drove the
66+
# agent to relocate the store onto the bind-mounted /workspace (huge, slow file transfers).
67+
# Re-asserting 0777 here, as the last build step that touches /home/forge, keeps the store
68+
# writable by any exec UID. Any future build step that runs pnpm as root must do the same.
69+
RUN corepack pnpm add -g fallow@2.101.0 --global-bin-dir /usr/local/bin \
70+
&& chmod -R 0777 /home/forge
71+
72+
# Docker Engine (daemon + CLI + buildx + compose) from Docker's official apt
73+
# repository. This enables Docker-in-Docker: when the container is launched with
74+
# FORGE_DIND=1 (privileged), the entrypoint starts a nested, isolated dockerd so
75+
# loops can build and run containers for end-to-end tests. iptables is required
76+
# by dockerd for container networking; debian bookworm uses the nft backend.
77+
RUN install -m0755 -d /etc/apt/keyrings \
78+
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
79+
&& chmod a+r /etc/apt/keyrings/docker.asc \
80+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list \
81+
&& apt-get update \
82+
&& apt-get install -y --no-install-recommends \
83+
docker-ce \
84+
docker-ce-cli \
85+
containerd.io \
86+
docker-buildx-plugin \
87+
docker-compose-plugin \
88+
iptables \
89+
&& rm -rf /var/lib/apt/lists/*
90+
91+
# Allow git to operate on the bind-mounted workspace regardless of owner UID.
92+
RUN git config --system --add safe.directory '*'
93+
94+
# Entrypoint conditionally boots the nested Docker daemon before handing off to
95+
# the container command. When FORGE_DIND is unset/0 it is a transparent passthrough,
96+
# so non-DinD loops pay no runtime cost.
97+
COPY dind-entrypoint.sh /usr/local/bin/dind-entrypoint.sh
98+
RUN chmod 0755 /usr/local/bin/dind-entrypoint.sh
99+
100+
WORKDIR /workspace
101+
ENTRYPOINT ["/usr/local/bin/dind-entrypoint.sh"]
102+
CMD ["sleep", "infinity"]

‎container/dind-entrypoint.sh‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/bin/sh
2+
# Forge sandbox entrypoint.
3+
#
4+
# When FORGE_DIND=1 (set by the sandbox manager for Docker-in-Docker loops, which
5+
# also launch the container as --privileged --init), this boots a nested, isolated
6+
# Docker daemon so the loop can build and run containers for end-to-end tests. The
7+
# nested daemon stores its data under /var/lib/docker, which the manager backs with
8+
# an anonymous volume so overlay2 works inside the privileged container.
9+
#
10+
# When FORGE_DIND is unset or 0 this is a transparent passthrough: the container
11+
# command runs unchanged and no daemon is started, so worktree-only and non-DinD
12+
# sandbox loops pay no cost.
13+
set -e
14+
15+
# Resolve the dockerd `--group` argument so the daemon socket is owned by the host GID's
16+
# group from creation. The container runs as root, but the agent's shell commands run as the
17+
# host UID (docker exec --user) so worktree files are host-owned; giving the socket that GID
18+
# lets the non-root user reach dockerd without a post-start chmod (which dockerd resets,
19+
# causing a race). When FORGE_HOST_GID is unset, commands run as root and need no group.
20+
resolve_socket_group_arg() {
21+
[ -n "${FORGE_HOST_GID:-}" ] || return 0
22+
group_name=$(getent group "$FORGE_HOST_GID" 2>/dev/null | cut -d: -f1)
23+
if [ -z "$group_name" ]; then
24+
groupadd -g "$FORGE_HOST_GID" forgehost 2>/dev/null || true
25+
group_name=$(getent group "$FORGE_HOST_GID" 2>/dev/null | cut -d: -f1)
26+
fi
27+
[ -n "$group_name" ] && printf -- '--group %s' "$group_name"
28+
}
29+
30+
# Guarantee the in-container exec user can reach the daemon socket. Agent shell commands run as
31+
# the host UID:GID via `docker exec --user` (not root), so the socket must be group-accessible to
32+
# that GID. dockerd is started with `--group` when FORGE_HOST_GID is known, but this runs only
33+
# AFTER the daemon is confirmed ready, so it is race-free (the startup race the `--group` arg
34+
# avoids is doing this while dockerd is still applying socket perms). It self-heals cases where the
35+
# group did not take, and falls back to a world-accessible socket when the GID is unknown — safe
36+
# because the container is per-loop, isolated, and already --privileged, so anyone who can exec in
37+
# is already root-capable.
38+
ensure_socket_access() {
39+
sock=/var/run/docker.sock
40+
[ -S "$sock" ] || return 0
41+
if [ -n "${FORGE_HOST_GID:-}" ] \
42+
&& chgrp "$FORGE_HOST_GID" "$sock" 2>/dev/null \
43+
&& chmod g+rw "$sock" 2>/dev/null; then
44+
echo "forge-dind: docker socket group set to GID $FORGE_HOST_GID" >&2
45+
return 0
46+
fi
47+
# GID unknown or chgrp failed: make the socket reachable by any exec UID/GID.
48+
chmod 666 "$sock" 2>/dev/null || true
49+
echo "forge-dind: docker socket made world-accessible (no usable FORGE_HOST_GID)" >&2
50+
}
51+
52+
start_dockerd() {
53+
echo "forge-dind: starting nested Docker daemon" >&2
54+
group_arg=$(resolve_socket_group_arg)
55+
# Run detached; logs go to a file so failures are inspectable via docker exec.
56+
# shellcheck disable=SC2086
57+
dockerd $group_arg >/var/log/dockerd.log 2>&1 &
58+
59+
# Wait for the daemon to accept API calls. Bounded so a broken daemon does not
60+
# hang the loop indefinitely; the loop's own tooling can still retry afterwards.
61+
tries=0
62+
until docker version >/dev/null 2>&1; do
63+
tries=$((tries + 1))
64+
if [ "$tries" -ge 60 ]; then
65+
echo "forge-dind: dockerd did not become ready within 60s" >&2
66+
tail -n 50 /var/log/dockerd.log >&2 2>/dev/null || true
67+
return 0
68+
fi
69+
sleep 1
70+
done
71+
echo "forge-dind: nested Docker daemon ready" >&2
72+
ensure_socket_access
73+
}
74+
75+
if [ "${FORGE_DIND:-0}" = "1" ]; then
76+
start_dockerd
77+
fi
78+
79+
exec "$@"

‎docs/agents-and-commands.md‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Agents and Slash Commands
2+
3+
Forge installs agent definitions and slash commands through the server plugin config hook.
4+
5+
See also: [Tools](tools.md), [Configuration](configuration.md), [Loop System](loop-system.md).
6+
7+
## Agents
8+
9+
| Agent | Mode | Description |
10+
|---|---|---|
11+
| `code` | `all` | Primary implementation agent. |
12+
| `architect` | `primary` | Read-only planning agent. Produces marked plans for approval and execution. |
13+
| `auditor` | `subagent` | Read-only code review agent for convention-aware reviews. |
14+
| `auditor-loop` | `primary`, hidden | Internal auditor used by loop audit sessions. |
15+
16+
Source: [`src/agents/index.ts`](../src/agents/index.ts), [`src/agents/auditor.ts`](../src/agents/auditor.ts).
17+
18+
## Auditor restrictions
19+
20+
The auditor agents are read-only. They cannot use file-modifying tools or loop-management tools.
21+
22+
Excluded tools:
23+
24+
- `apply_patch`
25+
- `edit`
26+
- `write`
27+
- `multiedit`
28+
- `plan`
29+
- `plan_exit`
30+
- `execute-plan`
31+
- `loop-cancel`
32+
- `loop-status`
33+
34+
Source: [`AUDITOR_TOOL_EXCLUDES`](../src/agents/auditor.ts).
35+
36+
## Slash Commands
37+
38+
| Command | Description | Agent | Subtask |
39+
|---|---|---|---|
40+
| `/review` | Run a code review. | `auditor` | yes |
41+
| `/review-plan` | Review a completed implementation against its original plan. | `auditor` | yes |
42+
| `/execute-plan` | Start an iterative development loop in a worktree (or launch the plan in a fresh standalone session with `mode: new-session`). | `code` | no |
43+
| `/loop-status` | Check status of all active loops. | `code` | no |
44+
| `/loop-cancel` | Cancel the active loop. | `code` | no |
45+
46+
Source: [`buildPluginCommands()`](../src/config.ts).

0 commit comments

Comments
 (0)