opencode-goal-mode 0.3.9 → 0.3.11

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/ARCHITECTURE.md CHANGED
@@ -188,8 +188,9 @@ progress is visible even without the banner.
188
188
  The JSX renderer is verified headlessly with `@opentui/solid`'s `testRender` in
189
189
  `tools/visual-test/sidebar-visual.jsx` (`npm run test:visual`, needs Bun + the
190
190
  OpenTUI stack): it asserts the rendered text, the exact foreground colours, and
191
- the bold attribute for Goal todo / done / native-todo-preserved states. That tool is excluded from
192
- the npm package and from `node --test`/CI.
191
+ the bold attribute for Goal todo / done / native-todo-preserved states. That tool
192
+ is excluded from the npm package and from `node --test`; the GitHub CI workflow
193
+ runs it in a separate Bun/OpenTUI job.
193
194
 
194
195
  ## Configuration
195
196
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.11
4
+
5
+ - Fixed Goal sidebar/status isolation so an explicit Build or other non-Goal
6
+ session never falls back to another active Goal session in the same worktree.
7
+ - Blocked mutating `goal_*` tools from activating Goal Guard state in non-Goal
8
+ sessions; read-only tools remain strictly scoped to the current session.
9
+ - Added regression coverage for mixed Goal/Build persisted snapshots, session-scoped
10
+ status/evidence/memory reads, and Build-mode tool calls.
11
+
12
+ ## v0.3.10
13
+
14
+ - Clarified the recommended install command to use a persistent global npm install
15
+ before running the installer, so OpenCode can resolve the TUI package on future
16
+ starts. `npx` remains documented for temporary installs/server-side checks.
17
+ - Added the missing historical `v0.3.8` changelog section. `v0.3.8` reached npm,
18
+ but its GitHub Release workflow failed while generating release notes, so it was
19
+ superseded by `v0.3.9`.
20
+
3
21
  ## v0.3.9
4
22
 
5
23
  - Installer docs and `--help` now put the one-command `npx opencode-goal-mode --global`
@@ -12,6 +30,12 @@
12
30
  - Destructive-command blocking no longer activates Goal enforcement for Build or
13
31
  other non-Goal sessions, preventing non-Goal tasks from being classified as goals.
14
32
 
33
+ ## v0.3.8
34
+
35
+ - Superseded release: npm publish succeeded, but the GitHub Release workflow failed
36
+ because the changelog section was still named `Unreleased`. The same functional
37
+ changes shipped correctly in `v0.3.9` with matching npm and GitHub releases.
38
+
15
39
  ## v0.3.7
16
40
 
17
41
  - **FIX: the sidebar now actually loads.** OpenCode loads a TUI plugin via the
package/README.md CHANGED
@@ -17,14 +17,15 @@ TUI sidebar.
17
17
  **One command** (recommended; needs [Node](https://nodejs.org) 20.11+ and a working [OpenCode](https://opencode.ai) install):
18
18
 
19
19
  ```bash
20
- npx opencode-goal-mode --global
20
+ npm install -g opencode-goal-mode && opencode-goal-mode --global
21
21
  ```
22
22
 
23
23
  Then **restart OpenCode**. That's the whole install: it copies the Goal agent,
24
24
  review subagents, slash commands, and guard plugin into `~/.config/opencode`, and
25
25
  merge-safely registers the Goal todo sidebar in `~/.config/opencode/tui.json`.
26
26
  In the agent picker you'll see only the **`goal`** agent; reviewers are subagents
27
- it drives automatically. Goal Mode inherits your existing OpenCode model/provider.
27
+ it drives automatically. The global install keeps the TUI package resolvable on
28
+ future OpenCode starts; Goal Mode inherits your existing OpenCode model/provider.
28
29
 
29
30
  <details>
30
31
  <summary>Other ways to install</summary>
@@ -34,6 +35,10 @@ it drives automatically. Goal Mode inherits your existing OpenCode model/provide
34
35
  npm install -g opencode-goal-mode
35
36
  opencode-goal-mode --global # alias of opencode-goal-mode-install
36
37
 
38
+ # Temporary npx install (server-side components work; for the TUI sidebar,
39
+ # prefer the global install above so OpenCode can resolve the package later)
40
+ npx opencode-goal-mode --global
41
+
37
42
  # Into a single project (writes ./.opencode, including ./.opencode/tui.json)
38
43
  npx opencode-goal-mode
39
44
 
@@ -253,6 +258,7 @@ enforcement and writes its state to disk, and an experimental TUI plugin
253
258
  ## Installer options
254
259
 
255
260
  ```bash
261
+ npm install -g opencode-goal-mode && opencode-goal-mode --global
256
262
  npx opencode-goal-mode --global --dry-run
257
263
  npx opencode-goal-mode --global
258
264
  opencode-goal-mode-install --global --uninstall
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-goal-mode",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Strict Goal Mode agents, commands, and guard plugin for OpenCode.",
5
5
  "type": "module",
6
6
  "main": "plugins/goal-sidebar.tsx",
@@ -30,8 +30,10 @@ function normalize(record) {
30
30
  }
31
31
 
32
32
  /**
33
- * Choose which session's goal to show: the most-recently-touched ACTIVE session
34
- * (optionally preferring an explicit sessionId when it is present and active).
33
+ * Choose which session's goal to show. When OpenCode gives us a concrete session
34
+ * id, never fall back to another session: Build/non-Goal sessions must not show a
35
+ * Goal from the same worktree. The most-recent active fallback is only for
36
+ * no-session contexts such as initial sidebar registration polling.
35
37
  */
36
38
  export function pickSession(snapshot, sessionId) {
37
39
  if (!snapshot || !Array.isArray(snapshot.sessions)) return null;
@@ -41,6 +43,7 @@ export function pickSession(snapshot, sessionId) {
41
43
  if (sessionId) {
42
44
  const direct = records.find(([key, st]) => key === sessionId && st.active);
43
45
  if (direct) return direct[1];
46
+ return null;
44
47
  }
45
48
  const active = records.filter(([, st]) => st.active);
46
49
  if (active.length === 0) return null;
@@ -16,6 +16,7 @@ import { evidenceMapReport, reviewerMemoryReport, statusReport } from "./summary
16
16
  import { recordEvidence } from "./events.js";
17
17
  import { refreshStickyGates } from "./gates.js";
18
18
  import { createState } from "./state.js";
19
+ import { isPrimaryAgent } from "./agents.js";
19
20
 
20
21
  const s = tool.schema;
21
22
 
@@ -28,6 +29,18 @@ const s = tool.schema;
28
29
  export function createGoalTools({ store, config, persist }) {
29
30
  const save = typeof persist === "function" ? persist : () => {};
30
31
 
32
+ function requireGoalMode(state) {
33
+ return Boolean(state?.active || isPrimaryAgent(state?.currentAgent));
34
+ }
35
+
36
+ function goalModeOnlyResult() {
37
+ return {
38
+ title: "Goal Mode required",
39
+ output: "This goal_* tool can only mutate Goal Guard state from an active Goal session. Switch to the `goal` agent or start with /goal.",
40
+ metadata: { blocked: true, reason: "not_goal_mode" },
41
+ };
42
+ }
43
+
31
44
  return {
32
45
  goal_status: tool({
33
46
  description:
@@ -109,6 +122,7 @@ export function createGoalTools({ store, config, persist }) {
109
122
  },
110
123
  async execute(args, ctx) {
111
124
  const state = store.stateFor(ctx.sessionID);
125
+ if (!requireGoalMode(state)) return goalModeOnlyResult();
112
126
  state.active = true;
113
127
  state.contract = {
114
128
  title: String(args.title || "").replace(/\s+/g, " ").trim(),
@@ -145,6 +159,7 @@ export function createGoalTools({ store, config, persist }) {
145
159
  },
146
160
  async execute(args, ctx) {
147
161
  const state = store.stateFor(ctx.sessionID);
162
+ if (!requireGoalMode(state)) return goalModeOnlyResult();
148
163
  state.active = true;
149
164
  recordEvidence(store, state, args.command, args.result, args.criteria);
150
165
  save();
@@ -164,6 +179,8 @@ export function createGoalTools({ store, config, persist }) {
164
179
  confirm: s.boolean().describe("Must be true to actually reset."),
165
180
  },
166
181
  async execute(args, ctx) {
182
+ const state = store.stateFor(ctx.sessionID);
183
+ if (!requireGoalMode(state)) return goalModeOnlyResult();
167
184
  if (!args.confirm) {
168
185
  return { title: "Reset not confirmed", output: "Pass confirm=true to reset Goal Guard state." };
169
186
  }