opencode-goal-mode 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +66 -31
- package/docs/sidebar-demo.svg +54 -0
- package/package.json +17 -2
- package/plugins/goal-guard/agents.js +20 -0
- package/plugins/goal-guard.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.3.3
|
|
4
|
+
|
|
5
|
+
- Release notes: the GitHub Release body is now generated from the matching
|
|
6
|
+
`CHANGELOG.md` section (`scripts/release-notes.mjs`), so releases always ship
|
|
7
|
+
real notes instead of an empty auto-summary.
|
|
8
|
+
- Richer repository: README badges (npm version/downloads, CI, release, license,
|
|
9
|
+
node), a quick-links bar, and a terminal-style sidebar demo
|
|
10
|
+
(`docs/sidebar-demo.svg`); a descriptive repo summary and topics; and
|
|
11
|
+
community health files (CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, issue and PR
|
|
12
|
+
templates).
|
|
13
|
+
- Stronger npm discoverability: expanded `keywords` (opencode-plugin,
|
|
14
|
+
opencode-tui-plugin, guardrails, review-gates, completion-enforcement, …).
|
|
15
|
+
|
|
16
|
+
## v0.3.2
|
|
17
|
+
|
|
18
|
+
- Only the `goal` agent is user-selectable. The structural validator now requires
|
|
19
|
+
every other agent to be `mode: subagent` (no `all`/extra `primary`), so the
|
|
20
|
+
specialist reviewers can only be invoked by the Goal agent via the task tool,
|
|
21
|
+
never picked by the user.
|
|
22
|
+
- Friendlier subagent names in the TUI: review-verdict toasts now read
|
|
23
|
+
"Security Reviewer → PASS" / "API Reviewer → PASS" instead of raw hyphenated ids
|
|
24
|
+
(`prettyAgentName` drops the `goal-` prefix, de-hyphenates, keeps acronyms).
|
|
25
|
+
- Release pipeline: a single `vX.Y.Z` tag push now publishes to npm AND creates
|
|
26
|
+
the matching GitHub Release (versions stay in sync). `publish:check` fails the
|
|
27
|
+
release if the tag does not match `package.json` or the version already exists.
|
|
28
|
+
- README: npm-first install instructions and a documented release flow.
|
|
29
|
+
|
|
3
30
|
## v0.3.1
|
|
4
31
|
|
|
5
32
|
- Sidebar: when a task is running but no goal is set, show a clean grey `No goal`
|
package/README.md
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
# OpenCode Goal Mode
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-goal-mode)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-goal-mode)
|
|
5
|
+
[](https://github.com/devinoldenburg/opencode-goal-mode/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/devinoldenburg/opencode-goal-mode/actions/workflows/publish.yml)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](package.json)
|
|
9
|
+
|
|
3
10
|
Strict Goal Mode for OpenCode: a primary `goal` agent, a matrix of specialized
|
|
4
11
|
review subagents, slash commands, and a `goal-guard` plugin that enforces review
|
|
5
12
|
discipline, blocks destructive shell commands, and preserves goal state across
|
|
6
13
|
compaction **and** restarts.
|
|
7
14
|
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g opencode-goal-mode && opencode-goal-mode-install --global
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
**[Install](#install) · [Why it's different](#why-its-different) · [Benchmarks](#benchmarks-honest-edition) · [TUI integration](#tui-integration) · [Configuration](#configuration) · [Releasing](#releasing) · [Architecture](ARCHITECTURE.md)**
|
|
22
|
+
|
|
8
23
|
See [ARCHITECTURE.md](ARCHITECTURE.md) for the design and [research/](research/)
|
|
9
24
|
for the platform reference, comparison, and threat model.
|
|
10
25
|
|
|
@@ -90,7 +105,11 @@ second) — negligible for a per-tool-call guard:
|
|
|
90
105
|
## What it adds
|
|
91
106
|
|
|
92
107
|
- A primary `goal` agent that owns implementation but delegates research,
|
|
93
|
-
discovery, verification planning, and reviews to subagents.
|
|
108
|
+
discovery, verification planning, and reviews to subagents. **`goal` is the only
|
|
109
|
+
user-selectable agent** — every specialist (security, diff, verifier, …) is a
|
|
110
|
+
`mode: subagent` that the Goal agent invokes via the task tool; the user never
|
|
111
|
+
picks one directly. They surface with friendly names (e.g. "Security Reviewer",
|
|
112
|
+
"API Reviewer") rather than raw ids.
|
|
94
113
|
- Strict review gates for prompt compliance, diff review, verification, security,
|
|
95
114
|
UX, operations, data, API, performance, tests, docs, quality, and final audit.
|
|
96
115
|
- Slash commands: `/goal`, `/goal-contract`, `/goal-review`,
|
|
@@ -111,8 +130,9 @@ second) — negligible for a per-tool-call guard:
|
|
|
111
130
|
`goal_reviewer_memory`, `goal_status`, `goal_reset`.
|
|
112
131
|
- **Live state injection** into the system prompt so the model always knows
|
|
113
132
|
what the guard requires.
|
|
114
|
-
- **TUI toasts**: a toast on each review verdict (PASS/FAIL)
|
|
115
|
-
"completion unlocked" toast the moment
|
|
133
|
+
- **TUI toasts**: a toast on each review verdict (PASS/FAIL), with the
|
|
134
|
+
reviewer's friendly name, and a single "completion unlocked" toast the moment
|
|
135
|
+
the last required gate clears.
|
|
116
136
|
- An **experimental** companion TUI plugin (`plugins/goal-sidebar.js`) that shows
|
|
117
137
|
the active goal as a shining-yellow banner in the sidebar with a compact gate
|
|
118
138
|
status line. See [TUI integration](#tui-integration).
|
|
@@ -140,27 +160,40 @@ enforcement and writes its state to disk, and an experimental TUI plugin
|
|
|
140
160
|
(`toastOnReview`), and blocked destructive commands / premature completions
|
|
141
161
|
toast as before (`toastOnBlock`).
|
|
142
162
|
|
|
143
|
-
## Install
|
|
163
|
+
## Install
|
|
164
|
+
|
|
165
|
+
### From npm (recommended)
|
|
144
166
|
|
|
145
167
|
```bash
|
|
146
|
-
npm
|
|
147
|
-
|
|
148
|
-
npm run install:global
|
|
168
|
+
npm install -g opencode-goal-mode
|
|
169
|
+
opencode-goal-mode-install --global # installs into ~/.config/opencode
|
|
149
170
|
```
|
|
150
171
|
|
|
151
|
-
|
|
152
|
-
|
|
172
|
+
Then restart OpenCode (it loads agents, commands, and plugins at startup). In the
|
|
173
|
+
agent picker you will see **only the `goal` agent** — the specialist reviewers are
|
|
174
|
+
subagents the Goal agent drives for you; they are never selectable by the user.
|
|
153
175
|
|
|
154
|
-
|
|
176
|
+
Install into a single project instead of globally:
|
|
155
177
|
|
|
156
178
|
```bash
|
|
179
|
+
npm install -D opencode-goal-mode
|
|
180
|
+
npx opencode-goal-mode-install # writes to ./.opencode
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Upgrade later by re-running the same install command after `npm install -g
|
|
184
|
+
opencode-goal-mode@latest`; the installer replaces only the files it owns and
|
|
185
|
+
leaves your local edits alone (see [Installer options](#installer-options)).
|
|
186
|
+
|
|
187
|
+
### From source
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
git clone https://github.com/devinoldenburg/opencode-goal-mode
|
|
191
|
+
cd opencode-goal-mode
|
|
157
192
|
npm ci
|
|
158
193
|
npm run validate
|
|
159
|
-
npm run install:local
|
|
194
|
+
npm run install:global # or: npm run install:local
|
|
160
195
|
```
|
|
161
196
|
|
|
162
|
-
This writes to `./.opencode` in the current project.
|
|
163
|
-
|
|
164
197
|
## Installer options
|
|
165
198
|
|
|
166
199
|
```bash
|
|
@@ -253,32 +286,34 @@ keeps read-only inspection from dirtying the session, preserves goal state durin
|
|
|
253
286
|
compaction and across restarts, and blocks premature `Goal Completed` responses
|
|
254
287
|
when review gates are missing or stale.
|
|
255
288
|
|
|
256
|
-
##
|
|
289
|
+
## Releasing
|
|
257
290
|
|
|
258
|
-
|
|
291
|
+
Releases are fully automated and **version-synced**: one pushed tag publishes to
|
|
292
|
+
npm *and* creates the matching GitHub Release. The pipeline lives in
|
|
293
|
+
[`.github/workflows/publish.yml`](.github/workflows/publish.yml) (Node 24).
|
|
259
294
|
|
|
260
295
|
```bash
|
|
261
|
-
npm
|
|
262
|
-
|
|
296
|
+
npm version patch # bumps package.json + package-lock.json and creates the vX.Y.Z tag
|
|
297
|
+
git push --follow-tags # pushes main + the tag → the Release workflow runs
|
|
263
298
|
```
|
|
264
299
|
|
|
265
|
-
|
|
266
|
-
and publishes with the `NPM_TOKEN` repository secret. The workflow validates the
|
|
267
|
-
package, checks the tag matches `package.json`, verifies the version is not
|
|
268
|
-
already on npm, then publishes. Manual workflow dispatch defaults to
|
|
269
|
-
`npm publish --dry-run`.
|
|
300
|
+
On a `vX.Y.Z` tag push the workflow:
|
|
270
301
|
|
|
271
|
-
|
|
302
|
+
1. installs and runs the full CI gate (`npm run ci` — tests, audit, structural
|
|
303
|
+
validation, `npm pack --dry-run`);
|
|
304
|
+
2. runs `npm run publish:check`, which **fails if the tag does not match
|
|
305
|
+
`package.json`** or if that version already exists on npm;
|
|
306
|
+
3. publishes with `npm publish --access public` using the `NPM_TOKEN` repository
|
|
307
|
+
secret;
|
|
308
|
+
4. creates the GitHub Release for the tag with auto-generated notes.
|
|
272
309
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
```
|
|
310
|
+
So the git tag, the `package.json` version, the npm version, and the GitHub
|
|
311
|
+
Release version are always identical. A manual `workflow_dispatch` is available
|
|
312
|
+
and defaults to a safe `npm publish --dry-run`.
|
|
277
313
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
the release.
|
|
314
|
+
**One-time setup:** add a publish-scoped npm token as the `NPM_TOKEN` repository
|
|
315
|
+
secret (`gh secret set NPM_TOKEN`). Treat that token as sensitive — never commit
|
|
316
|
+
it.
|
|
282
317
|
|
|
283
318
|
## Goal Completion Contract
|
|
284
319
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="280" viewBox="0 0 760 280" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" role="img" aria-label="OpenCode Goal Mode sidebar banner: the active goal in yellow with a gate-status line, and a grey No goal state.">
|
|
2
|
+
<defs>
|
|
3
|
+
<style>
|
|
4
|
+
.win { fill: #0d1117; stroke: #30363d; stroke-width: 1; }
|
|
5
|
+
.bar { fill: #161b22; }
|
|
6
|
+
.dot { stroke-width: 0; }
|
|
7
|
+
.title { fill: #8b949e; font-size: 12px; }
|
|
8
|
+
.label { fill: #8b949e; font-size: 12px; }
|
|
9
|
+
.goal { fill: #FFD700; font-size: 13px; }
|
|
10
|
+
.goalb { fill: #FFD700; font-size: 13px; font-weight: 700; }
|
|
11
|
+
.status { fill: #FFD700; font-size: 12px; }
|
|
12
|
+
.muted { fill: #808080; font-size: 13px; }
|
|
13
|
+
.chat { fill: #c9d1d9; font-size: 12px; }
|
|
14
|
+
.dim { fill: #6e7681; font-size: 12px; }
|
|
15
|
+
.ok { fill: #2da44e; font-size: 12px; }
|
|
16
|
+
.div { stroke: #30363d; stroke-width: 1; }
|
|
17
|
+
</style>
|
|
18
|
+
</defs>
|
|
19
|
+
|
|
20
|
+
<!-- window -->
|
|
21
|
+
<rect class="win" x="1" y="1" width="758" height="278" rx="8"/>
|
|
22
|
+
<rect class="bar" x="1" y="1" width="758" height="30" rx="8"/>
|
|
23
|
+
<rect class="bar" x="1" y="20" width="758" height="11"/>
|
|
24
|
+
<circle class="dot" cx="20" cy="16" r="5" fill="#ff5f56"/>
|
|
25
|
+
<circle class="dot" cx="38" cy="16" r="5" fill="#ffbd2e"/>
|
|
26
|
+
<circle class="dot" cx="56" cy="16" r="5" fill="#27c93f"/>
|
|
27
|
+
<text class="title" x="86" y="20">opencode — goal mode</text>
|
|
28
|
+
|
|
29
|
+
<!-- vertical divider between chat and sidebar -->
|
|
30
|
+
<line class="div" x1="470" y1="31" x2="470" y2="279"/>
|
|
31
|
+
|
|
32
|
+
<!-- chat pane (left) -->
|
|
33
|
+
<text class="dim" x="20" y="58">▌ goal</text>
|
|
34
|
+
<text class="chat" x="20" y="82">▸ Goal Contract recorded (4 acceptance criteria)</text>
|
|
35
|
+
<text class="chat" x="20" y="104">▸ implementing… running verification</text>
|
|
36
|
+
<text class="ok" x="20" y="126">✓ Security Reviewer → PASS</text>
|
|
37
|
+
<text class="ok" x="20" y="148">✓ Verifier → PASS</text>
|
|
38
|
+
<text class="dim" x="20" y="170">▸ Diff Reviewer running…</text>
|
|
39
|
+
|
|
40
|
+
<!-- sidebar pane (right) -->
|
|
41
|
+
<text class="label" x="490" y="58">SESSION</text>
|
|
42
|
+
<line class="div" x1="490" y1="68" x2="740" y2="68"/>
|
|
43
|
+
|
|
44
|
+
<!-- goal banner (active) -->
|
|
45
|
+
<text x="490" y="98"><tspan class="goal">◆ </tspan><tspan class="goalb">GOAL</tspan><tspan class="goal"> Ship the OAuth</tspan></text>
|
|
46
|
+
<text class="goal" x="490" y="116">refactor</text>
|
|
47
|
+
<text class="status" x="490" y="138">3/5 gates · dirty</text>
|
|
48
|
+
|
|
49
|
+
<line class="div" x1="490" y1="170" x2="740" y2="170"/>
|
|
50
|
+
|
|
51
|
+
<!-- no-goal state -->
|
|
52
|
+
<text class="label" x="490" y="196">when no goal is set</text>
|
|
53
|
+
<text class="muted" x="490" y="222">No goal</text>
|
|
54
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-goal-mode",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Strict Goal Mode agents, commands, and guard plugin for OpenCode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -48,9 +48,24 @@
|
|
|
48
48
|
},
|
|
49
49
|
"keywords": [
|
|
50
50
|
"opencode",
|
|
51
|
+
"opencode-plugin",
|
|
52
|
+
"opencode-tui-plugin",
|
|
51
53
|
"goal-mode",
|
|
52
54
|
"agents",
|
|
53
|
-
"
|
|
55
|
+
"ai-agents",
|
|
56
|
+
"agentic",
|
|
57
|
+
"subagents",
|
|
58
|
+
"code-review",
|
|
59
|
+
"review-gates",
|
|
60
|
+
"guardrails",
|
|
61
|
+
"completion-enforcement",
|
|
62
|
+
"shell-guard",
|
|
63
|
+
"destructive-command",
|
|
64
|
+
"tui",
|
|
65
|
+
"llm",
|
|
66
|
+
"claude",
|
|
67
|
+
"coding-agent",
|
|
68
|
+
"developer-tools",
|
|
54
69
|
"plugin"
|
|
55
70
|
],
|
|
56
71
|
"repository": {
|
|
@@ -130,3 +130,23 @@ export const CONTEXTUAL_GATES = Object.freeze({
|
|
|
130
130
|
|
|
131
131
|
/** The reviewer that, when it returns a verdict, closes one review cycle. */
|
|
132
132
|
export const CYCLE_CLOSING_AGENT = "goal-final-auditor";
|
|
133
|
+
|
|
134
|
+
/** Acronyms that should stay upper-case in display names. */
|
|
135
|
+
const ACRONYMS = new Set(["api", "ux", "ui", "sql", "ops", "qa"]);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Human-friendly display name for an agent id: drops the `goal-` namespace
|
|
139
|
+
* prefix, turns hyphens into spaces, Title-Cases words, and keeps known acronyms
|
|
140
|
+
* upper-case. e.g. "goal-security-reviewer" → "Security Reviewer",
|
|
141
|
+
* "goal-api-reviewer" → "API Reviewer", "goal-final-auditor" → "Final Auditor".
|
|
142
|
+
*/
|
|
143
|
+
export function prettyAgentName(id) {
|
|
144
|
+
const raw = String(id || "").trim();
|
|
145
|
+
if (!raw) return "";
|
|
146
|
+
return raw
|
|
147
|
+
.replace(/^goal-/, "")
|
|
148
|
+
.split(/[-_\s]+/)
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.map((w) => (ACRONYMS.has(w.toLowerCase()) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1)))
|
|
151
|
+
.join(" ");
|
|
152
|
+
}
|
package/plugins/goal-guard.js
CHANGED
|
@@ -23,7 +23,7 @@ import { createStore, createState } from "./goal-guard/state.js";
|
|
|
23
23
|
import { createPersistence } from "./goal-guard/persistence.js";
|
|
24
24
|
import { createLogger } from "./goal-guard/logger.js";
|
|
25
25
|
import { analyzeCommand, looksLikeDestructiveBash, looksLikeMutatingBash, isVerification } from "./goal-guard/shell.js";
|
|
26
|
-
import { isPrimaryAgent, isReviewAgent, CYCLE_CLOSING_AGENT } from "./goal-guard/agents.js";
|
|
26
|
+
import { isPrimaryAgent, isReviewAgent, CYCLE_CLOSING_AGENT, prettyAgentName } from "./goal-guard/agents.js";
|
|
27
27
|
import { textOf, parseVerdict, recordVerdict } from "./goal-guard/verdicts.js";
|
|
28
28
|
import { completionAllowed, missingGates, refreshStickyGates } from "./goal-guard/gates.js";
|
|
29
29
|
import { evaluateCompletionClaim } from "./goal-guard/completion.js";
|
|
@@ -210,9 +210,9 @@ export function createGuard(input = {}, options = {}, overrides = {}) {
|
|
|
210
210
|
// Surface review progress in the TUI: a toast per recorded verdict, and a
|
|
211
211
|
// single celebratory toast the moment the last required gate clears.
|
|
212
212
|
if (recordedAgent && recordedVerdict && config.toastOnReview) {
|
|
213
|
-
logger.toast(
|
|
213
|
+
logger.toast(`${prettyAgentName(recordedAgent)} → ${recordedVerdict}`, recordedVerdict === "PASS" ? "success" : "warning");
|
|
214
214
|
if (!wasAllowed && completionAllowed(state, config)) {
|
|
215
|
-
logger.toast("
|
|
215
|
+
logger.toast("All required gates passed — completion unlocked", "success");
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
persist();
|