gruntcode is a soft-fork of anomalyco/opencode maintained by grunt-it. It exists to layer hivemind-native autonomous-coordination behavior on top of opencode without waiting for upstream review cycles.
The changes from upstream fall into two buckets: a hivemind-integration patch series (the original reason for the fork) and a small set of hivemind-aware TUI enhancements that make ticket references first-class in the chat view. All are deliberately scoped to keep weekly rebases on upstream cheap.
These make opencode work better with hivemind-mcp — grunt-it's shared coordination MCP for parallel agent sessions. Each is tracked in hivemind #222:
- Auto-announce-at-session-start. TUI sessions automatically register themselves on the hivemind peer registry. No
hivemind_announceTAKEOFF call needed. - Attach subscribes to all session message updates. When a wake fires via
/session/<id>/prompt_async, the attach-client TUI renders the response. (Without this patch, externally-triggered messages land in the server's db but never reach the user's screen.) OPENCODE_SERVER_URLpropagation. The attach binary reads and propagates this env var to all subprocesses (MCP children, etc.) so agents can find their serve daemon without a launch-script shim.--peer-id <id>flag. Lets launch scripts set the tab's hivemind peer-id at startup instead of via TAKEOFF gymnastics. Powers patch #1.- Turn-end hook into the hivemind loop primitive (#266 Phase 2 — default-on). Every step-finish fires
hivemind_record_turn_endinto the hivemind MCP, which evaluates the three-layer goal-loop contract + decides to auto-wake the same peer (continue), wake the parent peer (escalate/await-review), or no-op. Per-tool-callhivemind_loop_progressbumps the progress timestamp. Fire-and-forget viaEffect.forkIn(scope)— hook failures NEVER break the TUI. ON by default as of Phase 2; opt out for a specific tab/session viaOPENCODE_HIVEMIND_LOOP_ENABLED=0. Pairs with a 3-turn grace window inhivemind-mcp(#266 Phase 2) — peers have 3 turns to callhivemind_set_loop_goalbefore the MCP escalates withviolationKind=missing-loop-goal.
Hivemind-aware quality-of-life changes in the assistant chat view (tracked in hivemind #331):
-
Clickable, hoverable hivemind ticket refs. A bare
#N(e.g.#331) in an assistant message is detected and made interactive:- Colored + underlined in the theme link color so refs stand out from prose.
- Hover shows a preview card with the ticket's title, status, priority, zone, owner, and a scope snippet (fetched live from the local hivemind-api, cached per id).
- Click opens the ticket in hivemind-ui (
https://hivemind.grunt.si/tasks/<id>, override withHIVEMIND_UI_BASE).
How it works (and why): the message renders as a single
<markdown>block (the only structure that streams without flicker and keeps full markdown formatting + correct text-wrap). Refs stay as plain#Ntext — they are deliberately not rewritten to a markdown link, because OpenTUI's markdown renderer leaks the link URL into the visible text, and an inline OSC-8<a>can't live inside a<markdown>block. Hover/click instead read the renderer's actual framebuffer (currentRenderBuffer.buffers.char) to find the#Ntoken painted under the cursor — pixel-accurate, no column estimation. The ref color is applied in a per-frame post-process (addPostProcessFn) that recolors the painted#Ncells. Boundary rules match the detection regex, so#fff(hex),foo#12, and##12are never treated as refs. Hover/click are inert when hivemind-api is offline.
Everything else is opencode upstream. We rebase against anomalyco/opencode:dev weekly.
We upstream each patch as a PR in parallel. The fork exists because:
- We need these patches now for the grunt-it autonomous-coordinator vision (the hivemind UI + multi-tab coordinator workflows depend on them)
- Upstream's contribution guidelines reasonably require design review for "core" changes; we don't want to wait
- Branding helps signal that hivemind <-> coding-agent integration is a grunt-it product, not just a config layer on top of someone else's tool
If upstream accepts a patch, we drop it on next rebase. The fork shrinks over time.
gruntcode tracks opencode upstream. If you don't need hivemind integration, install opencode directly — it's the same binary minus our hivemind patches + TUI enhancements.
We do NOT promise:
- API stability beyond what opencode itself promises
- Independent feature work on top of opencode's TUI / agents / providers
- Backports of opencode bugfixes (you get them on next rebase, typically within a week)
We DO promise:
- The hivemind-integration patches + TUI enhancements stay working as long as the hivemind-mcp tools they target stay working
- Weekly rebase on upstream
dev - Tagged releases as
vX.Y.Z-grunt.NwhereX.Y.Zis the upstream version we rebased on
brew install grunt-it/tap/gruntcodeOr build from source:
git clone https://github.com/grunt-it/gruntcode.git
cd gruntcode
bun install
bun run --cwd packages/opencode src/index.ts --helpTagged releases live under grunt-it/gruntcode releases. Tags follow vX.Y.Z-grunt.N where X.Y.Z is the upstream opencode version we last rebased on, and N is the grunt-patch revision against that base.
Pushing a tag matching v*-grunt.* to dev triggers .github/workflows/grunt-release.yml, which builds binaries for macOS arm64/x64 + Linux x64/arm64 and uploads tarballs to the GH release. The homebrew tap formula resolves the latest stable.
For patches that should land in opencode upstream: open the PR there directly. We'll see it and drop our equivalent on next rebase.
For patches that are gruntcode-specific (i.e. hivemind-integration only, not general opencode improvements): open a PR against grunt-it/gruntcode:dev. We keep our patch series tiny — please discuss in hivemind #222 before opening a large change.
For everything else: use upstream opencode.
Each grunt-it patch lives as a single commit on top of upstream dev. The history looks like:
upstream/dev
├── upstream commits (we rebase on these)
└── grunt-it patches (cherry-picked on top, one commit each)
├── chore(grunt): branding (README, GRUNTCODE.md, etc.)
├── feat(grunt/tui): hivemind ticket-ref hover/click + color (#331)
├── feat(grunt): turn-end hook into hivemind loop primitive (#222 patch 5 / #266)
├── feat(grunt): --peer-id flag (#222 patch 4)
├── feat(grunt): OPENCODE_SERVER_URL propagation (#222 patch 3)
├── feat(grunt): attach subscribes to all session updates (#222 patch 2)
└── feat(grunt): auto-announce-at-session-start (#222 patch 1)
Weekly rebase:
git fetch upstream
git rebase upstream/dev
# resolve conflicts patch-by-patch
git push --force-with-lease origin devIf a grunt-it patch lands upstream, drop it from the rebase chain (it's now part of the upstream commit set).
If a grunt-it patch conflicts non-trivially: file as a hivemind sub-ticket of #222, decide whether to update the patch or drop it.
Same as upstream: MIT.