An open API service indexing awesome lists of open source software.

https://github.com/devinoldenburg/opencode-goal-mode

Strict Goal Mode for OpenCode: a goal agent + enforced review gates + a guard plugin that blocks destructive commands and premature "Goal Completed", with a live TUI goal banner.
https://github.com/devinoldenburg/opencode-goal-mode

agentic ai-agents claude cli code-review coding-agent developer-tools guardrails llm opencode opencode-plugin opencode-tui-plugin tui

Last synced: about 5 hours ago
JSON representation

Strict Goal Mode for OpenCode: a goal agent + enforced review gates + a guard plugin that blocks destructive commands and premature "Goal Completed", with a live TUI goal banner.

Awesome Lists containing this project

README

          

# OpenCode Goal Mode

Strict Goal Mode for OpenCode: a primary `goal` agent, specialized review
subagents, slash commands, a `goal-guard` plugin that enforces review discipline
and blocks destructive shell commands, and a structured Goal-owned todo section
in the TUI sidebar.

## Install

**One command.** Needs [Node](https://nodejs.org) 20.11+ and a working
[OpenCode](https://opencode.ai). Works on macOS and Linux:

```bash
npm install -g opencode-goal-mode && opencode-goal-mode --global
```

Then **restart OpenCode**. That's the whole install — it copies the Goal agent,
review subagents, slash commands, and guard plugin into `~/.config/opencode`, and
merge-safely registers the Goal todo sidebar in `~/.config/opencode/tui.json`.
In the agent picker you'll see only the **`goal`** agent; reviewers are subagents
it drives automatically. The install is **idempotent** (re-run it to upgrade in
place), never touches files you've edited, and `--uninstall` removes exactly what
it added. Goal Mode inherits your existing OpenCode model/provider.

Other ways to install

```bash
# Global npm install, then run the installer separately
npm install -g opencode-goal-mode
opencode-goal-mode --global # alias of opencode-goal-mode-install

# Preview first, then install (no writes on --dry-run)
opencode-goal-mode --global --dry-run

# One-off install with npx (no global package needed; OpenCode still loads the TUI
# sidebar — it resolves that from its own plugin cache, not the global install)
npx opencode-goal-mode --global

# Into a single project (writes ./.opencode, including ./.opencode/tui.json)
npx opencode-goal-mode

# Clean removal of everything it installed (incl. its tui.json entry)
opencode-goal-mode --global --uninstall

# From source
git clone https://github.com/devinoldenburg/opencode-goal-mode
cd opencode-goal-mode && npm ci && npm run install:global
```

Use global install for normal daily use. Use project install only when you want
Goal Mode scoped to one repo and your OpenCode build reads project `.opencode`
config, including `.opencode/tui.json`. See [Installer options](#installer-options).

[![npm version](https://img.shields.io/npm/v/opencode-goal-mode?color=2da44e&label=npm)](https://www.npmjs.com/package/opencode-goal-mode)
[![npm downloads](https://img.shields.io/npm/dm/opencode-goal-mode?color=2da44e)](https://www.npmjs.com/package/opencode-goal-mode)
[![CI](https://github.com/devinoldenburg/opencode-goal-mode/actions/workflows/ci.yml/badge.svg)](https://github.com/devinoldenburg/opencode-goal-mode/actions/workflows/ci.yml)
[![Release](https://github.com/devinoldenburg/opencode-goal-mode/actions/workflows/publish.yml/badge.svg)](https://github.com/devinoldenburg/opencode-goal-mode/actions/workflows/publish.yml)
[![license](https://img.shields.io/npm/l/opencode-goal-mode?color=2da44e)](LICENSE)
[![node](https://img.shields.io/node/v/opencode-goal-mode?color=2da44e)](package.json)

![OpenCode Goal Mode sidebar preview](docs/sidebar-preview.png)

↑ In goal mode, the Goal plugin takes over the sidebar todo section with a
structured, evidence-aware Goal todo list — a bold `GOAL` label, then the goal
title, gate progress, and per-acceptance/gate todo rows, each on its own line in
its own colour. Build and every other mode keep
OpenCode's native todo section — see [TUI integration](#tui-integration).

**[Quick start](#quick-start) · [Why it's different](#why-its-different) · [Benchmarks](#benchmarks-honest-edition) · [TUI integration](#tui-integration) · [Configuration](#configuration) · [Releasing](#releasing) · [Architecture](ARCHITECTURE.md)**

## Quick start

```bash
# 1. Install (needs Node 20.11+ and OpenCode)
npm install -g opencode-goal-mode
opencode-goal-mode-install --global

# 2. Restart OpenCode, then verify it loaded — you should see ONLY `goal (primary)`,
# with every specialist as a (subagent):
opencode agent list | grep goal
```

3. In OpenCode, start a goal:

```
/goal add rate limiting to the login endpoint and prove it works
```

The `goal` agent writes a contract, delegates research/review to subagents, and
**cannot** answer `Goal Completed` until every required review gate passes — the
guard rewrites a premature claim to `Goal Not Completed`. Try a destructive
command mid-session (e.g. `rm -rf build`) and watch it get blocked. If your
OpenCode build supports TUI plugins, Goal sessions also get the Goal-owned
sidebar todo section (experimental — see [TUI integration](#tui-integration)).

That's it. Everything below is detail.

See [ARCHITECTURE.md](ARCHITECTURE.md) for the design and [research/](research/)
for the platform reference, comparison, and threat model.

## Why it's different

Most "goal mode" / agentic setups are **prompt-only**: the model is *asked* to
review its work and to keep going until done. Goal Mode adds a guard plugin that
makes that discipline **mechanical at the harness layer** — the model cannot
declare `Goal Completed` until the required reviews actually passed, and it
is blocked from the benchmarked destructive-command bypasses that a regex guard
would miss.

![Mechanically-enforced goal discipline vs. Claude Code and Codex](docs/benchmarks/capability-matrix.svg)

Compared to Claude Code and OpenAI Codex (full analysis, with citations and
honest caveats, in [research/goal-mode-comparison.md](research/goal-mode-comparison.md)):

- **It is the only one of the three that mechanically blocks a premature
completion claim by default.** Goal Mode intercepts the finished message and
rewrites `Goal Completed` → `Goal Not Completed` unless every required reviewer
gate has a *fresh* PASS and the claimed `Review cycles: N` matches the recorded
counter. Claude Code can do this only via a user-authored Stop hook; Codex's
code review is advisory.
- **An edit automatically invalidates prior approvals.** A reviewer gate counts
only when its PASS is newer (by a monotonic integer sequence) than the last
edit — so any change forces the relevant reviews to re-run. The public Claude
Code and Codex docs reviewed do not describe this stale-review invariant.
- **Required specialist reviews are auto-selected and enforced** (security, api,
data, performance …) from the goal text, contract, and changed files — not left
to the model's discretion.
- **Destructive commands are blocked by a real shell tokenizer**, not a regex.
Claude Code's own docs call Bash argument-matching *"fragile"*.

### Benchmarks (honest edition)

The headline number is measured on commands **the analyzer was never fitted to**:
704 real example commands from [tldr-pages](https://github.com/tldr-pages/tldr)
(common/linux/osx), authored by hundreds of contributors who have never seen
this guard. Ground-truth labels come from a deliberately simple, analyzer-*independent*
rule (see [build-external-corpus.mjs](benchmarks/build-external-corpus.mjs)).
Reproduce with `npm run bench` or `node benchmarks/external.mjs`.

![Guard accuracy on real third-party commands](docs/benchmarks/external-scorecard.svg)

| On 704 real third-party commands | Legacy regex guard | Goal Mode analyzer |
| --- | --- | --- |
| Destructive-command detection | 53.8% | **93.3%** |
| False positives on safe commands | 0.2% | **0.2%** |

Honest caveats, because the point of this rewrite was to stop overclaiming:

- The 7 remaining "misses" are all plain `rm` invocations without `-r`/`-f`
(single- or multi-target, a few with `-i`/`-v`/`-d`), which the guard
**intentionally permits**: bare `rm` is extremely common, so the guard marks it
dirty but lets the host's own `rm *` permission decide, while still blocking the
irreversible forms (`rm -r`/`rm -f`, wildcard/root, `$(rm …)`, `bash -c`,
`/bin/rm`, interpreters, etc.). Under a strict every-`rm`-is-destructive
labeling those count against it.
- The single counted false positive (`git filter-repo …`) actually *is* a
history-rewriting command, so the real-world false-positive rate is effectively
zero. `node benchmarks/external.mjs --json` lists every miss and false positive
so you can audit the disagreements yourself.

Two **curated fixture sets** also ship — and they are explicitly *fixtures*, not
an unbiased benchmark. They define the patterns the analyzer must catch and guard
against regressions, so they pass by construction; do not read the 100%/0% there
as measured accuracy:

- `benchmarks/corpus.mjs` — 71 destructive patterns (incl. `$(…)`, `bash -c`,
`sudo -u`, `/bin/rm`, `git -C … reset --hard`, `curl | sh`, interpreter
deletes) and their safe look-alikes (`git checkout -b`, `echo "rm -rf /"`).
- `benchmarks/completion-corpus.mjs` — 9 completion-claim policy cases (missing
review-cycle line, stale review after edit, missing contextual gate, inactive
session, custom marker). `npm run bench:truthfulness` prints them.

The analysis costs ~1µs per command (hundreds of thousands of classifications per
second) — negligible for a per-tool-call guard:

![Per-command analysis latency](docs/benchmarks/latency.svg)

## Requirements

- Node.js 20.11 or newer.
- OpenCode configured to load local agents, commands, and plugins. The package is
tested against `@opencode-ai/plugin` 1.17.6 and declares compatibility with the
1.15+ plugin hook surface used here; newer OpenCode builds that change plugin
or TUI slot APIs may need a package update.
- A working OpenCode provider/model; Goal Mode does not configure API keys or
choose a model for you.

## What it adds

- A primary `goal` agent that owns implementation but delegates research,
discovery, verification planning, and reviews to subagents. **`goal` is the only
user-selectable agent** — every specialist (security, diff, verifier, …) is a
`mode: subagent` that the Goal agent invokes via the task tool; the user never
picks one directly, and the guard blocks any other agent from invoking them (see
**Goal-only subagents** below). They surface with friendly names (e.g. "Security Reviewer",
"API Reviewer") rather than raw ids.
- Strict review gates for prompt compliance, diff review, verification, security,
UX, operations, data, API, performance, tests, docs, quality, and final audit.
- Slash commands: `/goal`, `/goal-contract`, `/goal-review`,
`/goal-evidence-map`, `/goal-status`, `/goal-repair`, `/goal-final`.
- The `goal-guard` plugin:
- **Quote-aware shell analysis** that blocks destructive and remote-exec
commands (including ones that evade naive regexes — `$(rm -rf …)`,
`bash -c "…"`, `/bin/rm`, `git -C … reset --hard`, `curl | sh`) without
false-positiving harmless commands like `git checkout -b`.
- **Completion enforcement**: a premature `Goal Completed` is rewritten to
`Goal Not Completed` with the exact missing review gates.
- **Contextual gating**: the goal text and changed files determine which
specialist reviewers are required.
- **Goal-only subagents**: the `goal-*` specialist subagents are mechanically
locked to Goal Mode. OpenCode resolves subagents globally, so the guard blocks
any Build, Plan, or custom agent that tries to invoke a `goal-*` reviewer via
the task tool — they run only under the Goal agent (toggle with
`restrictSubagents`). General-purpose subagents (`explore`/`general`/`scout`)
are never restricted.
- **Reviewer Memory**: blocking reviewer findings are carried across cycles,
surfaced in status/system context, and marked resolved by fresh PASS verdicts.
- **Disk persistence**: review ledgers and Reviewer Memory survive OpenCode restarts.
- **Custom tools**: `goal_contract`, `goal_evidence`, `goal_evidence_map`,
`goal_reviewer_memory`, `goal_status`, `goal_reset`.
- **Live state injection** into the system prompt so the model always knows
what the guard requires.
- **TUI toasts**: a toast on each review verdict (PASS/FAIL), with the
reviewer's friendly name, and a single "completion unlocked" toast the moment
the last required gate clears.
- An **experimental** companion TUI plugin (`plugins/goal-sidebar.tsx`) that, in
Goal sessions only, takes over the sidebar todo area with a structured,
evidence-aware Goal todo list (`GOAL` label, goal title, gate progress, and
todo rows — each on its own line in its own colour). See [TUI integration](#tui-integration).
- A test suite validating the analyzer, plugin hooks, state store, install
safety, and config compatibility.

## TUI integration

Goal Mode is a **plugin pair**: the server-side `goal-guard` plugin owns
enforcement and writes its state to disk, and an experimental TUI plugin
(`plugins/goal-sidebar.tsx`) reads that same state to render a live todo section.

- **Goal-owned todo section.** In a `goal` session with a goal set, the Goal
plugin renders its own structured todo section into the sidebar's `sidebar_content`
slot, stacked on separate lines, each in its own colour so it never reads as one
run of text:
- a bold **`GOAL`** label (yellow while running, red when done);
- the short goal title (white);
- the gate count `passing/total gates` (cyan), on its own line;
- the lifecycle status (orange) on its own line — `in progress`, or
`completed · N review cycles`. No "changes pending" noise; pending work shows
as a todo row instead;
- structured todo rows derived from real guard state: one per acceptance
criterion (✓ when fresh evidence covers it), a re-verify row when the tree
changed, and one row per still-missing review gate by friendly name
(e.g. "Pass Security Reviewer").

Each line uses its lifecycle colour (running → yellow label; done → red).
Because OpenCode renders the native todo list as that slot's *fallback*,
on builds that render `sidebar_content` in replace/single-winner mode the Goal
section **replaces** the native todo list while a goal is active; in append mode
it sits alongside it. In every case:
- **no render** — Build and every non-Goal mode (and a Goal session before a
goal is set) render nothing here, so OpenCode's native todo section stays in
the same position. The section is scoped to the session that owns the goal: a
Build session in the same worktree never inherits another session's goal.

Toggle/recolour with `sidebarBanner`, `sidebarColor` (running), `sidebarDoneColor`
(done), `sidebarMutedColor`, or the `GOAL_GUARD_SIDEBAR_*` env vars.

**How it loads — important.** TUI plugins are **not** loaded from the `plugins/`
dir; OpenCode loads them from `tui.json`. The Goal sidebar registers a
`sidebar_content` slot that renders content **only** for the active session when
that session is a Goal session; for any other session it renders nothing, so
non-Goal modes keep their native todo section. With `--global`, the installer
writes `~/.config/opencode/tui.json` for you (merge-safe):

```json
{ "$schema": "https://opencode.ai/tui.json", "plugin": ["opencode-goal-mode"] }
```

OpenCode installs the referenced package into its own plugin cache
(`~/.cache/opencode/packages/`) and provides the `@opentui/solid` + `solid-js`
runtime to it. It does **not** re-check that cache for newer versions, so the
installer clears the cached copy on install/uninstall — that's why an upgrade
needs only a restart to load the new sidebar. Restart OpenCode after install. The
Goal todo section appears in a **Goal session** view (not the home screen and not
Build mode), and because the Goal agent does its own todo tracking (native
`todowrite` is disabled in Goal Mode), it replaces — rather than sits beside —
the native todo list while a goal is active. The visual harness renders the
component headlessly in [visual test](tools/visual-test/README.md)
(`npm run test:visual`); the enforcement core is a separate server plugin and
works regardless of the sidebar.
- **Toasts.** Review verdicts and completion-unlock events surface as toasts
(`toastOnReview`), and blocked destructive commands / premature completions
toast as before (`toastOnBlock`).

## Installer options

```bash
npm install -g opencode-goal-mode && opencode-goal-mode --global
npx opencode-goal-mode --global --dry-run
npx opencode-goal-mode --global
opencode-goal-mode-install --global --uninstall
node scripts/install.mjs --dry-run
node scripts/install.mjs --target /path/to/opencode-config
node scripts/install.mjs --global --force
node scripts/install.mjs --global --uninstall
```

Default target rules are simple: `--global` writes to `~/.config/opencode`; no
flag writes to `./.opencode`; `--target` writes to exactly the directory you pass.
In every target, the installer copies only `agents/`, `commands/`, `plugins/`,
writes `.goal-mode-manifest.json`, and merge-safely adds `opencode-goal-mode` to
`tui.json` in that same target. On upgrade it replaces files it owns but refuses
to clobber files you have locally modified unless `--force` is passed.
`--uninstall` removes only owned files and removes only its own `tui.json` entry.

## Configuration

The guard works with zero configuration. To tune it, add options in
`opencode.json`:

```jsonc
{
"plugin": [
["./plugins/goal-guard.js", { "blockDestructive": true, "contextualGates": true }]
]
}
```

Or via environment variables (`GOAL_GUARD_*`):

| Option / env | Default | Effect |
| --- | --- | --- |
| `blockDestructive` / `GOAL_GUARD_BLOCK_DESTRUCTIVE` | `true` | Block destructive bash before execution. |
| `blockNetworkExec` / `GOAL_GUARD_BLOCK_NETWORK_EXEC` | `true` | Block `curl \| sh`-style remote execution. |
| `enforceCompletion` / `GOAL_GUARD_ENFORCE_COMPLETION` | `true` | Rewrite premature `Goal Completed`. |
| `injectSystemState` / `GOAL_GUARD_INJECT_SYSTEM_STATE` | `true` | Inject live state into the prompt. |
| `persist` / `GOAL_GUARD_PERSIST` | `true` | Persist state under the XDG state dir. |
| `contextualGates` / `GOAL_GUARD_CONTEXTUAL_GATES` | `true` | Require specialist gates by goal keywords. |
| `restrictSubagents` / `GOAL_GUARD_RESTRICT_SUBAGENTS` | `true` | Block non-Goal agents from invoking the `goal-*` subagents via the task tool. |
| `maxSessions` / `GOAL_GUARD_MAX_SESSIONS` | `200` | Session cache size. |
| `sessionTtlMs` / `GOAL_GUARD_SESSION_TTL_MS` | `86400000` | Idle session TTL. |
| `toastOnBlock` / `GOAL_GUARD_TOAST_ON_BLOCK` | `true` | Toast when something is blocked. |
| `toastOnReview` / `GOAL_GUARD_TOAST_ON_REVIEW` | `true` | Toast on each review verdict and when completion unlocks. |
| `sidebarBanner` / `GOAL_GUARD_SIDEBAR_BANNER` | `true` | Show the experimental Goal todo section in the TUI sidebar. |
| `sidebarColor` / `GOAL_GUARD_SIDEBAR_COLOR` | `#FFD700` | Colour of the GOAL label for a **running** goal. |
| `sidebarDoneColor` / `GOAL_GUARD_SIDEBAR_DONE_COLOR` | `#FF5555` | Colour of a **done** goal in the sidebar (red). |
| `sidebarMutedColor` / `GOAL_GUARD_SIDEBAR_MUTED_COLOR` | `#808080` | Reserved muted colour for no-goal projections. |

## Custom tools

The plugin registers six tools the model can call directly:

- `goal_contract` — record the Goal Contract (requirements, non-goals,
acceptance criteria). Activates enforcement and fixes the required gates.
- `goal_evidence` — record a verification command and result.
- `goal_evidence_map` — return the acceptance-criteria evidence map with
reviewer status, gaps, and next actions.
- `goal_reviewer_memory` — return unresolved and recently resolved reviewer findings.
- `goal_status` — return the authoritative gate/dirty/completion status.
- `goal_reset` — clear the session's goal state (requires `confirm: true`).

Use `/goal-evidence-map` when you need a read-only matrix of each acceptance
criterion against recorded evidence, reviewer status, gaps, and the next
required action. The command is backed by the `goal_evidence_map` tool, so it
uses persisted Goal Guard state rather than relying on transcript memory.

## Contributor validation

```bash
npm test
npm run validate
npm run audit
npm run publish:check
```

`npm run validate` runs the test suite, the structural config validator, the
publish readiness check, and an `npm pack --dry-run`.

## Models

Agents do not pin a provider-specific model, so they inherit the model OpenCode
is configured to use. To give a particular agent a specific model, add a
`model:` (and optional `variant:`) line to that agent's frontmatter in your
installed copy.

## Safety

The installer copies only `agents/*.md`, `commands/*.md`, and the `plugins/`
tree — never auth files, session files, tokens, or personal provider config.

The guard blocks destructive shell commands, marks real file mutations dirty,
keeps read-only inspection from dirtying the session, preserves goal state during
compaction and across restarts, and blocks premature `Goal Completed` responses
when review gates are missing or stale.

## Releasing

Releases are fully automated and **version-synced**: one pushed tag publishes to
npm *and* creates the matching GitHub Release. The pipeline lives in
[`.github/workflows/publish.yml`](.github/workflows/publish.yml) (Node 24).

```bash
npm version patch # bumps package.json + package-lock.json and creates the vX.Y.Z tag
git push --follow-tags # pushes main + the tag → the Release workflow runs
```

On a `vX.Y.Z` tag push the workflow:

1. installs and runs the full CI gate (`npm run ci` — tests, audit, structural
validation, `npm pack --dry-run`);
2. runs `npm run publish:check`, which **fails if the tag does not match
`package.json`** or if that version already exists on npm;
3. publishes with `npm publish --access public` using the `NPM_TOKEN` repository
secret;
4. creates the GitHub Release for the tag with auto-generated notes.

So the git tag, the `package.json` version, the npm version, and the GitHub
Release version are always identical. A manual `workflow_dispatch` is available
and defaults to a safe `npm publish --dry-run`.

**One-time setup:** add a publish-scoped npm token as the `NPM_TOKEN` repository
secret (`gh secret set NPM_TOKEN`). Treat that token as sensitive — never commit
it.

## Goal Completion Contract

`Goal Completed` is allowed only when:

- All acceptance criteria are mapped to evidence.
- Required verification passed or is credibly accounted for.
- No edit is newer than the latest required review cycle.
- Required reviewers return `Verdict: PASS`.
- The final answer includes an accurate `Review cycles: N`.