opencode-orchestrator-uni 0.5.0 → 0.5.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,577 +1,313 @@
1
- # opencode-orchestrator-uni
2
-
3
- **English** · [Русский](./README.ru.md)
4
-
5
- A native [OpenCode V2](https://opencode.ai/v2/docs/build/plugins) package plugin that
6
- ships the **orchestrator agent kit**:
7
-
8
- - `orchestrator` — the root orchestrator (primary agent) with the audit loop as a
9
- built-in mode;
10
- - read-only audit subagents — `factcheck`, `empiric`, `invariant`,
11
- `arbiter`, `meta`, `redteam`, `context-lint`;
12
- - `fix-runner` — the only writing subagent (applies the "to close" list);
13
- - the kit's process documents, exposed as the **`audit-kit` reference**
14
- (`call-mechanics.md`, `process-ready-loop.md`, `root-runbook.md`,
15
- `instruction-other-session.md`, `context-efficiency.md`, `session-discipline.md`);
16
- - five deterministic, read-only tools — `orch_uni_validate_report`, `orch_uni_context_lint`,
17
- `orch_uni_snapshot`, `orch_uni_sessions`, `orch_uni_session_stats` (see [Tools](#tools));
18
- - three slash commands — `/agents-md`, `/spec` and `/handoff` (see [Commands](#commands)).
19
-
20
- Read-only audit agents are enforced by `edit: deny` plus the prompt ban; their shell
21
- permission stays allowed so they can run verification commands (an accepted risk, no
22
- permission is relaxed).
23
-
24
- The package is loaded directly from TypeScript: `exports` points at
25
- [`index.ts`](./index.ts). There is **no build step and no `dist/`** — OpenCode's
26
- Bun-based runtime resolves and loads the entry point natively (same pattern as the
27
- official `@opencode/plugin-figma` package).
28
-
29
- ## What the plugin registers
30
-
31
- On setup (`ctx.agent.transform` / `ctx.reference.transform`):
32
-
33
- | Agent | Mode | Role |
34
- | ----------------- | ---------- | -------------------------------------------------------- |
35
- | `orchestrator` | `primary` | Root orchestrator; audit loop as a built-in mode |
36
- | `factcheck` | `subagent` | Read-only: staleness pre-pass (already done / outdated) |
37
- | `empiric` | `subagent` | Read-only: reproductions, measurements, first sources |
38
- | `invariant` | `subagent` | Read-only: states, cancels, races, hostile input |
39
- | `arbiter` | `subagent` | Read-only: resolves disputed findings only |
40
- | `meta` | `subagent` | Read-only: meta-audit of reports (pass/fail gate) |
41
- | `redteam` | `subagent` | Read-only: acceptance red team (release gates) |
42
- | `context-lint` | `subagent` | Read-only: audits AGENTS.md/SPEC.md for token efficiency |
43
- | `fix-runner` | `subagent` | Writer: applies the fix list and proves closure |
44
-
45
- All agents ship **without a model**: models are deployment configuration (see
46
- [Models](#models)). Definitions live in [`agents/`](./agents) as plain Markdown agent
47
- files and are parsed at setup time — the files are the single source of truth.
48
-
49
- "Read-only" here means `edit` is denied and the prompt forbids writes; `shell` stays
50
- allowed so an auditor can run the verification commands it needs (accepted risk
51
- I-M15). The audit loop treats that as a contract, not as a sandbox.
52
-
53
- The `audit-kit` reference points at this package's per-locale `docs/<locale>/`
54
- directory (see [Locale](#locale)). OpenCode lists it in session context as an
55
- additional local directory, so the orchestrator (and any other agent) can read
56
- the process documents directly, for example `<reference path>/call-mechanics.md`.
57
-
58
- The plugin also registers five deterministic, **read-only** tools in the `orch_uni`
59
- namespace and three slash commands — see [Tools](#tools) and [Commands](#commands).
60
-
61
- ## Commands
62
-
63
- | Command | What it does |
1
+ <a id="readme-top"></a>
2
+
3
+ [![npm version][npm-shield]][npm-url]
4
+ [![CI][ci-shield]][ci-url]
5
+ [![License][license-shield]][license-url]
6
+ [![OpenCode V2][opencode-shield]][opencode-url]
7
+
8
+ <br />
9
+
10
+ <div align="center">
11
+ <h3 align="center">opencode-orchestrator-uni</h3>
12
+
13
+ <p align="center">
14
+ An OpenCode V2 plugin that runs coding work as an evidence-driven audit loop.
15
+ <br />
16
+ <a href="https://github.com/OniVe/opencode-orchestrator-uni/tree/main/docs/en"><strong>Explore the docs »</strong></a>
17
+ <br />
18
+ <br />
19
+ <a href="https://github.com/OniVe/opencode-orchestrator-uni/issues/new">Report Bug</a>
20
+ &middot;
21
+ <a href="https://github.com/OniVe/opencode-orchestrator-uni/issues/new">Request Feature</a>
22
+ </p>
23
+
24
+ <p align="center">
25
+ <strong>English</strong> &middot; <a href="https://github.com/OniVe/opencode-orchestrator-uni/blob/main/README.ru.md">Русский</a>
26
+ </p>
27
+ </div>
28
+
29
+ <details>
30
+ <summary>Table of Contents</summary>
31
+ <ol>
32
+ <li><a href="#about-the-project">About The Project</a>
33
+ <ul>
34
+ <li><a href="#what-you-get">What You Get</a></li>
35
+ <li><a href="#built-with">Built With</a></li>
36
+ </ul>
37
+ </li>
38
+ <li><a href="#getting-started">Getting Started</a>
39
+ <ul>
40
+ <li><a href="#prerequisites">Prerequisites</a></li>
41
+ <li><a href="#installation">Installation</a></li>
42
+ <li><a href="#configure">Configure</a></li>
43
+ </ul>
44
+ </li>
45
+ <li><a href="#usage">Usage</a>
46
+ <ul>
47
+ <li><a href="#quick-start">Quick Start</a></li>
48
+ <li><a href="#the-audit-loop">The Audit Loop</a></li>
49
+ <li><a href="#agents">Agents</a></li>
50
+ <li><a href="#commands">Commands</a></li>
51
+ </ul>
52
+ </li>
53
+ <li><a href="#reference">Reference</a></li>
54
+ <li><a href="#development">Development</a></li>
55
+ <li><a href="#roadmap">Roadmap</a></li>
56
+ <li><a href="#contributing">Contributing</a></li>
57
+ <li><a href="#license">License</a></li>
58
+ <li><a href="#contact">Contact</a></li>
59
+ <li><a href="#acknowledgments">Acknowledgments</a></li>
60
+ </ol>
61
+ </details>
62
+
63
+ ## About The Project
64
+
65
+ Agent sessions drift: the same agent writes the code, judges the result, and
66
+ summarizes its own success. This plugin makes the separation explicit for
67
+ [OpenCode V2](https://opencode.ai/v2/docs/build/plugins). It ships the
68
+ **orchestrator agent kit**: a root orchestrator with a built-in audit loop,
69
+ read-only audit subagents, one writer, deterministic verification tools, and the
70
+ process documents that keep the loop honest. Plugin id: `orchestrator.uni`.
71
+
72
+ The kit is a protocol, not a sandbox. Auditors are read-only by tool permission
73
+ and prompt; the orchestrator delegates every code/doc/test edit; audit artifacts
74
+ stay in writable paths. The write guard keeps the orchestrator out of source —
75
+ see [Write Guard](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/docs/en/reference.md#write-guard).
76
+
77
+ ### What You Get
78
+
79
+ | Layer | What ships |
64
80
  | --- | --- |
65
- | `/agents-md` | Injects the AGENTS.md audit/draft workflow: `context-efficiency.md` template, `orch_uni_context_lint`, `context-lint`, approval before writing. |
66
- | `/spec` | Injects the SPEC.md workflow: brief, clarifying questions, EARS/AC/traceability, `orch_uni_context_lint`, `context-lint`, approval before writing. |
67
- | `/handoff` | Injects the slice-closing workflow: session and working-tree facts, the `session-discipline.md` handoff template, writes `docs/planning/handoff-<YYYY-MM-DD>.md`. |
81
+ | Agents | `orchestrator` (primary); read-only `factcheck`, `empiric`, `invariant`, `arbiter`, `meta`, `redteam`, `context-lint`; writer `fix-runner` |
82
+ | Tools | Five read-only tools: `orch_uni_validate_report`, `orch_uni_context_lint`, `orch_uni_snapshot`, `orch_uni_sessions`, `orch_uni_session_stats` |
83
+ | Commands | Three slash commands: `/agents-md`, `/spec`, `/handoff` |
84
+ | Reference | Process documents exposed as the `audit-kit` reference; deep details in [Reference](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/docs/en/reference.md) |
68
85
 
69
- A command is a prompt, not a writer: its handler injects a workflow prompt into the
70
- current session. The orchestrator then follows the usual evidence and approval rules.
86
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
71
87
 
72
- ## Tools
88
+ ### Built With
73
89
 
74
- | Effective name | Read-only | What it does |
75
- | ------------------------ | --------- | ----------------------------------------------------------------------------- |
76
- | `orch_uni_validate_report` | yes | Extract the SGR JSON block of a report, resolve its schema, validate it |
77
- | `orch_uni_context_lint` | yes | Deterministic audit of AGENTS.md/SPEC.md: budget, vague phrases, links, sections |
78
- | `orch_uni_snapshot` | yes | Deterministic revision of a directory tree (per-file sha256 + aggregate hash) |
79
- | `orch_uni_sessions` | yes | Child sessions of a session: agent, model, cost, tokens, time |
80
- | `orch_uni_session_stats` | yes | Session protocol counters: prompt, compactions, tools/turn, capped outputs |
90
+ - [![OpenCode V2][opencode-shield]][opencode-url]
91
+ - [![TypeScript][typescript-shield]][typescript-url]
92
+ - [![Bun][bun-shield]][bun-url]
93
+ - [![Biome][biome-shield]][biome-url]
94
+ - [![semantic-release][semantic-release-shield]][semantic-release-url]
81
95
 
82
- Every tool answers with a JSON text body. Failures are **typed codes** inside the body
83
- (`E_INPUT`, `E_NO_JSON`, …) instead of thrown tool errors, so an agent can branch on the
84
- code without parsing prose. None of the five writes to the workspace, the session store
85
- or the configuration.
96
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
86
97
 
87
- ### `orch_uni_validate_report`
98
+ ## Getting Started
88
99
 
89
- ```jsonc
90
- { "text": "<report text>" } // or
91
- { "path": "reports/empiric-r1.md", // absolute, or relative to the location directory
92
- "schema": "empiric/v1" } // optional: expected schema id
93
- ```
94
-
95
- Provide exactly one of `text` / `path`. The tool takes the **last** fenced ```` ```json ````
96
- block that parses and carries a `schema` field, resolves the schema by that field and
97
- validates the document.
100
+ ### Prerequisites
98
101
 
99
- ```jsonc
100
- { "valid": true, "schema": "empiric/v1", "errors": [], "warnings": [ … ] }
101
- { "valid": false, "code": "E_INVALID", "schema": "empiric/v1",
102
- "errors": [ { "path": "$.findings[0].severity", "message": "expected one of …" } ],
103
- "warnings": [] }
104
- ```
102
+ - **OpenCode 2.x** with the V2 plugin API (`plugins` field). V1's loader expects
103
+ the legacy `server()` export and will not load this plugin.
104
+ - **Bun** for tests and check scripts, **Node.js 22.14+** for release tooling.
105
+ The plugin has no build step and no `dist/`.
106
+ - A git checkout for `npm run check:locales` and `npm run verify:kit`; the npm
107
+ package ships no `tools/` directory.
105
108
 
106
- | Code | Meaning |
107
- | ------------------- | ---------------------------------------------------------------- |
108
- | `E_INPUT` | Bad call: neither/both of `text`/`path`, wrong types, unreadable file |
109
- | `E_NO_JSON` | No fenced ```` ```json ```` block found |
110
- | `E_PARSE` | A block exists but none parses as JSON |
111
- | `E_UNKNOWN_SCHEMA` | The report names an id that is not shipped (or no id at all) |
112
- | `E_SCHEMA_MISMATCH` | The report names another schema than the expected one |
113
- | `E_INVALID` | The document does not satisfy its schema (see `errors`) |
114
- | `E_SCHEMA_IO` | `schemas/*.json` unreadable/malformed (see `directory`, `file`) |
115
- | `E_ABORTED` | The session was stopped while reading the report |
116
-
117
- `warnings` carry the prompt rules a *structurally valid* report can still miss — things
118
- JSON Schema cannot express: a missing `rc` on an `empiric` claim whose `method` is
119
- `run|measure|compare_base`, `self_check` items other than 1–19, an `invariant`
120
- invariant that is `violated` without a counterexample, an uncovered mandatory
121
- `lines_checked` line, `meta`'s `verdict` diverging from its `gate`, and
122
- `redteam`'s `closing_list`/`verdict` invariants.
123
-
124
- The schemas ship as `schemas/*.json` and are derived **strictly** from the SGR
125
- sections of `agents/*.md`, which stay the source of truth: the test suite parses every
126
- agent's SGR template and fails when a prompt field or enum value is not accepted by its
127
- schema. `context-lint-failure/v1` is the typed failure envelope of
128
- `orch_uni_context_lint` — it has no agent template and is never a report. Schemas set
129
- `additionalProperties` permissively — agents may add fields of their
130
- own (`verdict_label`, `check`, …).
131
-
132
- ### `orch_uni_context_lint`
109
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
133
110
 
134
- ```jsonc
135
- { "path": "AGENTS.md" } // file or directory, relative to the location
136
- { "path": ".", "max_lines": 150, "max_tokens": 2000 }
137
- { "path": ".", "max_words": 2000 } // SPEC word budget; default 1500 (target), ceiling 2000
138
- { "path": ".", "max_file_bytes": 500000, "max_files": 100, "max_findings": 50, "max_total_findings": 500 }
139
- ```
111
+ ### Installation
140
112
 
141
- Deterministic, read-only audit of `AGENTS.md`, `SPEC.md` and `CLAUDE.md`. It returns
142
- `{ schema: "context-lint/v1", ok: true, verdict, closing_list, files, summary, truncated? }`;
143
- each finding carries `id`, `severity`, `kind`, `evidence`, `detail` and `fix`. Checks:
144
- line/token/word budget, distinct vague phrases, duplicate lines, broken relative links,
145
- placeholders, dates/versions in the first three lines, required sections and AC test
146
- columns. Links and explicit targets are confined by their **real** path: a symlink or
147
- junction that resolves outside the root is skipped, and such a target is refused
148
- (`E_OUT_OF_ROOT`). Code fences, inline code and HTML comments are masked, so a sample is
149
- never a rule: structural checks (commands, boundaries, verification) read the full mask,
150
- while requirements, AC ids and non-goals read the fence mask. Link targets are
151
- streamed from the line scanner one at a time (no candidate array), so memory does
152
- not grow with the number of links; checks are capped per file at 50 000
153
- candidates, the signal is checked per line and every 100 candidates, and the
154
- link caches keep at most 5 000 entries, so an abort wins; 2001 links are checked
155
- normally, 50 000 valid links still pass, and the 50 001st candidate makes the list
156
- incomplete (`truncated: true` plus a MAJOR `link candidates truncated` marker in
157
- `closing_list`), so a capped run can never pass. Broken
158
- links have no separate per-file cap: the finding caps bound the output and report the
159
- overflow. A line is scanned however long it is: the link scanner and the placeholder regex
160
- are linear, so a 4100-character line is checked like any other and a broken link or
161
- placeholder on it is the real finding, not a marker. Duplicate lines have no inner cap.
162
- UNC/network targets are never probed. Measured (bun 1.4.2, Windows, fresh
163
- process): 50 001 broken links with `max_findings: 2` → ~1.4 s and ~67 MB peak
164
- RSS; 50k plain lines → ~0.14 s / ~65 MB; a 2 MB single line with links →
165
- ~3.1 s / ~116 MB (~41 MB without links). Masking reuses a line as-is when it
166
- has no backtick and no `<pre>`/`<code>`/`<!--`, so it never copies a line
167
- character by character, and a 2 MB single line stays capped at 50 000
168
- candidates instead of materializing every link.
169
- Limits: ≤ 2 MB per file, ≤ 500 files per scan. `max_findings` (per file) is N per
170
- severity: up to N MAJOR and N MINOR/INFO are kept, and any excess is replaced by one
171
- marker (MAJOR when a MAJOR was dropped, otherwise INFO); a MAJOR marker stays in
172
- `closing_list`. `max_total_findings` (global, default 1000) keeps MAJOR
173
- findings first; when MAJOR overflow, `max_total` findings plus one MAJOR `truncated`
174
- marker are kept. A capped list sets `truncated: true` instead of hiding findings.
175
- Markers are additional to `max_findings` and do not consume its budget: a capped
176
- link-candidate list and a capped findings list report one marker each in the same run, so
177
- `max_findings: 2` over 20 001 broken links yields 2 kept findings plus 2 markers. Cap
178
- markers point at `file:1` in `evidence`.
179
- Non-goals are counted as top-level items: a wrapped paragraph is one item, inline
180
- `;`/`,`/`and`/`и` split only at the top level (not inside `(…)`, `[…]`, backticks of any
181
- width or `<code>`), and a bullet at 0–4 spaces under the label is an item — fenced,
182
- `<pre>` and deeper-indented code is not.
183
- Placeholders are `<...>` (three or more dots) and `TODO`/`TBD`/`FIXME`; `<DoD>` and
184
- `<name>` are markup.
185
- `max_lines` is the single 200-line root budget the tool applies: the 60/200/100
186
- per-level figures in [`docs/`](./docs) are authoring guidance, not separate tool limits.
187
- `max_words` defaults to the 1500-word SPEC target; pass `max_words: 2000` to enforce the
188
- documented ceiling instead.
189
- Typed failures: `E_INPUT`, `E_NOT_FOUND`, `E_NO_FILES`, `E_IO`, `E_ABORTED`,
190
- `E_LIMIT_FILE`, `E_LIMIT_FILES`, `E_OUT_OF_ROOT`.
191
- A failure is a typed payload `{schema:"context-lint-failure/v1", ok:false, code, message, path?, bytes?, files?, max?}`
192
- that validates only against `context-lint-failure/v1`; a report is
193
- `{schema:"context-lint/v1", ok, verdict, closing_list, files, summary, truncated?}` and every file carries `lines`,
194
- `words`, `chars` and `tokens_est` (tokens are estimated as Cyrillic/3 + the rest/4). A directory
195
- target is scanned to depth 3, and `max_files` refuses early: the walk stops at the first file above
196
- the limit. Nothing is dropped silently: a directory below the depth limit, a link or junction that
197
- resolves outside the workspace root, and an unclosed fence, `<pre>` or HTML comment are INFO
198
- findings, so a `pass` never hides an unchecked region. An unclosed `<code>` is a
199
- line-local mask: it hides the rest of its line and is not reported (only fence,
200
- `<pre>` and HTML comments span lines and get an INFO marker). An abort is observed
201
- between files, every 2048 lines of a line-oriented check and at each gate, so the
202
- worst-case latency is one mask pass over a file capped by `max_file_bytes` (tens of
203
- milliseconds per 2 MB), not the whole scan. Commands count only inside code, a Task table
204
- or a Commands section, an HTML comment such as `<!-- npm install -->` is not a command, and `make`
205
- needs a target, so prose such as "Make sure …" is not a command. A scan started at the root also
206
- picks up fixture trees such as `eval/`; pass an explicit path for the canonical files.
207
-
208
- ### `orch_uni_snapshot`
113
+ With the OpenCode CLI:
209
114
 
210
- ```jsonc
211
- { "path": "lab/stack-test", "exclude": ["tmp"] } // exclude is optional and additive
115
+ ```sh
116
+ opencode plugin add opencode-orchestrator-uni
212
117
  ```
213
118
 
119
+ Or add it to the `plugins` array in `~/.config/opencode/opencode.json(c)`:
120
+
214
121
  ```jsonc
215
- { "root": "C:\\…\\lab\\stack-test",
216
- "files": [ { "path": "a.txt", "sha256": "…", "bytes": 1 } ],
217
- "aggregate": "…" }
122
+ {
123
+ "$schema": "https://opencode.ai/config.json",
124
+ "plugins": ["opencode-orchestrator-uni"]
125
+ }
218
126
  ```
219
127
 
220
- - `aggregate = sha256` over the sorted lines `"<relpath>\0<sha256>\n"` (`/` separators,
221
- codepoint order) — stable across runs, machines and traversal order, and it changes
222
- whenever one file's path or content changes.
223
- - Excluded by default (always): `node_modules`, `.git`, `.ae`; `exclude` adds names,
224
- whole relative paths or relative prefixes (`lab/tmp`). Symlinks are skipped.
225
- - Limits: ≤ 5000 files, ≤ 50 MiB per file — `E_LIMIT_FILES` / `E_LIMIT_BYTES` (fail the
226
- whole snapshot; a truncated snapshot would be a wrong aggregate). `E_NOT_FOUND` for a
227
- missing path or a non-directory, `E_ABORTED` when the session is stopped.
128
+ Pin a version with `opencode-orchestrator-uni@0.5.1`. For model options, use the
129
+ object form — see [Configure](#configure) and [Models](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/docs/en/reference.md#models).
228
130
 
229
- ### `orch_uni_sessions`
131
+ Restart the service after changing the config:
230
132
 
231
- ```jsonc
232
- {} // children of the calling session
233
- { "sessionID": "ses_…", "recursive": true } // whole subtree of another session
133
+ ```sh
134
+ opencode service restart
234
135
  ```
235
136
 
236
- ```jsonc
237
- { "sessionID": "ses_parent", "recursive": false, "source": "session.index",
238
- "authoritative": false, "indexSize": 1, "count": 1,
239
- "limitations": [ … ],
240
- "sessions": [ { "sessionID": "ses_child", "parentID": "ses_parent", "agent": "explore",
241
- "model": "deepseek/deepseek-flash", "variant": "default", "title": "…",
242
- "cost": 0.0005, "tokens": { "input": 3491, "output": 2 },
243
- "created": "2026-10-15T04:01:06.627Z", "updated": "…" } ] }
244
- ```
137
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
245
138
 
246
- `source` tells which route produced the answer:
139
+ ### Configure
247
140
 
248
- - `session.list` — this build exposes a session listing API; the answer is
249
- **authoritative** and complete (`recursive` walks the subtree page by page).
250
- - `session.index` — the fallback used by OpenCode 2.0.28: `ctx.session` has
251
- `get/create/…` but **no `list`**, so children cannot be enumerated from the store. The
252
- plugin subscribes to `session.created` / `session.deleted` events at setup and keeps
253
- `child → parent` in its plugin-scoped storage; per-child details are read from
254
- `ctx.session.get()` at call time. The answer is **not authoritative**; the caveats are
255
- returned in `limitations` (sessions created before the plugin instance started are
256
- invisible, and an event gap hides children created while the plugin was unloaded).
257
- - `E_NO_API` — neither route exists. The context-scan fallback (looking for the
258
- `subagent` tool parts in `ctx.session.context`) was evaluated and rejected: in-flight
259
- children carry no session id in their tool part and compaction drops the history.
260
- Open question: a plugin-facing session listing (or a `parentID` filter on
261
- `ctx.session.context`) would remove the fallback entirely.
141
+ All options are optional and live on the plugin entry:
262
142
 
263
- `E_INPUT` for malformed arguments, `E_NOT_FOUND` when the parent session does not exist,
264
- `E_ABORTED` when the session is stopped.
143
+ - `locale` — `"en"` or `"ru"`; full tags such as `"ru-RU"` are accepted.
144
+ - `model` / `agents` — default and per-agent models in
145
+ `"provider/model[#variant]"` or `{ providerID, model, variant }` form.
146
+ - `sessionGuard` — prompt-budget reminders and the compaction limit.
147
+ - `toolOutputGuard` — caps large `shell`/`bash`/`webfetch` output.
265
148
 
266
- ### `orch_uni_session_stats`
149
+ Invalid values are ignored with an `[orchestrator.uni] …` warning. Full option
150
+ reference: [Models](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/docs/en/reference.md#models),
151
+ [Locale](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/docs/en/reference.md#locale), and
152
+ [Session Hygiene](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/docs/en/reference.md#session-hygiene).
267
153
 
268
- ```jsonc
269
- {} // counters of the calling session
270
- { "sessionID": "ses_…" } // any session on this server, not only a child
271
- ```
154
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
272
155
 
273
- ```jsonc
274
- { "ok": true, "sessionID": "ses_…",
275
- "counters": {
276
- "requests": 12, "promptTokensLast": 151204, "promptTokensMax": 161002,
277
- "promptTokensMedian": 148900, "band": 1, "compactions": 1,
278
- "compactionAttempts": 1, "compactionFailures": 0, "compactionSource": "event",
279
- "reminders": 2, "toolCalls": 31, "outputsCapped": 1,
280
- "outputCharsSaved": 18422, "errors": 0,
281
- "toolCallsByTool": { "shell": 18, "bash": 6, "webfetch": 2, "read": 5 },
282
- "toolOutputsCappedByTool": { "shell": 1, "bash": 0, "webfetch": 0, "read": 0 }
283
- },
284
- "limits": { "promptTokensSoft": 150000, "maxCompactions": 1, "maxToolOutputChars": 20000 },
285
- "overBudget": true, "compactionLimitReached": true, "toolsPerTurn": 2.58,
286
- "tracked": true,
287
- "guard": { "available": true, "hooksRegistered": true, "sessionEnabled": true,
288
- "toolOutputEnabled": true,
289
- "toolOutputTools": ["shell", "bash", "webfetch"],
290
- "compactionSource": "event", "eventLive": true,
291
- "streamFailures": 0, "unkeyedCalls": 0 } }
292
- ```
156
+ ## Usage
293
157
 
294
- `sessionID` may name **any session on this server**, not only a child of the caller;
295
- that is deliberate — the orchestrator uses the report to control every slice it runs.
296
- Counters are per plugin instance, and `tracked` is `true` once any event or hook
297
- observed the session; a `session.deleted` event forgets it. `band` is the highest
298
- budget level reached (`0` below the soft threshold); `promptTokensMedian` is the
299
- median over the last 256 requests; `toolCalls` and the per-tool counters include
300
- nested Code Mode calls; `toolCallsByTool` and `toolOutputsCappedByTool` split calls
301
- and capped outputs by the normalized tool name (namespace stripped, lower-case);
302
- `outputCharsSaved` is a character count, not bytes, and an upper estimate (the
303
- injected stub is subtracted). `compactions` counts confirmed
304
- `session.compaction.ended` events only; `compactionAttempts` counts every
305
- `session.compaction` hook call, so on a host with a dead or absent event stream
306
- attempts keep growing while `compactions` and `compactionLimitReached` stay put (a
307
- failed attempt is never a stop signal). A dead stream is reported once in
308
- `guard.streamFailures`; the per-session `errors` counts hook failures only, so a
309
- transport incident is not attributed to every session. Duplicate compaction events
310
- are counted once only when the host supplies `event.id` or a durable position;
311
- without either identity a re-delivered event may be counted again (fail-open in the
312
- direction of counting). A
313
- session the store does not return and this instance never observed is
314
- `E_NOT_FOUND` (recognized by the typed `_tag`/`name`/`code` — `Session.NotFoundError`,
315
- `SessionNotFoundError`, `NotFoundError`, `ENOENT` — with a narrow message fallback);
316
- `session.get` returning `undefined` for an untracked session is the same not-found
317
- answer; a transient `session.get` failure (a lock, permission, `unknown database`) is
318
- `E_INTERNAL`; a known session with zero requests is a valid report with
319
- `tracked: false`. Without `session.get`,
320
- an untracked session is `E_NO_API`, a tracked one is `ok` with `session: null` and a
321
- `limitations` entry. Codes: `E_INPUT`, `E_NOT_FOUND`, `E_INTERNAL`, `E_NO_API`,
322
- `E_UNAVAILABLE` (the guard is not registered or its hooks failed), `E_ABORTED`.
323
-
324
- ## Session hygiene
325
-
326
- The plugin implements the [`session-discipline.md`](./docs/en/session-discipline.md)
327
- protocol: a session is one slice, the prompt budget is ≤150k tokens, at most one
328
- compaction, tool output goes to a file, and a slice ends with a handoff.
329
-
330
- The runtime part is three hooks (`session.context`, `session.compaction`,
331
- `tool.execute.after`) plus a compaction-event subscription:
332
-
333
- - `session.context` — estimates the prompt (system + messages) and, when it crosses
334
- a budget level, adds one short reminder. The reminder is injected **once, when a
335
- new level is crossed**, not on every request; within a level the text is stable,
336
- so the prefix cache is not broken.
337
- - compaction events — the `session.compaction` hook is registered on **every** host:
338
- it counts attempts (`compactionAttempts`) and sends a tentative note to the
339
- nearest request; the note names no compaction number and claims no limit. Only
340
- `session.compaction.ended` confirms a compaction (deduplicated by `event.id` or
341
- the durable position) and raises a confirmed note with the number and the limit;
342
- `session.compaction.failed` increments `compactionFailures`, drops the tentative
343
- note and keeps a confirmed note that is still waiting for delivery. If the event
344
- stream ends or throws, `guard.eventLive` turns `false`,
345
- `guard.compactionSource` becomes `hook` and `guard.streamFailures` becomes `1`
346
- (the per-session `errors` stays `0`); the hook keeps counting attempts, but no
347
- confirmed compaction and no false "limit reached" is invented on that path.
348
- - `tool.execute.after` — for `shell`, `bash`, `webfetch` it caps output over
349
- `maxChars`: the full text goes to a private per-process root
350
- `os.tmpdir()/opencode-tool-output-<random>/` (POSIX 0700 for the root and the
351
- session directory, 0600 for the file). Windows ignores mode (measured: 0666),
352
- so privacy there rests on the ACL of `os.tmpdir()` and is not checked by code.
353
- The model gets a head and the path; the root is created with `mkdtemp` at plugin
354
- setup, so a
355
- pre-created `opencode-tool-output` directory or junction is never used; on POSIX
356
- the root must belong to the current uid with no group/other access, otherwise
357
- the guard disables itself with a warning (fail-open: results are untouched).
358
- Session directories (`s-<uuid>`) and files (`c-<uuid>.txt`, written with `wx`)
359
- are random, and each `realpath` is proven inside the root. The root is removed
360
- when the plugin unloads (best effort). A lone surrogate is normalized once
361
- (U+FFFD), so the file, the head and the counters describe the same string.
362
- Residual risk accepted: a same-user
363
- attacker can still swap a session directory between `realpath` and `writeFile`
364
- (Node has no `openat`). If `TMPDIR` points inside the workspace, the files land
365
- there instead. `read`, `write`, `edit`, `execute` and the kit's own tools are
366
- not capped, and a kit tool named without its namespace (`session_stats`) is
367
- refused in the config with a warning.
368
-
369
- `maxCompactions: 0` disables the compaction limit. The guard is fail-open: a hook
370
- error neither breaks the request nor corrupts the result. Either part can be
371
- disabled with its `enabled` flag:
158
+ ### Quick Start
372
159
 
373
- ```jsonc
374
- {
375
- "plugins": [
376
- {
377
- "package": "opencode-orchestrator-uni",
378
- "options": {
379
- "sessionGuard": { "enabled": true, "promptTokensSoft": 150000, "maxCompactions": 1 },
380
- "toolOutputGuard": {
381
- "enabled": true,
382
- "maxChars": 20000,
383
- "headChars": 4000,
384
- "tools": ["shell", "bash", "webfetch"]
385
- }
386
- }
387
- }
388
- ]
389
- }
390
- ```
160
+ 1. Install the plugin, restart OpenCode, and open a session in the project directory.
161
+ 2. Set `model` — or leave it unset so subagents inherit the session model.
162
+ 3. Give the orchestrator a slice. Use `/spec` before implementation, `/agents-md`
163
+ for an `AGENTS.md`, and `/handoff` to close the slice.
391
164
 
392
- Slice metrics — `orch_uni_session_stats`; closing a slice — the `/handoff` command.
165
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
393
166
 
394
- ## Models
167
+ ### The Audit Loop
395
168
 
396
- Agent definitions carry no `model`; models are configured on the plugin entry in
397
- `opencode.json(c)`:
169
+ `factcheck` marks stale work; `empiric` reproduces and measures; `invariant`
170
+ probes states and hostile input; `arbiter` resolves disputes. `meta` gates the
171
+ reports, `redteam` checks acceptance, and `fix-runner` closes the list. The
172
+ process documents ship as the `audit-kit` reference and live in
173
+ [`docs/en`](https://github.com/OniVe/opencode-orchestrator-uni/tree/main/docs/en).
398
174
 
399
- ```jsonc
400
- {
401
- "$schema": "https://opencode.ai/config.json",
402
- "plugins": [
403
- {
404
- "package": "opencode-orchestrator-uni",
405
- "options": {
406
- // Default for every kit agent.
407
- "model": "deepseek/deepseek-flash",
408
- // Per-agent overrides (id -> string or expanded form).
409
- "agents": {
410
- "meta": "deepseek/deepseek-reasoner",
411
- "fix-runner": { "providerID": "anthropic", "model": "claude-sonnet-4-5", "variant": "high" }
412
- }
413
- }
414
- }
415
- ]
416
- }
417
- ```
175
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
418
176
 
419
- - `model` — default model for every kit agent; `agents.<id>` — per-agent override.
420
- Both accept `"provider/model[#variant]"` or `{ providerID, model, variant }`.
421
- - Without options, kit agents keep no model, so subagents inherit the session's model.
422
- - Invalid values are ignored with a warning (`[orchestrator.uni] …`).
423
- - Subagents run on their configured model; the primary `orchestrator` runs on the
424
- session's model, so a model set for `orchestrator` is informational for clients
425
- (as of OpenCode 2.0.28).
426
- - An `agents` config entry or a local Markdown agent file that defines its own
427
- model still wins — configuration is applied after package plugins.
177
+ ### Agents
428
178
 
429
- ## Locale
179
+ | Agent | Mode | Role |
180
+ | -------------- | ---------- | ----------------------------------------------------------------- |
181
+ | `orchestrator` | `primary` | Root orchestrator; delegates edits; writes audit/state artifacts |
182
+ | `factcheck` | `subagent` | Read-only: staleness pre-pass (already done / outdated) |
183
+ | `empiric` | `subagent` | Read-only: reproductions, measurements, first sources |
184
+ | `invariant` | `subagent` | Read-only: states, cancels, races, hostile input |
185
+ | `arbiter` | `subagent` | Read-only: resolves disputed findings only |
186
+ | `meta` | `subagent` | Read-only: meta-audit of reports (pass/fail gate) |
187
+ | `redteam` | `subagent` | Read-only: acceptance red team (release gates) |
188
+ | `context-lint` | `subagent` | Read-only: audits AGENTS.md/SPEC.md for token efficiency |
189
+ | `fix-runner` | `subagent` | Writer: applies the fix list and proves closure |
430
190
 
431
- Agent definitions and process documents ship per locale — `agents/<locale>/`
432
- and `docs/<locale>/`; currently `ru` and `en` (the English content is a
433
- translation of the Russian originals). The kit picks one locale in this order:
191
+ All agents ship without a model; definitions live in [`agents/`](https://github.com/OniVe/opencode-orchestrator-uni/tree/main/agents) and
192
+ are parsed at setup. "Read-only" means `edit` is denied and the prompt forbids
193
+ writes; `shell` stays allowed for verification (accepted risk I-M15).
434
194
 
435
- 1. `options.locale` (`"ru"` or `"en"`; full tags such as `"ru-RU"` are accepted);
436
- 2. auto-detection: `LC_ALL` / `LC_MESSAGES` / `LANG`, then the runtime's Intl
437
- locale (the OS setting);
438
- 3. the default: `en`.
195
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
439
196
 
440
- ```jsonc
441
- {
442
- "$schema": "https://opencode.ai/config.json",
443
- "plugins": [
444
- {
445
- "package": "opencode-orchestrator-uni",
446
- "options": { "locale": "ru", "model": "deepseek/deepseek-flash" }
447
- }
448
- ]
449
- }
450
- ```
197
+ ### Commands
451
198
 
452
- A requested locale that ships no content falls back to the default locale and is
453
- reported with a warning in the plugin log. Both locales register the same agents
454
- under the same ids — switching the locale only changes the language of the
455
- prompts, the process documents and the reference description.
199
+ | Command | What it does |
200
+ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
201
+ | `/agents-md` | Injects the AGENTS.md audit/draft workflow: `context-efficiency.md` template, `orch_uni_context_lint`, `context-lint`, approval before writing. |
202
+ | `/spec` | Injects the SPEC.md workflow: brief, clarifying questions, EARS/AC/traceability, `orch_uni_context_lint`, `context-lint`, approval before writing. |
203
+ | `/handoff` | Injects the slice-closing workflow: session and working-tree facts, the `session-discipline.md` handoff template, writes `docs/planning/handoff-<YYYY-MM-DD>.md`. |
456
204
 
457
- ## Requirements
205
+ A command is a prompt, not a writer: it injects a workflow into the current
206
+ session, and the orchestrator follows the usual evidence and approval rules.
458
207
 
459
- - **OpenCode 2.x** with the V2 plugin API (the `plugins` config field). V1's plugin
460
- loader expects the legacy `server()` export and will not load this plugin.
208
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
461
209
 
462
- ## Install
210
+ ## Reference
463
211
 
464
- ### With the OpenCode CLI
212
+ The deep reference lives in [`docs/en/reference.md`](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/docs/en/reference.md):
213
+ write guard, all five tools with schemas and limits, session hygiene, models,
214
+ locale, precedence, and notes.
465
215
 
466
- ```sh
467
- opencode plugin add opencode-orchestrator-uni
468
- ```
216
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
469
217
 
470
- ### Manually
218
+ ## Development
471
219
 
472
- Add the package to the `plugins` array in your global
473
- `~/.config/opencode/opencode.json(c)`:
220
+ There is no build. `index.ts` is the shipped entry point.
474
221
 
475
- ```jsonc
476
- {
477
- "$schema": "https://opencode.ai/config.json",
478
- "plugins": ["opencode-orchestrator-uni"]
479
- }
222
+ ```sh
223
+ npm install
224
+ npm run typecheck
225
+ npm test
226
+ npm run check:locales
227
+ npm run verify:kit
228
+ npm run lint
229
+ npm run lint:fix
480
230
  ```
481
231
 
482
- Pin a version if you prefer:
232
+ `npm test` scans only `./test/` and now runs **438 tests in 11 files** — 437 pass
233
+ and 1 skip. A bare `bun test` from the root is red by design: it also picks up
234
+ intentionally failing fixtures under `eval/cases/` and drafts under `lab/`; use
235
+ `npm test`.
483
236
 
484
- ```jsonc
485
- {
486
- "$schema": "https://opencode.ai/config.json",
487
- "plugins": ["opencode-orchestrator-uni@0.1.0"]
488
- }
489
- ```
237
+ `check:locales` and `verify:kit` need a git checkout — the npm package ships no
238
+ `tools/` (`stand: unavailable`). [Biome](https://biomejs.org)
239
+ ([`biome.json`](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/biome.json)) covers `index.ts`, `lib/`, `test/` and `eval/*.ts`,
240
+ skips `lab/` and `eval/results/`, and freezes `eval/cases/**` and
241
+ `eval/baselines/**`; baseline records use `JSON.stringify(…, null, 2)` style, so
242
+ reformatting them would churn on `eval:recompute`. `lint/style/noNonNullAssertion`
243
+ is the one rule disabled.
490
244
 
491
- To set kit models at the same time, use the object form — see [Models](#models):
245
+ For a live server, point a project config at the checkout:
246
+ `"plugins": ["file:///C:/path/to/opencode-orchestrator-uni"]`. Local plugin
247
+ paths must be directories; OpenCode reloads them on change.
492
248
 
493
- ```jsonc
494
- {
495
- "$schema": "https://opencode.ai/config.json",
496
- "plugins": [
497
- {
498
- "package": "opencode-orchestrator-uni",
499
- "options": { "model": "deepseek/deepseek-flash" }
500
- }
501
- ]
502
- }
503
- ```
249
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
504
250
 
505
- Restart the OpenCode service after changing the config:
251
+ ## Roadmap
506
252
 
507
- ```sh
508
- opencode service restart
509
- ```
510
-
511
- ## Precedence
253
+ The current published release is **0.5.1**. Pre-1.0, a breaking change bumps the
254
+ minor version. Release notes live on [GitHub Releases][releases-url]; planned
255
+ work and known issues live in the [issue tracker][issues-url].
512
256
 
513
- Agent **definitions** are only filled in for ids that are not defined elsewhere: an
514
- agent with the same id coming from your configuration — an `agents` config entry, or a
515
- Markdown file under `~/.config/opencode/agents/` (or `.opencode/agents/`) — wins and the
516
- plugin skips its definition. A **configured model** (see [Models](#models)) is still
517
- applied to kit agents; a model defined by your own agent entry or file wins over it,
518
- because configuration is applied after package plugins. The same skip-if-taken rule
519
- applies to the `audit-kit` reference.
257
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
520
258
 
521
- If you previously installed the kit by copying agent files into your config directory,
522
- **remove those copies** after enabling the plugin, otherwise the stale copies keep
523
- overriding the plugin-provided definitions.
259
+ ## Contributing
524
260
 
525
- ## Development
261
+ See [`CONTRIBUTING.md`](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/CONTRIBUTING.md). Fork, create a branch
262
+ (`git checkout -b feature/AmazingFeature`), commit with
263
+ [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`,
264
+ `docs:`; `commitlint` enforces the format), push, and open a pull request. `ru` is
265
+ the reference locale; `en` must mirror it. After editing agents or docs, run
266
+ `npm run check:locales`; after README edits, run `npm test` (heading and fence
267
+ parity).
526
268
 
527
- There is no build. `index.ts` is the shipped entry point.
269
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
528
270
 
529
- ```sh
530
- npm install # installs gray-matter + dev tooling
531
- npm run typecheck # tsc --noEmit
532
- npm test # bun test ./test/ (hermetic: registration, tools, validator, snapshot, prompt↔schema + identifier guards)
533
- npm run check:locales # bun tools/locale-check.ts (ru↔en parity of agents + docs)
534
- npm run verify:kit # bun tools/kit-verify.ts (locales, tests, lint, types — the kit's stand)
535
- npm run lint # biome check . (lint + format + import order; read-only)
536
- npm run lint:fix # biome check --write . (applies safe fixes)
537
- ```
271
+ ## License
538
272
 
539
- The `check:locales` and `verify:kit` scripts need a git checkout of the kit — the npm package ships no `tools/` directory, so in an installed package they are unavailable (`stand: unavailable` plus your own reproducible checks).
273
+ Distributed under the MIT License. See [`LICENSE`](https://github.com/OniVe/opencode-orchestrator-uni/blob/main/LICENSE).
540
274
 
541
- `npm test` scans only `./test/` — the shipped suite (400 tests in 10 files, up from
542
- 72 in 5 before the identifier guards). A bare `bun test` from the repository root is red by
543
- design: the positional argument of `bun test` is a path substring filter, so the
544
- bare command also picks up the intentionally failing fixtures under `eval/cases/`
545
- and the scratch drafts under `lab/`. Use `npm test` [F5].
275
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
546
276
 
547
- [Biome](https://biomejs.org) ([`biome.json`](./biome.json)) covers the code surface —
548
- `index.ts`, `lib/`, `test/`, `eval/*.ts` — and skips `lab/` and `eval/results/` through the
549
- git ignore file, plus two frozen data kinds: `eval/cases/**` (fixtures with intentional
550
- defects) and `eval/baselines/**` (records generated in `JSON.stringify(…, null, 2)` style;
551
- reformatting them would churn on the next `eval:recompute`). `lint/style/noNonNullAssertion`
552
- is the one rule turned off — the dev contour and tests use `!` deliberately after in-code
553
- checks.
277
+ ## Contact
554
278
 
555
- Test against a live server by pointing a project config at the checkout:
279
+ Questions, bugs and feature requests: [GitHub Issues][issues-url].
556
280
 
557
- ```jsonc
558
- {
559
- "$schema": "https://opencode.ai/config.json",
560
- "plugins": ["file:///C:/path/to/opencode-orchestrator-uni"]
561
- }
562
- ```
281
+ Project Link: [https://github.com/OniVe/opencode-orchestrator-uni][repo-url]
563
282
 
564
- Configured local plugin paths must be directories (or `file://` URLs of directories);
565
- OpenCode watches them and reloads on change.
283
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
566
284
 
567
- ## Notes
285
+ ## Acknowledgments
568
286
 
569
- - One runtime dependency: [`gray-matter`](https://www.npmjs.com/package/gray-matter)
570
- for frontmatter parsing. OpenCode installs package plugin dependencies automatically.
571
- - Plugin id: `orchestrator.uni`.
572
- - Ships only the files listed in `package.json#files`: `index.ts`, `lib/`, `schemas/`,
573
- `agents/`, `docs/`, `README.md`, `README.ru.md`, `LICENSE`.
287
+ - [Best-README-Template](https://github.com/othneildrew/Best-README-Template) — the README structure.
288
+ - [OpenCode V2 plugin documentation](https://opencode.ai/v2/docs/build/plugins) — the plugin API.
289
+ - [shields.io](https://shields.io) — the badges.
290
+ - [Bun](https://bun.sh), [Biome](https://biomejs.org) and
291
+ [semantic-release](https://semantic-release.gitbook.io/) — the toolchain.
574
292
 
575
- ## License
293
+ <p align="right">(<a href="#readme-top">back to top</a>)</p>
576
294
 
577
- MIT
295
+ [npm-shield]: https://img.shields.io/npm/v/opencode-orchestrator-uni?style=for-the-badge
296
+ [npm-url]: https://www.npmjs.com/package/opencode-orchestrator-uni
297
+ [ci-shield]: https://img.shields.io/github/actions/workflow/status/OniVe/opencode-orchestrator-uni/ci.yml?branch=main&style=for-the-badge
298
+ [ci-url]: https://github.com/OniVe/opencode-orchestrator-uni/actions/workflows/ci.yml
299
+ [license-shield]: https://img.shields.io/github/license/OniVe/opencode-orchestrator-uni?style=for-the-badge
300
+ [license-url]: https://github.com/OniVe/opencode-orchestrator-uni/blob/main/LICENSE
301
+ [opencode-shield]: https://img.shields.io/badge/OpenCode-V2-000000?style=for-the-badge
302
+ [opencode-url]: https://opencode.ai/v2/docs/build/plugins
303
+ [typescript-shield]: https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white
304
+ [typescript-url]: https://www.typescriptlang.org/
305
+ [bun-shield]: https://img.shields.io/badge/Bun-000000?style=for-the-badge&logo=bun&logoColor=white
306
+ [bun-url]: https://bun.sh/
307
+ [biome-shield]: https://img.shields.io/badge/Biome-60A5FA?style=for-the-badge&logo=biome&logoColor=white
308
+ [biome-url]: https://biomejs.org/
309
+ [semantic-release-shield]: https://img.shields.io/badge/semantic--release-E10079?style=for-the-badge
310
+ [semantic-release-url]: https://semantic-release.gitbook.io/
311
+ [repo-url]: https://github.com/OniVe/opencode-orchestrator-uni
312
+ [issues-url]: https://github.com/OniVe/opencode-orchestrator-uni/issues
313
+ [releases-url]: https://github.com/OniVe/opencode-orchestrator-uni/releases