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