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 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
+ [![npm version](https://img.shields.io/npm/v/opencode-goal-mode?color=2da44e&label=npm)](https://www.npmjs.com/package/opencode-goal-mode)
4
+ [![npm downloads](https://img.shields.io/npm/dm/opencode-goal-mode?color=2da44e)](https://www.npmjs.com/package/opencode-goal-mode)
5
+ [![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)
6
+ [![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)
7
+ [![license](https://img.shields.io/npm/l/opencode-goal-mode?color=2da44e)](LICENSE)
8
+ [![node](https://img.shields.io/node/v/opencode-goal-mode?color=2da44e)](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
+ ![OpenCode Goal Mode sidebar banner](docs/sidebar-demo.svg)
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) and a single
115
- "completion unlocked" toast the moment the last required gate clears.
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 globally
163
+ ## Install
164
+
165
+ ### From npm (recommended)
144
166
 
145
167
  ```bash
146
- npm ci
147
- npm run validate
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
- Restart OpenCode after installation. OpenCode loads agents, commands, and
152
- plugins at startup.
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
- ## Install into one project
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
- ## npm publishing
289
+ ## Releasing
257
290
 
258
- Install from npm after the first publish:
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 install -g opencode-goal-mode
262
- opencode-goal-mode-install --global
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
- Publishing is handled by `.github/workflows/publish.yml`, which runs on Node 24
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
- Release flow for a new version:
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
- ```bash
274
- npm version patch
275
- git push --follow-tags
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
- For a version that is already bumped and reviewed, commit the current tree, tag
279
- the reviewed version (for example `v0.2.4`), push the branch and tag, then create
280
- the GitHub Release. Ensure `NPM_TOKEN` has npm publish rights before publishing
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.1",
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
- "review",
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
+ }
@@ -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(`Goal Guard: ${recordedAgent} → ${recordedVerdict}`, recordedVerdict === "PASS" ? "success" : "warning");
213
+ logger.toast(`${prettyAgentName(recordedAgent)} → ${recordedVerdict}`, recordedVerdict === "PASS" ? "success" : "warning");
214
214
  if (!wasAllowed && completionAllowed(state, config)) {
215
- logger.toast("Goal Guard: all required gates passed — completion unlocked", "success");
215
+ logger.toast("All required gates passed — completion unlocked", "success");
216
216
  }
217
217
  }
218
218
  persist();