@herjarsa/omo-meta-governor 0.26.0 → 0.26.2

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/README.md CHANGED
@@ -1,8 +1,43 @@
1
1
  # @herjarsa/omo-meta-governor
2
2
 
3
- Self-judging agent orchestration layer for OpenCode. Observes tool executions,
4
- reads session state, scores progress, and dispatches decisions. Includes **9 custom tools**
5
- that the agent can invoke across CodeGraph, Graphify, AgentMemory, and SQLite.
3
+ > Self-judging agent orchestration layer for [OpenCode](https://opencode.ai).
4
+ > Observes tool executions, scores progress, dispatches decisions, and exposes
5
+ > **12 custom tools** the agent can invoke across CodeGraph, Graphify,
6
+ > AgentMemory, and SQLite — for cheaper, more accurate code understanding.
7
+
8
+ **Current version:** `0.26.0` · **License:** MIT · **Status:** stable
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ - [Install](#install)
15
+ - [What it does](#what-it-does)
16
+ - [12 Custom Tools](#12-custom-tools)
17
+ - [Code search & navigation](#code-search--navigation)
18
+ - [Lesson & memory](#lesson--memory)
19
+ - [File & symbol lookup](#file--symbol-lookup)
20
+ - [Safety & status](#safety--status)
21
+ - [Governance pipeline](#governance-pipeline)
22
+ - [Scoring engine](#scoring-engine)
23
+ - [Intervention modes](#intervention-modes)
24
+ - [Protocol enforcement](#protocol-enforcement)
25
+ - [Skill priming](#skill-priming)
26
+ - [Multi-phase plans](#multi-phase-plans)
27
+ - [Graph sync (codegraph + graphify)](#graph-sync-codegraph--graphify)
28
+ - [Auto-init](#auto-init)
29
+ - [Auto-upgrade (v0.26.0)](#auto-upgrade-v0260)
30
+ - [Git hooks](#git-hooks)
31
+ - [Process safeguards](#process-safeguards)
32
+ - [Persistence & observability](#persistence--observability)
33
+ - [CI monitor (v0.25.0)](#ci-monitor-v0250)
34
+ - [Configuration reference](#configuration-reference)
35
+ - [Architecture overview](#architecture-overview)
36
+ - [Testing](#testing)
37
+ - [Migration from earlier versions](#migration-from-earlier-versions)
38
+ - [License](#license)
39
+
40
+ ---
6
41
 
7
42
  ## Install
8
43
 
@@ -10,9 +45,7 @@ that the agent can invoke across CodeGraph, Graphify, AgentMemory, and SQLite.
10
45
  npm install @herjarsa/omo-meta-governor
11
46
  ```
12
47
 
13
- ## Usage
14
-
15
- Add as a plugin in your OpenCode config:
48
+ Add as a plugin in your OpenCode config (`~/.config/opencode/opencode.jsonc`):
16
49
 
17
50
  ```jsonc
18
51
  {
@@ -20,156 +53,165 @@ Add as a plugin in your OpenCode config:
20
53
  }
21
54
  ```
22
55
 
23
- The 9 custom tools register automatically (even without setting enabled:true).
24
- To also enable the governance pipeline (intervention, protocol enforcement):
56
+ The 12 custom tools register automatically on every load. To also enable
57
+ the governance pipeline (scoring, intervention, protocol enforcement):
25
58
 
26
59
  ```jsonc
27
60
  {
28
61
  "meta_governor": {
29
62
  "enabled": true,
30
- "intervention": {
31
- "mode": "message",
32
- "minActionForMessage": "warn"
33
- }
63
+ "intervention": { "mode": "message", "minActionForMessage": "warn" }
34
64
  }
35
65
  }
36
66
  ```
37
67
 
38
- ## 9 Custom Tools
68
+ ---
69
+
70
+ ## What it does
71
+
72
+ `omo-meta-governor` is a single OpenCode plugin that ships **five
73
+ interconnected subsystems**:
74
+
75
+ | Subsystem | Purpose | Surface |
76
+ |---|---|---|
77
+ | **Graph sync** | Auto-install codegraph + graphify, build initial index, wire git hooks, auto-upgrade binaries on every load | `graphSync.*` config |
78
+ | **12 custom tools** | Semantic code search, impact analysis, symbol lookup, lesson recall, file/caller/node queries, health | `omo_*` tools |
79
+ | **Governance pipeline** | Score session progress → dispatch decision (`continue` / `warn` / `escalate` / `stop`) → optionally inject it into the agent's context | `meta_governor.enabled` |
80
+ | **Memory + lessons** | Persist decisions and lessons in SQLite (FTS5) + bridge to AgentMemory for cross-session recall | `omo_recall`, `omo_remember`, `omo_recall_mcp` |
81
+ | **Observability** | Health JSON, rotating JSONL logs, metrics, audit state, CI monitor | `omo_health`, `~/.config/opencode/meta-governor-health.json` |
82
+
83
+ All five run **inside the plugin** — no daemon, no sidecar. They share
84
+ process boundaries, lifecycle, and the opencode event hooks
85
+ (`tool.execute.before` / `tool.execute.after` / `chat.messages.transform`).
39
86
 
40
- The plugin registers 9 tools the LLM can invoke. All available immediately on install.
87
+ ---
88
+
89
+ ## 12 Custom Tools
41
90
 
42
- ### Code Search & Navigation
91
+ The plugin registers 12 tools the LLM can invoke. All are available
92
+ immediately on install (no `enabled: true` required for tools — only the
93
+ governance pipeline needs `meta_governor.enabled: true`).
94
+
95
+ ### Code search & navigation
43
96
 
44
97
  | Tool | What it does | Use case |
45
- |------|-------------|----------|
46
- | `omo_search` | Semantic code search via codegraph/graphify | Architecture questions, finding features — USE THIS FIRST |
47
- | `omo_find` | Exact symbol lookup (definition + direct callers) via codegraph node | "Find the function `validateToken`" |
48
- | `omo_impact` | Impact analysis: callers, transitive callers, test files, doc files | Run BEFORE modifying a function |
98
+ |------|--------------|----------|
99
+ | `omo_search` | Semantic code search via codegraph or graphify | "Where is authentication handled?" USE THIS FIRST for any architecture question |
100
+ | `omo_find` | Exact symbol lookup (definition + direct callers) via `codegraph node` | "Find the function `validateToken`" |
101
+ | `omo_impact` | Impact analysis: direct + transitive callers, test files, doc files | Run BEFORE modifying a function |
49
102
  | `omo_path` | Shortest conceptual path between two concepts via graphify | "How does auth connect to database?" |
50
103
  | `omo_explain` | Plain-language explanation of a concept via graphify | "What is the SwinTransformer?" |
51
104
 
52
- ### Lesson & Memory
105
+ ### Lesson & memory
53
106
 
54
107
  | Tool | What it does | Use case |
55
- |------|-------------|----------|
108
+ |------|--------------|----------|
56
109
  | `omo_recall` | Search past lessons via local SQLite FTS5 (fast, always available) | "How did we set up auth before?" |
57
110
  | `omo_recall_mcp` | Search cross-session memory via AgentMemory | "What did we learn about X in previous sessions?" |
58
- | `omo_remember` | Save a fact/observation to cross-session AgentMemory | "Remember this bug pattern for next time" |
111
+ | `omo_remember` | Save a fact / observation / pattern to cross-session AgentMemory | "Remember this bug pattern for next time" |
59
112
 
60
- ### Rules & Notes
113
+ ### File & symbol lookup
61
114
 
62
115
  | Tool | What it does | Use case |
63
- |------|-------------|----------|
116
+ |------|--------------|----------|
117
+ | `omo_files` | List files indexed by codegraph or graphify | "What files are in the graph?" |
118
+ | `omo_callers` | List all call sites of a symbol via `codegraph callers` | "Who calls `UserService.create`?" |
119
+ | `omo_node` | Get source + direct callers of a symbol via `codegraph node` | "Show me the source of `validateToken` and its callers" |
64
120
 
65
- ### Safety & Status
121
+ ### Safety & status
66
122
 
67
123
  | Tool | What it does | Use case |
68
- |------|-------------|----------|
124
+ |------|--------------|----------|
69
125
  | `omo_health` | Show plugin runtime status: metrics, decisions, errors | "Is the plugin working?" |
70
126
 
71
- ## Health & Observability
72
-
73
- The plugin exposes a health JSON file at `~/.config/opencode/meta-governor-health.json`:
74
-
75
- ```bash
76
- cat ~/.config/opencode/meta-governor-health.json
77
- ```
78
-
79
- Or the agent can call `omo_health` directly to get a formatted report.
80
-
81
- Structured JSONL logs at `~/.config/opencode/meta-governor.log` with size-based rotation
82
- (10MB max, 5 rotated files).
83
-
84
- ## Persistence
85
-
86
- Lessons learned by the plugin persist in **SQLite** at `~/.omo-meta-governor/meta-governor.db`
87
- with full-text search (FTS5) for fast recall. Zero dependencies needed — uses Bun's built-in
88
- `bun:sqlite`.
89
-
90
- Optionally, the Opción A tools (`omo_remember`, `omo_recall_mcp`) can bridge to AgentMemory via `session.prompt()` — the LLM
91
- receives a structured instruction to call the appropriate MCP tool.
127
+ All tools return a typed `ToolResult` with `title`, `output`, and
128
+ `metadata` (`{tool, kind, durationMs, sessionID}`). They degrade
129
+ gracefully when codegraph or graphify is missing, they return a
130
+ **friendly hint** (e.g. `npx codegraph init` to recover) instead of
131
+ crashing.
92
132
 
93
- ## Graph Sync (v0.11.0)
133
+ ---
94
134
 
95
- MetaGovernor wires the plugin into the native git hooks of **codegraph** and
96
- **graphify** so each commit automatically reindexes both graphs.
135
+ ## Governance pipeline
97
136
 
98
- ### What it does on first load in a project
137
+ When `meta_governor.enabled: true`, the plugin attaches to opencode's
138
+ tool-execution stream and runs an **observe → score → decide → (optionally)
139
+ intervene** loop on every turn.
99
140
 
100
- 1. **Auto-install** codegraph via `npm i -D @colbymchenry/codegraph` and
101
- graphify via `pip install graphifyy` (falls back to `uv tool install
102
- graphifyy`) if they're not already on PATH.
103
- 2. **Run `codegraph init`** + **`graphify . --no-viz`** to build the initial
104
- indexes for the project.
105
- 3. **Run `graphify hook install`** to wire up the native `post-commit` and
106
- `post-checkout` git hooks.
141
+ ### Scoring engine
107
142
 
108
- ### What it does on each `git commit`
143
+ `src/scoring-engine.ts` computes a single composite score in `[-1, 1]`
144
+ from weighted signals:
109
145
 
110
- - **Primary path** (native git hook): `graphify update` runs in background.
111
- - **Backup path** (plugin's `tool.execute.after`): detects `git commit` in
112
- bash commands and runs `codegraph sync -q [path]`.
146
+ | Signal | Weight | Source |
147
+ |--------|--------|--------|
148
+ | `progress-detector` | 0.30 | did the last 5 tool calls make forward progress? |
149
+ | `deviation-detector` | 0.20 | accumulated protocol violations (capped at 5/session) |
150
+ | `no-progress-detector` | 0.20 | is the agent reading without writing? |
151
+ | `iteration-budget` | 0.15 | are we approaching `maxIterations`? |
152
+ | `oracle-burn` | 0.10 | did recent oracle calls detect issues? |
153
+ | `stop-advice` | 0.05 | did prior lessons recommend stop? |
113
154
 
114
- ### Process zombie safeguards (v0.22.0)
155
+ The score maps to an action via configurable thresholds (see
156
+ [Configuration reference](#configuration-reference)):
115
157
 
116
- Every subprocess the plugin spawns (graphify, codegraph, npx, python,
117
- npm/pip) is guaranteed to die after use on success, error, AND timeout —
118
- including its descendant tree. On Windows this uses `taskkill /pid <pid> /T /F`
119
- (plain `child.kill()` only kills the direct shell, orphaning grandchildren —
120
- the confirmed cause of the Bun/OpenChamber crashes).
158
+ - `score continueThreshold` **continue** (silent)
159
+ - `score -warnThreshold` **warn** (log + nudge)
160
+ - `score -escalateThreshold` **escalate** (block + inject)
161
+ - `score -stopThreshold` **stop** (latch intervention)
121
162
 
122
- Config: `graphSync.killOrphanedOnInit` (default `true`) — on graph-sync init
123
- the plugin sweeps orphaned `graphify`/`codegraph` processes left by
124
- previous crashed runs. Set to `false` to disable the sweep.
163
+ Default thresholds: `continue: 0.05`, `warn: 0.3`, `escalate: 0.45`,
164
+ `stop: 0.55` (worst-case math gives `stop -0.55`, so it actually
165
+ fires verified via Gap C audit).
125
166
 
126
- ## Intervention
167
+ ### Intervention modes
127
168
 
128
- MetaGovernor can inject governance decisions into the agent's context.
129
- Enabled when `meta_governor.enabled: true` in config.
130
-
131
- ### Modes
169
+ When the decision is `warn` / `escalate` / `stop`, the plugin can inject
170
+ the rationale into the agent's context via `experimental.chat.messages.transform`:
132
171
 
133
172
  | Mode | Mechanism | Effect |
134
173
  |------|-----------|--------|
135
174
  | `silent` | (none) | Decision is logged only |
136
- | `message` | `experimental.chat.messages.transform` | Injects a synthetic user message visible to the LLM |
137
- | `system` | `experimental.chat.system.transform` | Appends guidance to the system prompt |
175
+ | `message` | `chat.messages.transform` | Injects a synthetic user message visible to the LLM |
176
+ | `system` | `chat.system.transform` | Appends guidance to the system prompt |
177
+
178
+ `maxInterventionsPerSession: 3` (default) hard-stops injection after 3
179
+ interventions per session to prevent infinite instruction loops
180
+ (v0.10.0). When `respectDoneSignal: true` (default), injection stops
181
+ once the agent emits the terminal signal AND Oracle has verified.
182
+
183
+ ### Protocol enforcement
138
184
 
139
- ### Configuration
185
+ `src/protocol-enforcer.ts` audits tool calls against a configurable
186
+ protocol markdown file. Use it to enforce rules like "do not save
187
+ routine operations to memory" or "always invoke Oracle before declaring
188
+ done".
140
189
 
141
190
  ```jsonc
142
191
  {
143
192
  "meta_governor": {
144
193
  "enabled": true,
145
- "intervention": {
146
- "mode": "message",
147
- "minActionForMessage": "warn",
148
- "maxInterventionsPerSession": 3,
149
- "respectDoneSignal": true,
150
- "phaseAwareDoneSignal": true // v0.15.0: multi-phase plan support
194
+ "protocolEnforcement": {
195
+ "enabled": true,
196
+ "path": "./PROTOCOL.md",
197
+ "injectIntoSystem": true,
198
+ "auditToolCalls": true
151
199
  }
152
200
  }
153
201
  }
154
202
  ```
155
203
 
156
- ### Fields
157
-
158
- | Field | Default | Description |
159
- |-------|---------|-------------|
160
- | `mode` | `"message"` | How to inject: `"silent"`, `"message"`, or `"system"` |
161
- | `minActionForMessage` | `"warn"` | Minimum action: `"warn"`, `"escalate"`, or `"stop"` |
162
- | `maxInterventionsPerSession` | `3` | Hard cap on injections per session |
163
- | `respectDoneSignal` | `true` | Stop injecting after terminal signal + Oracle verified |
164
- | `phaseAwareDoneSignal` | `false` | **v0.15.0**: when `true`, only `<promise>PLAN-COMPLETE</promise>` latches intervention. DONE/PHASE-N-COMPLETE are per-phase hints. Recommended for multi-phase plans. |
204
+ Violations accumulate in `state.accumulatedDeviations` (capped at 5 per
205
+ session) and feed the `deviation-detector` scoring signal.
165
206
 
166
- ## Skill Priming (v0.20.0)
207
+ ### Skill priming
167
208
 
168
- Proactive skill-selection nudge: the plugin injects **one** synthetic user message at session
169
- start (or once implementation work begins) prompting the agent to select precise skills for the
170
- task via the **AAS skill catalog** (`aas search_skills` / `get_skill` / `compose_stack`) and/or
171
- the task-appropriate **superpowers** skill before writing code. Minimal context cost: the
172
- directive forbids enumerating the full catalog.
209
+ `src/skill-priming.ts` (v0.20.0) injects **one** synthetic user message
210
+ at session start (or once implementation work begins) prompting the
211
+ agent to select precise skills for the task via the AAS skill catalog
212
+ (`aas search_skills` / `get_skill` / `compose_stack`) and/or the
213
+ task-appropriate superpowers skill before writing code. Minimal context
214
+ cost: the directive forbids enumerating the full catalog.
173
215
 
174
216
  ```jsonc
175
217
  {
@@ -177,623 +219,394 @@ directive forbids enumerating the full catalog.
177
219
  "enabled": true,
178
220
  "skillPriming": {
179
221
  "enabled": true,
180
- "trigger": "sessionStart", // or "firstImplement" (default)
181
- "router": "both" // "aas", "superpowers", or "both"
222
+ "trigger": "firstImplement",
223
+ "router": "both"
182
224
  }
183
225
  }
184
226
  }
185
227
  ```
186
228
 
187
- | Field | Default | Description |
188
- |-------|---------|-------------|
189
- | `enabled` | `false` | Master switch for the skill-priming nudge |
190
- | `trigger` | `"firstImplement"` | `"sessionStart"`: first transform call of the session. `"firstImplement"`: once a write/edit-like tool is observed |
191
- | `router` | `"both"` | Which system(s) the directive references: `"aas"`, `"superpowers"`, `"both"` |
192
-
193
- ### Multi-phase plans (v0.15.0)
229
+ ### Multi-phase plans
194
230
 
195
- For work plans with multiple phases (e.g. Sisyphus/Prometheus work plans),
196
- configure `phaseAwareDoneSignal: true` and emit `<promise>PLAN-COMPLETE</promise>`
197
- only when the **entire** plan is verified done by Oracle. The new markers:
231
+ For work plans with multiple phases (e.g. Sisyphus/Prometheus work
232
+ plans), set `phaseAwareDoneSignal: true` and emit
233
+ `<promise>PLAN-COMPLETE</promise>` only when the **entire** plan is
234
+ verified done by Oracle.
198
235
 
199
236
  | Marker | Effect |
200
237
  |--------|--------|
201
238
  | `<promise>DONE</promise>` | Per-phase hint. Logged but does NOT latch intervention (when `phaseAwareDoneSignal: true`). |
202
- | `<promise>PHASE-N-COMPLETE</promise>` | Per-phase hint (e.g. `<promise>PHASE-1-COMPLETE</promise>`). Same as DONE — logged, does NOT latch. |
239
+ | `<promise>PHASE-N-COMPLETE</promise>` | Per-phase hint (e.g. `<promise>PHASE-1-COMPLETE</promise>`). Same as `DONE`. |
203
240
  | `<promise>PLAN-COMPLETE</promise>` | Terminal. Latches intervention when Oracle has verified. |
204
241
 
205
- **Migration**: existing v0.10.0–v0.14.x users keep working without changes (default
206
- `phaseAwareDoneSignal: false` preserves the legacy single-task behavior). Set the
207
- flag to `true` and switch your terminal marker to `PLAN-COMPLETE` to enable
208
- multi-phase governance.
209
-
210
- ## v0.16.0 — Audit remediation: memory hygiene, dead code, tool coverage, CI
211
-
212
- v0.16.0 closes the 50+ findings from the multi-front audit at `.omo/ulw-research/20260727-000530/plan-audit-v0.15.0.md`. The release is **additive in behavior, no breaking API changes** for users — only internal cleanup, dead code removal, and CI hardening.
213
-
214
- ### Highlights
215
-
216
- #### Memory hygiene (F1)
217
-
218
- - **`AuditStateCache`** (`src/audit-state-cache.ts`) — TTL+LRU bounded cache (100 entries, 1h TTL) replaces the bare `Map` that accumulated audit state without bounds. Stale sessions are evicted automatically.
219
- - **`TTLQueue`** (`src/ttl-queue.ts`) — TTL-based expiration for `pendingBotFeedback` and `pendingViolations` queues. Previously unbounded.
220
- - Removed dynamic `require("node:fs")` inside `shouldInjectPlanReminder` — replaced with static ESM imports (no more runtime module resolution failures).
221
-
222
- #### Dead code elimination (F2)
223
-
224
- - `takeAnyDecision()` — deprecated; removed from the active governance pipeline.
225
- - `systemInjection` — now awaited eagerly instead of fire-and-forget, eliminating a silent failure route.
226
- - `logToFile` in `graph-sync.ts` — wired to the real JSONL file logger (was a no-op stub).
227
- - Plugin version — derived from `package.json` at runtime instead of hardcoded "0.13.0" (closes the version-drift bug where `omo_health` reported stale versions).
228
-
229
- #### Tool bug fixes (F3)
230
-
231
- - **AFT checkpoint/undo**: args split on whitespace broke names with spaces. Rewrote arg construction with proper quoting.
232
- - **AFT subcommand**: now uses `options.projectDir` instead of `process.cwd()`.
233
- - **graphify binary override**: `omo_path` / `omo_explain` honored the `graphifyBin` option (was hardcoded).
234
- - **`as never` cast** on `setClient` → proper runtime guard that validates client shape.
235
- - **`session-bridge`**: replaced module-level `_client` with `AsyncLocalStorage` for per-request isolation. Concurrent sessions no longer race on the same client reference.
236
-
237
- #### Test coverage (F4)
238
-
239
- - 22 tests covering all 15 custom tools (`src/custom-tools.test.ts`). Previously the entire public tool surface had zero test coverage.
240
- - 12 tests for `decision-store` (previously untested).
241
-
242
- #### Type/token pipeline (F5)
243
-
244
- - `token-predictor` refactor: dead code (`delegate`/`switch-model`) removed; output is now informational-only as designed.
245
- - Type alignment across `types.ts`, `token-predictor.ts`, `orchestrator.ts`.
246
-
247
- #### CI matrix (F6)
248
-
249
- - `bun run typecheck` now runs on **macos-latest** and **windows-latest** (was Ubuntu-only).
250
- - Removed `package-lock.json` (bun project — canonical is `bun.lock`).
251
- - Secret redaction layer in `logToFile` (JWT, OpenAI keys, Bearer tokens, GitHub PATs, generic key:value patterns).
252
- - Implementation plan renamed `IMPLEMENTATION_PLAN.md` → `ARCHITECTURE.md`.
253
-
254
- #### Final refactors (F7)
255
-
256
- - Score formula documented (header doc with full formula spec).
257
- - **NaN guard** in `score()` — defaults to neutral continue when `iterationRatio` or `ambient.iteration/maxIterations` produce NaN.
258
- - `ACTION_SEVERITY` keyed by `DecisionHandlerOutput["action"]` union literal (was bare `Record<string, number>`).
259
- - `projectHasCodegraph` / `projectHasGraphify` IIFE booleans replaced with lookup-time calls to `graphRetrieval.hasCodegraphDir(cwd)`.
260
- - `extractConcepts` includes file basename for FTS lookup by tool/file name.
261
- - Backup graph-sync uses `triggerReindex` (was `triggerCodegraphSync`) — reindexes both codegraph AND graphify backends.
262
-
263
- ### Test & build status
264
-
265
- - **495/495 tests pass** (up from 487 in v0.15.0/0.15.1).
266
- - `bun run typecheck` clean.
267
- - `bun build.ts` clean (0.34 MB dist).
268
- - `npm pack --dry-run` validated (no forbidden artifacts).
269
-
270
- ### Migration
271
-
272
- No user action required. All changes are internal. The default `phaseAwareDoneSignal` is still `false` for backward compatibility; the v0.15.0 multi-phase behavior is preserved when explicitly enabled.
273
-
274
- ### Deferred to v0.17.0
275
-
276
- - F5.1 — wiring `escalate` action to a real dispatcher (Oracle is recommended but not yet wired).
277
- - F5.4 — `maxLessonsPerSession` enforcement (config field exists but is not enforced).
278
- - F3.6 — Bridge tools lying about delivery (5 tools still return "dispatched" without polling). Recommend the user explicitly request this if delivery verification is critical.
279
-
280
-
281
-
282
-
283
- ## v0.17.0 — Wire escalate to Oracle, enforce lesson cap, verify bridge delivery
284
-
285
- v0.17.0 closes the 3 deferred items from the v0.16.0 audit: **F5.1** (escalate → Oracle), **F5.4** (`maxLessonsPerSession` enforcement), and **F3.6** (bridge tool delivery verification).
286
-
287
- ### Highlights
288
-
289
- #### F5.1 — Escalate action now fires Oracle (v0.17.0)
290
-
291
- When the scoring engine produces an `escalate` action with target `oracle`, the plugin's `tool.execute.after` hook now fires a `session.prompt()` instructing the LLM to invoke `task(subagent_type=oracle)`. The prompt includes the decision reasoning, evidence count, and a verification pass directive. New `buildEscalationPrompt()` function in `session-bridge.ts` is the pure prompt builder (testable in isolation). User-targeted escalations get a separate prompt asking the LLM to summarize for human input.
292
-
293
- ```ts
294
- // Decision flow when score lands in escalate band:
295
- score ≤ -escalateThreshold (default -0.6)
296
- → decision.action = "escalate"
297
- → decision.shouldEscalateTo = "oracle" (or "user" for grave deviations)
298
- → plugin fires session.prompt with buildEscalationPrompt(...)
299
- → LLM invokes Oracle (or summarizes for user)
300
- → Oracle verifies → oracleInvoked=true → governance continues
301
- ```
242
+ ---
302
243
 
303
- #### F5.4 — `maxLessonsPerSession` is now enforced
244
+ ## Graph sync (codegraph + graphify)
304
245
 
305
- The cap (default 20) was a config field that was never enforced. v0.17.0 adds:
306
- - `currentLessonCount` on `LearnFromOutcomeInput` and `MetaGovernorInput`
307
- - `lessonCount` tracked in per-session `AuditState`
308
- - `observeAndLearn()` short-circuits when `currentLessonCount >= maxLessonsPerSession`
309
- - The orchestrator increments `sessionState.lessonCount` after each successful save
310
- - **Cap semantics: inclusive** — when count equals cap, no more lessons are saved
246
+ The plugin wires the native git hooks of **codegraph** and **graphify**
247
+ so each commit automatically reindexes both graphs.
311
248
 
312
- #### F3.6 — Bridge tool delivery verification
249
+ ### Auto-init
313
250
 
314
- The 5 bridge tools (`omo_remember`, `omo_recall_mcp`, `omo_rule`, `omo_history`, `omo_note`) previously returned "dispatched" after the `session.prompt()` was queued — without verifying the LLM actually called the MCP tool. v0.17.0 adds:
251
+ On first load in a project (when `graphSync.enabled: true`, default):
315
252
 
316
- - **New `PendingDeliveryRegistry` module** (`src/delivery-registry.ts`) — tracks pending dispatches per session with TTL-based cleanup.
317
- - **`tool.execute.after` hook** marks deliveries when a matching MCP tool call is observed.
318
- - **All 5 bridge tools** now report `deliveryStatus: "delivered" | "pending"` in their tool result and metadata, and briefly poll (1.5s) for fast deliveries.
319
- - When the LLM follows the prompt, the tool returns immediately with `"delivered"`. When it doesn't, the tool returns `"pending"` and the entry expires silently after 10s.
253
+ 1. **Auto-install** codegraph via `npm i -D @colbymchenry/codegraph` and
254
+ graphify via `pip install graphifyy` (falls back to
255
+ `uv tool install graphifyy`) if not already on PATH.
256
+ 2. **Run `codegraph init`** + **`graphify . --no-viz`** to build the
257
+ initial indexes for the project.
258
+ 3. **Run `graphify hook install`** to wire up the native `post-commit`
259
+ and `post-checkout` git hooks.
260
+
261
+ ### Auto-upgrade (v0.26.0)
262
+
263
+ Before v0.26.0, `autoUpgrade: true` (default) silently failed. Six
264
+ bugs in `src/graph-sync.ts:503-628` forced users to manually run
265
+ `npm install -g @colbymchenry/codegraph@latest` and
266
+ `pip install --upgrade graphifyy`.
267
+
268
+ **Root cause bugs fixed in v0.26.0:**
269
+
270
+ 1. `getInstalledCodegraphVersion` only probed `npx` — failed when the
271
+ binary was at `node_modules/.bin/codegraph` (Windows users).
272
+ 2. `getInstalledGraphifyVersion` had no DI runner — Windows dual-python
273
+ fallback was untestable.
274
+ 3. `shouldUpgrade` ignored the cache value (`latest=null`).
275
+ 4. Cache cold + undetectable binary → silent noop (no diagnostic code).
276
+ 5. **`pip install` without `--upgrade` returned 0** with
277
+ "Requirement already satisfied" but **did NOT upgrade** — most
278
+ visible bug.
279
+ 6. `graphify check-update` was ignored — semantic re-extraction flag
280
+ never triggered.
281
+
282
+ **Fixes:**
283
+
284
+ - Tiered probe matching `checkToolAvailability`: `npx` +
285
+ `node node_modules/.bin/codegraph` for codegraph; `graphify` →
286
+ `python -m pip show` → `python3 -m pip show` for graphify.
287
+ - Runner DI seam on `getInstalledCodegraphVersion`,
288
+ `getInstalledGraphifyVersion`, `installCodegraph`, `installGraphify`
289
+ — hermetic tests, no real network in CI.
290
+ - `resolveLatest()` inlines cache into `shouldUpgrade` — avoids
291
+ double-fetch from the registry.
292
+ - Cache written **ONCE** at the end of the upgrade block (was being
293
+ fetched 3× per run).
294
+ - `pip install --upgrade graphifyy` / `uv tool install --upgrade graphifyy`
295
+ flags.
296
+ - `graphify check-update` integration emits
297
+ `graphify-reextract-triggered` when semantic re-extraction is pending.
298
+ - New codes: `codegraph-upgrade-broken`, `graphify-reextract-triggered`,
299
+ `upgrade-cache-written`.
300
+ - New config fields: `autoUpgrade`, `upgradeCachePath`,
301
+ `checkGraphifyNeedsUpdate`.
302
+
303
+ **Verified surface run:** `codegraph 0.6.8 → 1.5.0` and
304
+ `graphify 0.8.30 → 0.9.46` upgraded silently without manual
305
+ intervention.
306
+
307
+ **Configuration:**
320
308
 
321
- ```ts
322
- // Bridge tool result metadata now includes:
309
+ ```jsonc
323
310
  {
324
- tool: "omo_remember",
325
- ok: true,
326
- deliveryStatus: "delivered" | "pending",
327
- messageID: "...",
328
- durationMs: 1234,
329
- contentLength: 256
311
+ "meta_governor": {
312
+ "graphSync": {
313
+ "enabled": true, // default true
314
+ "autoUpgrade": true, // v0.26.0: default true
315
+ "upgradeCachePath": "~/.omo-meta-governor/upgrade-cache.json",
316
+ "checkGraphifyNeedsUpdate": true // emit graphify-reextract-triggered when schema changed
317
+ }
318
+ }
330
319
  }
331
320
  ```
332
321
 
333
- ### Test & build status
334
-
335
- - **514/514 tests pass** (up from 495 in v0.16.0 — 5 + 4 + 10 new tests across F5.4, F5.1, F3.6).
336
- - `bun run typecheck` clean.
337
- - `bun build.ts` clean (0.34 MB dist).
338
- - `npm pack --dry-run` validated.
339
-
340
- ### Migration
341
-
342
- No user action required. All changes are internal or additive:
343
- - `deliveryStatus` is an additive metadata field — existing consumers ignore it.
344
- - `maxLessonsPerSession` is now actually enforced — if you have sessions that previously saved more than 20 lessons (e.g. from before the cap was added), this may surprise you. Bump the cap in your config if needed.
345
- - `escalate` action now actively fires Oracle — this is the first version where Oracle is auto-invoked, not just manually invoked by the LLM.
346
-
347
- ### Audit roadmap (status as of v0.17.0)
348
-
349
- | Release | Status | Scope |
350
- |---------|--------|-------|
351
- | v0.15.1 (F0) | ✅ Shipped | Hotfix self-dep + npm pack gate |
352
- | v0.16.0 (F1-F7) | ✅ Shipped | Memory hygiene, dead code, tool coverage, CI |
353
- | v0.17.0 (deferred) | ✅ Shipped | F5.1 escalate, F5.4 cap, F3.6 delivery verify |
354
-
355
- All audit findings are now closed. Future work focuses on new features and user-driven feedback.
356
-
357
-
358
-
359
- ## v0.17.1 — Audit args fix (patch)
360
-
361
- v0.17.1 is a single-bug patch release. The fix addresses an issue discovered during v0.17.0 verification:
362
-
363
- ### The bug
364
-
365
- The `tool.execute.before` hook passed an empty `{}` object as the second argument to `auditToolCall()`. This meant the audit function never saw the tool's args (e.g. file content for write tools) and could never detect:
366
-
367
- - `@ts-ignore` / `@ts-expect-error` directives
368
- - `as any` type assertions
369
- - `catch(e) {}` empty catch blocks
370
-
371
- The hook signature was also incomplete — it didn't receive the `output` parameter that contains the mutable args, even though the SDK provides it.
372
-
373
- ### The fix
374
-
375
- Two changes in `src/plugin.ts`:
376
-
377
- 1. **Hook signature updated** to receive the `output` parameter:
378
- ```ts
379
- "tool.execute.before": async (
380
- toolInput: { tool: string; sessionID: string; callID: string },
381
- _output: { args: unknown },
382
- ): Promise<void> => {
383
- ```
384
-
385
- 2. **Audit call** now passes `_output.args` instead of `{}`:
386
- ```ts
387
- const violations = auditToolCall(toolInput.tool, _output.args, { ... })
388
- ```
389
-
390
- ### Tests
391
-
392
- Added 4 new tests in `src/plugin.test.ts`:
393
- - `@ts-ignore + as any` in args → `no-type-suppression` violation detected and injected
394
- - `catch(e) {}` in args → `no-empty-catch` violation detected and injected
395
- - Clean code → no violation injected (false-positive guard)
396
- - `auditToolCalls: false` → audit short-circuits (regression check)
397
-
398
- ### Test & build status
399
-
400
- - **518/518 tests pass** (up from 514 in v0.17.0 — 4 new audit tests).
401
- - `bun run typecheck` clean.
402
- - `bun build.ts` clean (0.34 MB dist).
403
-
404
- ### Migration
405
-
406
- No user action required. The audit detection now correctly fires when the agent writes forbidden patterns. This means:
407
-
408
- - **If your agent previously wrote `@ts-ignore` without being flagged**: it will now be flagged with `[GRAVE] no-type-suppression: ...` injected as a synthetic user message.
409
- - **If you want to disable the audit**: set `protocolEnforcement.auditToolCalls: false` (already supported).
410
-
411
-
412
-
413
- ## v0.17.2 — Fix escalation dead code + 4 audit gaps
414
-
415
- v0.17.2 closes 4 gaps discovered during live verification of v0.17.0/v0.17.1. The most important: F5.1 (escalate → Oracle) was effectively dead in production due to two compounding bugs.
416
-
417
- ### Highlights
418
-
419
- #### Gap C (CRITICAL) — Escalation now actually fires
420
-
421
- The score formula's `noProgress` and `deviations` inputs were hardcoded as `false` and `[]` in the plugin. This meant the `no-progress-detector` (weight 0.20) and `deviation-detector` (weight 0.20) signals always contributed 0. Combined with default thresholds, the maximum possible score was -0.55 — never reaching `escalateThreshold: 0.6` or `stopThreshold: 0.8`.
422
-
423
- **Fix:**
424
- 1. **Derive `noProgress`** from the recent tool call window. If the last 5 tool calls contain no `write`/`edit`/`task` (i.e. the agent is only reading/grepping without producing artifacts), `noProgress = true`.
425
- 2. **Derive `deviations`** from accumulated protocol violations. The audit hook now stores violations in `state.accumulatedDeviations` (capped at 5 per session); the orchestrator input reads them.
426
- 3. **Lower default thresholds** to match the new worst-case math:
427
- - `escalateThreshold`: 0.6 → 0.45
428
- - `stopThreshold`: 0.8 → 0.55
429
-
430
- Now worst-case state (no oracle, no progress, 2 grave deviations, iteration at limit, stop-advice lessons) produces score ≈ -0.55 → `stop` action fires.
431
-
432
- #### Gap Q (HIGH) — File paths threaded through pipeline
433
-
434
- `orchestrator.ts` was hardcoding `filesChanged: []` instead of `input.filePaths`. This meant lesson extraction never saw the actual changed files, so F7.5's file-basename FTS indexing was empty.
435
-
436
- **Fix:**
437
- 1. Track `recentWriteFilePaths` in AuditState (alongside existing `recentWriteContents`).
438
- 2. Capture `filePath` from `toolInput.args` on write/edit tool calls.
439
- 3. New `MetaGovernorInput.filePaths?: readonly string[]` passed through to `observeAndLearn`.
440
-
441
- #### Gap D (HIGH) — Three config fields now actually do something
442
-
443
- Three fields were in the schema and config projection but NEVER consulted by the logic:
444
-
445
- - `closedLoop.saveLessons` — parallel to `saveDecisions`. When `false`, lessons are skipped (decision records still save).
446
- - `intervention.includeDecisionHistory` — when `true`, `messages.transform` prepends recent intervention texts (capped at `maxHistoryMessages`) so the LLM sees its history of decisions.
447
- - `intervention.maxHistoryMessages` — limit for the above (default 5).
448
-
449
- **Fix:** All three fields now control behavior. Track `recentInterventionTexts` in AuditState, format them into the injection text.
450
-
451
- #### Bonus — iteration-budget signal wired (Oracle finding)
452
-
453
- Oracle flagged a pre-existing gap alongside Gap C: `iteration` was hardcoded `0` in the orchestrator input, making the `iteration-budget` signal (weight 0.15) effectively dead.
454
-
455
- Fix:
456
- - Added `iteration: number` to `AuditState`, incremented per tool call.
457
- - Threaded `iteration: sessionState?.iteration ?? 0` into `MetaGovernorInput`.
458
- - `maxIterations` now reads from config instead of being hardcoded.
459
-
460
- Worst-case score math updated: with iteration at 100% (-0.12), all signals bad, no oracle → score = -0.65 → `stop` action fires.
461
-
462
- #### Gap I (MEDIUM) — `verifyDelivery` return type includes "expired"
463
-
464
- The TypeScript signature was `Promise<"delivered" | "pending">` but the registry could return `"expired"`. The expired case leaked through as `"pending"` silently.
465
-
466
- **Fix:** Signature updated to `Promise<"delivered" | "pending" | "expired">`. Bridge tools now distinguish: `"delivered"` (verified), `"pending"` (still polling), `"expired"` (TTL elapsed).
467
-
468
- ### Test & build status
469
-
470
- - **521/521 tests pass** (up from 518 — 3 new tests for the v0.17.2 fixes).
471
- - `bun run typecheck` clean.
472
- - `bun build.ts` clean (0.34 MB dist).
473
- - `npm pack --dry-run` validated.
474
-
475
- ### Migration
476
-
477
- No user action required. Two behavior changes:
478
-
479
- 1. **Escalation now fires more aggressively.** If your agent has been producing violations and not making progress, expect to see escalate → Oracle prompts more often. This is the intended behavior; v0.17.0 was incorrectly silent.
480
- 2. **`includeDecisionHistory` and `maxHistoryMessages` are now functional.** If you set them in v0.17.0 expecting them to work, they will now actually take effect.
481
-
482
- ### Audit roadmap (status as of v0.17.2)
483
-
484
- | Release | Status | Scope |
485
- |---------|--------|-------|
486
- | v0.15.1 (F0) | ✅ | Hotfix self-dep |
487
- | v0.16.0 (F1-F7) | ✅ | Memory hygiene, dead code, tool coverage, CI |
488
- | v0.17.0 | ✅ | F5.1 escalate, F5.4 cap, F3.6 delivery verify |
489
- | v0.17.1 | ✅ | Audit args fix |
490
- | v0.17.2 | ✅ | Gap C (escalation live), Q (file paths), D (config fields), I (delivery expired) |
491
-
492
-
493
-
494
- ## v0.17.3 — Fix Gap I properly (patch)
495
-
496
- v0.17.3 is a single-bug patch. During live verification of v0.17.2, Gap I was found to be incompletely fixed.
497
-
498
- ### The bug (v0.17.2 cosmetic fix)
499
-
500
- The `verifyDelivery` export signature was widened to include `"expired"` in v0.17.2, and the bridge tools' title/output text was updated to handle it. **BUT the underlying `pollForDelivery` helper was still collapsing `"expired"` → `"pending"` silently:**
501
-
502
- ```ts
503
- // v0.17.2 (BUG):
504
- return status === "delivered" ? "delivered" : "pending"
505
- ```
506
-
507
- So bridge tools could never report `"expired"` to the user, even though the registry correctly tracked it. Live verification confirmed: `deliveryStatus` always showed `"pending"`.
508
-
509
- ### The fix (v0.17.3)
510
-
511
- ```ts
512
- // v0.17.3:
513
- return await pendingRegistryRef.awaitDelivery({ sessionID, mcpTool, timeoutMs })
514
- ```
515
-
516
- Now the actual status from the registry propagates through. `"expired"` flows end-to-end to the bridge tool's `metadata.deliveryStatus` and title.
517
-
518
- ### Tests
519
-
520
- Added 3 RED tests in `src/custom-tools.test.ts`:
521
- - Returns `"expired"` when registry entry exists past timeout (real registry instance)
522
- - Returns `"delivered"` when `markDelivered` fires before timeout
523
- - Returns `"pending"` when no registry is configured
524
-
525
- ### Test & build status
526
-
527
- - **525/525 tests pass** (up from 522 in v0.17.2 — 3 new tests for pollForDelivery).
528
- - `bun run typecheck` clean.
529
- - `bun build.ts` clean (0.34 MB dist).
530
-
531
- ### Migration
532
-
533
- No user action required. Bridge tools will now correctly distinguish all three delivery states:
534
- - `"delivered"` — LLM's MCP tool call was observed within 1.5s
535
- - `"expired"` — TTL elapsed without delivery (entry expires after 10s, but bridge tool sees this immediately as "expired" when polling times out at 1.5s)
536
- - `"pending"` — no registry configured (graceful degradation for tests/mocks)
537
-
538
- ### Audit roadmap (status as of v0.17.3)
539
-
540
- | Release | Status | Scope |
541
- |---------|--------|-------|
542
- | v0.15.1 → v0.17.2 | ✅ | All audit findings + 5 gap fixes |
543
- | v0.17.3 | ✅ | Gap I real fix (pollForDelivery returns "expired") |
544
-
545
- Two remaining gaps documented but require SDK support to fix:
546
- - `recentTurnTokens: []` — token-predictor signal dead (10% of score); needs per-turn token counts from OpenCode SDK
547
- - `agentName` defaults to `"unknown"` — cosmetic, no functional impact
548
-
549
-
550
-
551
- ## v0.18.0 — Audit remediation: 7+ silent config drops + circular ref crash
322
+ ### Git hooks
552
323
 
553
- v0.18.0 is a thorough-audit patch release. Each fix addresses a bug found by testing every public function with edge cases and adversarial inputs.
324
+ On every `git commit`:
554
325
 
555
- ### Highlights
556
-
557
- | # | Bug | Severity | Fix |
558
- |---|-----|----------|-----|
559
- | 1 | `file-logger.redactData` crashed on circular references with stack overflow | 🔴 CRITICAL | `WeakSet` guard + `try/catch` fallback |
560
- | 2 | `loadOrchestratorConfig` only projected `closedLoop.saveDecisions` — `enabled`, `minSeverityToLearn`, `maxLessonsPerSession`, `saveLessons` were silently dropped | 🔴 CRITICAL | Project all 5 fields |
561
- | 3 | `loadOrchestratorConfig` didn't project `decision.warnMessageTemplate`, `escalateMessageTemplate`, `stopMessageTemplate` | 🟠 HIGH | Project all 3 templates |
562
- | 4 | `loadOrchestratorConfig` didn't project `scoring.paralysisThreshold`, `defaultEscalationTarget` | 🟠 HIGH | Project all fields |
563
- | 5 | `loadOrchestratorConfig` had `memory.timeoutMs` field name mismatch (schema said `agentmemoryTimeoutMs`) | 🟠 HIGH | Accept both names |
564
- | 6 | `isMetaGovernorEnabled` only checked top-level `enabled`, not `meta_governor.enabled` (wrapped shape from `opencode.jsonc`) | 🟠 HIGH | Check both shapes |
565
- | 7 | `createMetricsCollector` crashed when called without config (`config.version` on `undefined`) | 🟠 HIGH | Accept `Partial<MetricsCollectorConfig>` |
566
- | 8 | `metrics.inc` crashed on unknown event names (`bucket.count++` on `undefined`) | 🟠 HIGH | Guard `if (!bucket) return` |
567
- | 9 | `isNewerVersion` returned `false` for `installed=null` (no upgrade triggered for fresh installs) | 🟡 MEDIUM | Return `true` when installed is null AND latest is valid |
568
-
569
- ### Test & build status
570
-
571
- - **557/557 tests pass** (up from 530 in v0.17.3 — 27 new tests for the audit fixes).
572
- - `bun run typecheck` clean.
573
- - `bun build.ts` clean (0.34 MB dist).
574
- - `npm pack --dry-run` validated.
575
-
576
- ### Migration
577
-
578
- No user action required. The fix to `loadOrchestratorConfig` means **users who were setting `closedLoop.maxLessonsPerSession` or other previously-dropped fields will now see those values actually take effect**. If you had a config like `{ "closedLoop": { "maxLessonsPerSession": 50 } }` before v0.18.0, it was silently being overridden to 20. Starting v0.18.0, the value 50 is now respected.
326
+ - **Primary path** (native git hook): `graphify update` runs in background.
327
+ - **Backup path** (plugin's `tool.execute.after`): detects `git commit`
328
+ in bash commands and runs `codegraph sync -q [path]`.
579
329
 
580
- ### Audit roadmap (status as of v0.18.0)
330
+ ### Process safeguards
581
331
 
582
- | Release | Status | Scope |
583
- |---------|--------|-------|
584
- | v0.15.1 → v0.17.3 | ✅ | All audit findings + deferred items + audit args fix + gap fixes |
585
- | v0.18.0 | ✅ | 7 silent config drops + circular ref crash + metrics crashes + upgrade trigger |
332
+ Every subprocess the plugin spawns (graphify, codegraph, npx, python,
333
+ npm/pip) is guaranteed to die after use — on success, error, AND
334
+ timeout including its descendant tree. On Windows this uses
335
+ `taskkill /pid <pid> /T /F` (plain `child.kill()` only kills the direct
336
+ shell, orphaning grandchildren — the confirmed cause of the
337
+ Bun/OpenChamber crashes).
586
338
 
587
- This release closes the final round of gaps found by a thorough function-by-function audit. The plugin now correctly projects **all** user configuration, handles **all** circular reference cases, and fails safely on **all** missing-input scenarios.
339
+ Config: `graphSync.killOrphanedOnInit` (default `true`) on graph-sync
340
+ init the plugin sweeps orphaned `graphify`/`codegraph` processes left
341
+ by previous crashed runs. Set to `false` to disable the sweep.
588
342
 
343
+ ---
589
344
 
590
- ## v0.19.4 Dual-shape default export: opencode 1.18.x loader compat (patch)
345
+ ## Persistence & observability
591
346
 
592
- v0.19.3 shipped a function-only default export hoping to fix the opencode
593
- serve factory-not-invoked bug. Empirical verification (reading
594
- `~/.config/opencode/meta-governor.log`) showed it didn''t help: opencode
595
- 1.18.16 npm-package plugins still loaded the module without ever calling
596
- the factory under `opencode serve`.
347
+ **Lesson storage.** Decisions and lessons persist in **SQLite** at
348
+ `~/.omo-meta-governor/meta-governor.db` with full-text search (FTS5) for
349
+ fast recall. Zero dependencies — uses Bun's built-in `bun:sqlite`.
597
350
 
598
- ### The fix
351
+ **Cross-session memory.** The `omo_remember` / `omo_recall_mcp` tools
352
+ bridge to AgentMemory via `session.prompt()` — the LLM receives a
353
+ structured instruction to call the appropriate MCP tool.
599
354
 
600
- `src/index.ts` now exports an object that is **both** a Plugin function
601
- **and** a PluginModule:
355
+ **Health JSON** at `~/.config/opencode/meta-governor-health.json`:
602
356
 
603
- ```ts
604
- const _plugin = createMetaGovernorPlugin()
605
- _plugin.id = "omo-meta-governor"
606
- _plugin.server = _plugin
607
- export default _plugin
357
+ ```bash
358
+ cat ~/.config/opencode/meta-governor-health.json
608
359
  ```
609
360
 
610
- Bundled as `var u4=IN(); u4.id="omo-meta-governor"; u4.server=u4; export{...JX=u4...}`.
611
- Whichever path opencode picks (`default(input, options)` or
612
- `default.server(input, options)`), the same callable fires and the hooks
613
- register.
614
-
615
- ### Verification
616
-
617
- After restart of OpenChamber, log shows:
618
- - factory_invoked events (was 0 with v0.19.3)
619
- - config_loaded events (was 0 with v0.19.3)
620
- - intervention / violations / persist calls firing normally
621
-
622
- ### Test & build status
361
+ Or invoke `omo_health` directly for a formatted report.
623
362
 
624
- - 4/4 new tests in `src/index.test.ts` lock down the dual-shape contract.
625
- - `bun run typecheck` clean.
626
- - `bun build.ts` clean (0.35 MB dist).
627
- - `npm publish` to registry OK.
628
-
629
- ### Migration
630
-
631
- No user action required. If you previously set
632
- `intervention.persistToSession: false` in your config, that toggle is
633
- still respected.
363
+ **Structured JSONL logs** at `~/.config/opencode/meta-governor.log` with
364
+ size-based rotation (10MB max, 5 rotated files). Secret redaction layer
365
+ strips JWT, OpenAI keys, Bearer tokens, GitHub PATs, and generic
366
+ `key:value` patterns before writing.
634
367
 
635
368
  ---
636
369
 
637
- ## v0.19.7 — Fix npm-published package.json: restore `import` condition in `exports` (patch)
370
+ ## CI monitor (v0.25.0)
638
371
 
639
- v0.19.6 shipped a single callable default export with the dual-shape contract (memory #1126) but the published tarball's `package.json` had the `exports` map regenerated to contain only the `types` condition — no `import`/`default`/`require` runtime condition. Under opencode 1.18.16's plugin loader, that caused the npm-package plugin to load the module (module scope ran, `MetaGovernor plugin loaded` logged) but **never invoke the factory** (`factory_invoked` was 0). v0.19.4 had empirically verified the dual-shape fix for `opencode serve`; v0.19.6 on `opencode run` with the same dual-shape bundle in the npm cache was silently broken for the same reason.
372
+ `src/ci-monitor.ts` auto-triggers GitHub Actions on `git push` and
373
+ surfaces failures to the agent:
640
374
 
641
- ### The bug
375
+ - Detects `git push` in bash commands via the `tool.execute.after` hook.
376
+ - Polls the GH Actions API for the resulting run (5s initial delay,
377
+ exponential backoff).
378
+ - On failure, injects a synthetic message with the failed logs into the
379
+ agent's context so it can fix and retry.
642
380
 
643
- `package.json` in the v0.19.6 tarball (read from the installed package in `~/.cache/opencode/packages/@herjarsa/omo-meta-governor@latest/...`):
381
+ Configurable via `meta_governor.ciMonitor` (disabled by default opt-in
382
+ feature).
644
383
 
645
- ```json
646
- "exports": {
647
- ".": { "types": "./dist/index.d.ts" },
648
- "./lib": { "types": "./dist/lib.d.ts" }
649
- }
650
- ```
384
+ ---
651
385
 
652
- vs. the repo's source `package.json` (correct, with `import` condition for runtime resolution):
386
+ ## Configuration reference
387
+
388
+ All configuration lives under the `meta_governor` key in
389
+ `opencode.jsonc`. Full schema:
390
+ [assets/omo-meta-governor.schema.json](assets/omo-meta-governor.schema.json).
391
+
392
+ ### Top-level
393
+
394
+ | Field | Type | Default | Description |
395
+ |-------|------|---------|-------------|
396
+ | `enabled` | boolean | `false` | Master feature flag — must be true to run the orchestrator. |
397
+ | `decision` | object | — | Decision handler tuning. |
398
+ | `memory` | object | — | Memory aggregator config. |
399
+ | `tokenPredictor` | object | — | Token predictor (compact-now / switch-model / delegate recommendations). |
400
+ | `scoring` | object | — | Scoring engine thresholds. |
401
+ | `closedLoop` | object | — | Closed-loop learning (save decisions + lessons). |
402
+ | `modelOverride` | object | — | Model override for MetaGovernor's internal LLM usage. |
403
+ | `intervention` | object | — | Visible decision injection config. |
404
+ | `protocolEnforcement` | object | — | Sisyphus protocol enforcement. |
405
+ | `skillPriming` | object | — | Proactive skill-selection nudge (v0.20.0). |
406
+ | `graphSync` | object | — | Graph synchronization (auto-init codegraph/graphify). |
407
+
408
+ ### `decision`
409
+
410
+ | Field | Type | Default | Description |
411
+ |-------|------|---------|-------------|
412
+ | `maxHistoryPerSession` | integer | — | Maximum history entries per session before oldest are trimmed. |
413
+ | `forceContinueAfterStops` | integer | — | How many consecutive stops before forcing continue. |
414
+
415
+ ### `memory`
416
+
417
+ | Field | Type | Default | Description |
418
+ |-------|------|---------|-------------|
419
+ | `agentmemoryTimeoutMs` | integer | — | Timeout for agentmemory queries in milliseconds. |
420
+ | `boulderStateTimeoutMs` | integer | — | Timeout for boulder-state queries in milliseconds. |
421
+ | `query` | string | — | Natural-language query for memory recall. |
422
+
423
+ ### `tokenPredictor`
424
+
425
+ | Field | Type | Default | Description |
426
+ |-------|------|---------|-------------|
427
+ | `compactBurnRateThreshold` | integer | — | Burn rate (tokens/turn) above which to recommend compact-now. |
428
+ | `compactUsageThreshold` | number | — | Context usage ratio (0..1) above which to recommend compact-now. |
429
+ | `switchModelUsageThreshold` | number | — | Context usage ratio above which to recommend switch-model. |
430
+ | `delegateConsecutiveHighBurn` | integer | — | Max consecutive high-burn turns before recommending delegate. |
431
+
432
+ ### `scoring`
433
+
434
+ | Field | Type | Default | Description |
435
+ |-------|------|---------|-------------|
436
+ | `continueThreshold` | number | `0.05` | Score ≥ this → continue silently. |
437
+ | `warnThreshold` | number | `0.3` | Score ≤ -this → warn. |
438
+ | `escalateThreshold` | number | `0.45` | Score ≤ -this → escalate. |
439
+ | `stopThreshold` | number | `0.55` | Score ≤ -this → stop. |
440
+
441
+ ### `closedLoop`
442
+
443
+ | Field | Type | Default | Description |
444
+ |-------|------|---------|-------------|
445
+ | `saveDecisions` | boolean | `true` | Whether to save decision records. |
446
+ | `saveLessons` | boolean | `true` | Whether to save lessons. |
447
+
448
+ ### `modelOverride`
449
+
450
+ | Field | Type | Default | Description |
451
+ |-------|------|---------|-------------|
452
+ | `providerID` | string | — | Provider ID (e.g. `'openai'`, `'anthropic'`). |
453
+ | `modelID` | string | — | Model ID (e.g. `'gpt-4o-mini'`, `'claude-sonnet-4-20250514'`). |
454
+ | `modelLimit` | integer | — | Context window size for token predictor (min 1000). |
455
+ | `temperature` | number | `0.2` | Sampling temperature (0..2). |
456
+ | `topP` | number | `1` | Top-p nucleus sampling (0..1). |
457
+ | `maxTokens` | integer | — | Max output tokens for internal reasoning. |
458
+ | `reasoning` | boolean | — | Enable extended reasoning / thinking mode. |
459
+ | `verbosity` | enum | — | `'silent'` \| `'minimal'` \| `'verbose'`. |
460
+
461
+ ### `intervention`
462
+
463
+ | Field | Type | Default | Description |
464
+ |-------|------|---------|-------------|
465
+ | `mode` | enum | — | `'silent'` \| `'message'` \| `'system'`. |
466
+ | `includeDecisionHistory` | boolean | — | Whether to include recent decision history in injection. |
467
+ | `maxHistoryMessages` | integer | `5` | Max history entries when `includeDecisionHistory: true`. |
468
+ | `minActionForMessage` | enum | — | Minimum action: `'warn'` (all non-continue), `'escalate'`, `'stop'`. |
469
+ | `persistToSession` | boolean | `true` | v0.19.0: when true, intervention messages ALSO persist to the session. |
470
+ | `maxInterventionsPerSession` | integer | `3` | v0.10.0: hard cap before auto-disable. |
471
+ | `respectDoneSignal` | boolean | `true` | Stop injecting once terminal signal + Oracle verified. |
472
+ | `phaseAwareDoneSignal` | boolean | `false` | v0.15.0: split per-phase hint from terminal signal. |
473
+
474
+ ### `protocolEnforcement`
475
+
476
+ | Field | Type | Default | Description |
477
+ |-------|------|---------|-------------|
478
+ | `enabled` | boolean | — | Master switch. |
479
+ | `path` | string | — | Path to protocol markdown file. |
480
+ | `injectIntoSystem` | boolean | — | Whether to inject protocol rules into the system prompt. |
481
+ | `auditToolCalls` | boolean | — | Whether to audit tool calls for violations. |
482
+
483
+ ### `skillPriming`
484
+
485
+ | Field | Type | Default | Description |
486
+ |-------|------|---------|-------------|
487
+ | `enabled` | boolean | `false` | Master switch. |
488
+ | `trigger` | enum | `'firstImplement'` | `'sessionStart'` (first transform) or `'firstImplement'` (once write-like tool observed). |
489
+ | `router` | enum | `'both'` | `'aas'` \| `'superpowers'` \| `'both'`. |
490
+
491
+ ### `graphSync`
492
+
493
+ | Field | Type | Default | Description |
494
+ |-------|------|---------|-------------|
495
+ | `enabled` | boolean | `true` | Enable auto-initialization. |
496
+ | `watch` | boolean | `false` | Enable watch mode (re-index on file changes). |
497
+ | `killOrphanedOnInit` | boolean | `true` | Sweep orphaned processes on init. |
498
+ | `autoUpgrade` | boolean | `true` | **v0.26.0** — auto-upgrade installed codegraph + graphify binaries. |
499
+ | `upgradeCachePath` | string | — | **v0.26.0** — path for the upgrade cache file. |
500
+ | `checkGraphifyNeedsUpdate` | boolean | `true` | **v0.26.0** — run `graphify check-update` after upgrade. |
653
501
 
654
- ```json
655
- "exports": {
656
- ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
657
- "./lib": { "types": "./dist/lib.d.ts", "import": "./dist/lib.js" }
658
- }
659
- ```
502
+ ---
660
503
 
661
- The published manifest lost the `import` condition (and `peerDependencies: {"@opencode-ai/plugin": ">=1.0.0"}`, `publishConfig`, `repository.url`, `devDependencies.typescript`, `files` README entry, `scripts.test`). When opencode's plugin installer resolves the entry via `exports`, only `"types"` is offered — no runtime condition matches → the loader imports the bundle by `main` fallback but the export contract that uk() (`opencode.util.createPlugin`) inspects isn't satisfied, so the plugin is loaded but never invoked.
504
+ ## Architecture overview
662
505
 
663
- ### How the bug was reproduced
506
+ The plugin is a single ESM module with five layers wired through opencode
507
+ event hooks:
664
508
 
665
- Cheap-shape probes (`@herjarsa/omg-shape-probe2` … `probe7`) with byte-identical Bun-bundles from `dist/index.js` were published to npm and tested under `opencode run` with XDG-isolated config. All probes with `exports."."` containing `"import"` or `"default"` invoked the factory. The probe replicating the exact published shape (types-only exports) failed to invoke. Bundle SHA-256 identical between probes and the real published package:
666
509
  ```
667
- E737C676FCC74B6CBA771616058BC5620C1F0364089B1B8626246E96FF02ED69
510
+ ┌─────────────────────────────────────────────────────┐
511
+ │ opencode event hooks │
512
+ │ tool.execute.before tool.execute.after │
513
+ │ chat.messages.transform chat.system.transform │
514
+ └───────────────┬─────────────────────┬────────────────┘
515
+ │ │
516
+ ┌───────────────────▼────────┐ ┌──────────▼─────────────┐
517
+ │ AuditStateCache (TTL) │ │ Decision + Scoring │
518
+ │ recentWriteFilePaths │ │ Engine (-1..+1 score) │
519
+ │ accumulatedDeviations │ └──────────┬─────────────┘
520
+ │ recentInterventionTexts │ │
521
+ └───────────────────────────┘ │
522
+ │ │
523
+ ┌─────────────────────────▼─────────────────────▼──────────────┐
524
+ │ Governance pipeline │
525
+ │ Protocol Enforcer → Scoring → Decision Handler → │
526
+ │ Intervention │
527
+ └──────────────────────────────────────────────────────────────┘
528
+
529
+
530
+ ┌─────────────────────────▼──────────────────────────────────────┐
531
+ │ Graph sync + tool layer │
532
+ │ codegraph + graphify (auto-init, git hooks, auto-upgrade) │
533
+ │ 12 omo_* tools (search, find, impact, recall, files, etc.) │
534
+ └───────────────────────────────────────────────────────────────┘
535
+
536
+
537
+ ┌──────────────────────────────────┐
538
+ │ SQLite (bun:sqlite) + AgentMem │
539
+ │ meta-governor.db / decisions │
540
+ │ / lessons / audit state │
541
+ └──────────────────────────────────┘
668
542
  ```
669
543
 
670
- ### The fix
671
-
672
- No source/bundle changes — the bundle was correct all along. The malformed published `package.json` was restored from the repo (`git show HEAD:package.json` has the canonical full exports map with `import`) and only the version was bumped to `0.19.7`. Rebuild with `bun build.ts` (bakes version into the bundle), published, verified.
673
-
674
- ### Verification
544
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for module-level relationships and
545
+ [STRUCTURE.md](STRUCTURE.md) for the file layout.
675
546
 
676
- XDG-isolated opencode run (`opencode run` with custom config pointing at `@herjarsa/omo-meta-governor@latest`):
677
-
678
- - v0.19.6 (cache populated): log shows `MetaGovernor plugin loaded` only — **no `factory_invoked`**, intervention never runs.
679
- - v0.19.7 (cache cleared, reinstalled): log shows full chain:
680
- ```
681
- [meta-governor] v0.21.1 MetaGovernor plugin loaded
682
- [meta-governor] v0.21.1 factory_invoked
683
- [meta-governor] SessionBridge: OpenCode client hydrated — session.prompt() available
684
- [meta-governor] v0.21.1 config_loaded
685
- ```
686
- > **Note (v0.21.1+)**: every init log line now prepends `v<DEFAULT_VERSION>` (read from `package.json` at build time by Bun). Use this prefix to confirm which release OpenChamber actually loaded — a stale npm cache could otherwise serve an older bundle silently because `@latest` does not force re-fetch if the cache is still valid.
687
-
688
- Updated `~/.cache/opencode/packages/@herjarsa/omo-meta-governor@latest/node_modules/@herjarsa/omo-meta-governor/package.json` now reads `exports."."` = `{ types: "./dist/index.d.ts", import: "./dist/index.js" }` and `peerDependencies` = `{ "@opencode-ai/plugin": ">=1.0.0" }`.
689
-
690
- ### Test & build status
691
-
692
- - `bun run typecheck` clean.
693
- - `bun build.ts` clean (0.34 MB dist, version 0.19.7 baked in).
694
- - `npm publish` to registry OK.
695
- - Empirical harness verification: factory_invoked + config_logged restored.
547
+ ---
696
548
 
697
- ### Migration
549
+ ## Testing
698
550
 
699
- No user action required. If you were running v0.19.6 and the plugin appeared silent (no interventions, no metrics), delete your opencode cache:
700
551
  ```bash
701
- rm -rf ~/.cache/opencode/packages/@herjarsa/omo-meta-governor@latest
552
+ bun test # full suite (672+ tests)
553
+ bun test src/upgrade-autofix.test.ts # Wave 1: auto-upgrade regression
554
+ bun test src/custom-tools.test.ts # Wave 2: 12 tools
702
555
  ```
703
- Then restart opencode — it will reinstall v0.19.7 from the registry and the pipeline will fire normally.
704
-
705
- ---
706
-
707
- ## v0.19.3 — opencode serve factory invocation + persistSessionMessage wiring
708
-
709
- This release closes the persistSessionMessage wiring arc (memory #999) and
710
- loads config from three sources automatically (CLI > project > user).
711
- The `index.ts` `export default` change was incomplete (see v0.19.4 for
712
- the proper dual-shape fix that resolves opencode serve invocation).
713
-
714
- ### Highlights
715
-
716
- - **index.ts**: switched `export default` to call `createMetaGovernorPlugin()`
717
- directly (function path), abandoning the `PluginModule` wrapper. Was the
718
- wrong shape — see v0.19.4 for the actual fix.
719
- - **plugin.ts**: factory now calls `loadMetaGovernorConfig({ projectDir })` so
720
- config from `.opencode/omo-meta-governor.jsonc` and
721
- `~/.config/opencode/omo-meta-governor.jsonc` flows in automatically.
722
- - **session-bridge.ts**: `persistSessionMessage()` — fire-and-forget
723
- `session.prompt()` helper that records intervention text as a REAL session
724
- message (visible in TUI and session DB).
725
- - **types.ts + config.ts + orchestrator.ts**: `persistToSession` defaults to
726
- true on `InterventionConfig`. Helps users running OpenChamber actually
727
- SEE interventions in their TUI.
728
- - **tests**: 16 `createMetaGovernorPlugin()` calls in plugin/v172/v173-gap-d
729
- tests now pass `graphSync: { enabled: false, autoInstall: false }`
730
- because user config enables `autoInstall` by default (memory #989).
731
-
732
- ### Test & build status
733
-
734
- - ~437/437 tests pass (per-file, `graphsink-fix.ts` skipped per known Bun
735
- Windows integer-overflow crash).
736
- - `bun run typecheck` clean.
737
- - `bun build.ts` clean (0.35 MB dist).
738
- - `npm publish` to registry OK.
739
-
740
- ### Migration
741
556
 
742
- No user action required.
557
+ **Coverage highlights (v0.26.0):**
558
+
559
+ - 10 tests in `src/upgrade-autofix.test.ts` (AUT-1..AUT-7) — tiered probe,
560
+ pip `--upgrade` flag, `graphify check-update` integration, cache
561
+ write-once semantics.
562
+ - 7 tests in `src/custom-tools.test.ts` for the new tools
563
+ (FIL-1..3, CAL-1..2, NOD-1..2) plus full coverage of the existing 9.
564
+ - 686+ tests across `decision-store`, `token-predictor`,
565
+ `protocol-enforcer`, `graph-sync`, `skill-priming`, `ci-monitor`,
566
+ `audit-state-cache`, `closed-loop-learning`, `closed-loop`,
567
+ `config-file`, `session-bridge`, `sqlite-backend`, `memory-aggregator`,
568
+ `proc-guard`, `ttl-queue`, `mcp-client`, `scoring-engine`, `v018-fixes`,
569
+ `v172`, `v173-f51`, `v173-gap-d`, `intervention-fix`, `graphsink-fix`,
570
+ `plugin`, `plugin-graphsync`, `plugin-audit-postwave`, `postwave-wire`,
571
+ `postwave-gate`.
572
+
573
+ Known flaky test: `runGuarded > times out` (1 test) — pre-existing,
574
+ unrelated to v0.26.0, confirmed by Oracle audit.
743
575
 
744
576
  ---
745
577
 
746
- ## v0.19.0–v0.19.2 Internal persistSessionMessage wiring arc (memory #999)
578
+ ## Migration from earlier versions
747
579
 
748
- Three iterations on the persistSessionMessage wiring arc. Each iteration
749
- added call sites and tightened the test coverage, but the fix was lost in
750
- stashes and merges until v0.19.3 finally shipped a working version of
751
- the helper. See v0.19.3 for the user-facing release notes; v0.19.4
752
- supersedes with the correct dual-shape export.
753
- ## Auto-upgrade (v0.12.0)
580
+ **From v0.24.x v0.26.0:**
754
581
 
755
- On plugin load, queries npm/pip registries to check whether newer versions
756
- of **codegraph** or **graphify** exist. Config: `graphSync.autoUpgrade` (default `true`),
757
- `graphSync.upgradeCheckTtlMs` (default `86400000`).
582
+ - **Stale-cache detection (v0.24.3):** On plugin load, an async npm
583
+ version check runs in the background. If the loaded version differs
584
+ from the latest published version, a warning is logged with
585
+ cache-clearing instructions. If you see `STALE_CACHE` in
586
+ `meta-governor.log`, run:
758
587
 
759
- ## v0.24.3 — Background Oracle intervention suppression + memory hygiene
760
-
761
- ### Highlights
762
-
763
- #### Background Oracle intervention suppression (v0.24.0)
764
-
765
- When the agent invokes Oracle via `task(subagent_type="oracle", run_in_background=true)`, the plugin now detects the background task and suppresses all interventions until Oracle returns. Previously, the agent's idle window during Oracle execution triggered noProgress + accumulated deviations → intervention pile-up.
766
-
767
- New `AuditState` fields:
768
- - `oracleInFlight: boolean` — true while a background Oracle task is running
769
- - `signalAtMs: number` — timestamp of the last done/phase signal, used to prevent stale latches from immediately clearing oracleInFlight
770
-
771
- Detection uses `toolInput.args` (not `toolOutput.output`) to correctly identify background Oracle calls.
772
-
773
- 3-tier clear strategy (Oracle-reviewed):
774
- 1. Promise signal detected AFTER oracleInFlight was set (signal timestamp > oracleInFlightSinceMs)
775
- 2. Timeout safety net (5 minutes since invocation)
776
- 3. Foreground Oracle call (agent explicitly waiting for a new Oracle)
777
-
778
- #### Memory save directive (v0.24.0)
779
-
780
- Added explicit negative guidance to the protocol-enforcer memory save directive:
781
- > "Do NOT save routine operations (file reads, greps, list commands), trivial decisions, or facts already covered by existing memory."
588
+ ```bash
589
+ npm cache clean --force && rm -rf ~/.cache/opencode/packages/@herjarsa/omo-meta-governor*
590
+ ```
782
591
 
783
- Narrowed `save-discovery-to-memory` audit rule from 5 tools (grep, glob, read, codegraph_explore, graphify query) to 2 (codegraph_explore, graphify query) to align with the directive.
592
+ Then restart opencode.
784
593
 
785
- #### Stale-cache detection (v0.24.3)
594
+ - **Auto-upgrade (v0.26.0):** No user action required. The plugin now
595
+ upgrades codegraph and graphify silently on every load. If you
596
+ previously disabled `graphSync.enabled` to work around the broken
597
+ upgrade, re-enable it.
786
598
 
787
- On plugin load, an async npm version check runs in the background. If the loaded version differs from the latest published version, a warning is logged with cache-clearing instructions. This prevents silent stale-cache issues where `@latest` does not force re-fetch from opencode's package cache.
599
+ - **New config fields:** `graphSync.autoUpgrade`,
600
+ `graphSync.upgradeCachePath`, `graphSync.checkGraphifyNeedsUpdate` —
601
+ all default true. Schema is backward-compatible.
788
602
 
789
- ### Migration
603
+ **From earlier versions:** no user action required. All changes through
604
+ v0.18.0 were transparent — the audit gaps (config drops, circular refs,
605
+ metrics crashes) only affected edge cases where users set obscure
606
+ fields. See [CHANGELOG.md](CHANGELOG.md) for the full history.
790
607
 
791
- No user action required. This is a transparent improvement. If you see a `STALE_CACHE` warning in your meta-governor.log, run:
792
- ```bash
793
- npm cache clean --force && rm -rf ~/.cache/opencode/packages/@herjarsa/omo-meta-governor*
794
- ```
795
- Then restart opencode.
608
+ ---
796
609
 
797
610
  ## License
798
611
 
799
- MIT
612
+ MIT