opencode-orchestrator-uni 0.2.0 → 0.3.0
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 +6 -1
- package/README.ru.md +6 -1
- package/agents/en/arbiter.md +4 -4
- package/agents/en/empiric.md +22 -22
- package/agents/en/factcheck.md +4 -4
- package/agents/en/fix-runner.md +13 -9
- package/agents/en/invariant.md +19 -19
- package/agents/en/meta.md +9 -9
- package/agents/en/orchestrator.md +21 -20
- package/agents/en/redteam.md +9 -9
- package/agents/ru/fix-runner.md +7 -3
- package/agents/ru/orchestrator.md +4 -3
- package/docs/en/call-mechanics.md +64 -57
- package/docs/en/instruction-other-session.md +12 -12
- package/docs/en/process-ready-loop.md +10 -8
- package/docs/en/root-runbook.md +12 -12
- package/docs/ru/call-mechanics.md +13 -6
- package/docs/ru/instruction-other-session.md +2 -2
- package/docs/ru/process-ready-loop.md +3 -1
- package/docs/ru/root-runbook.md +3 -3
- package/lib/tools.ts +95 -19
- package/package.json +2 -1
- package/schemas/fix-runner.v1.json +11 -8
|
@@ -110,15 +110,20 @@ subagent({
|
|
|
110
110
|
### Фаза 5. Правки и повтор
|
|
111
111
|
- Правки начинаются только после фиксации вердикта свода (и верификации/арбитража). До вердикта — не править.
|
|
112
112
|
- Правки по порогу: микроправки (1–3 строки, тексты/доки) — root сам; средние/крупные (мультифайл, логика, тесты) — субагент `fix-runner`; спорное до делегирования решает root (после арбитра).
|
|
113
|
+
- Несколько `fix-runner` параллельно — только в отдельных рабочих пространствах, и только когда их **наборы файлов** не могут пересечься. Норма безусловна: «писатель один на рабочее пространство; не запускай двух писателей, чьи **наборы файлов** могут пересечься» (даже в разных пространствах). Непересечение считается по файлам (пути + правило поиска однотипных мест класса), а не по id списка; пересечение наборов ⇒ сериализация фиксеров либо разнесение путей.
|
|
114
|
+
- Рабочее пространство фиксера — git worktree **вне дерева проекта** со своей веткой: основной путь `git worktree add -b fix/<round>-<n> <путь> <base>`; `worktree_create` — только с немедленным `git switch -c fix/<round>-<n>` (detached HEAD для сведения не годится). Посадка сессии: `session_move({sessionID, directory})`, если инструмент доступен, иначе `cd <абс. путь>` и абсолютные пути; если ни то ни другое недоступно — параллельный запуск запрещён.
|
|
115
|
+
- Сведение — строго по **полному hex SHA** из отчёта: `commit` принимается только как полный hex SHA — проверка `^[0-9a-f]{40}$` ДО любых git-команд (`git check-ref-format --branch` барьером НЕ является: refname пропускает `; & | > # $()` и backticks; `git rev-parse --verify '<x>^{commit}'` тоже резолвит имена). SHA передаётся в команды **отдельным аргументом в кавычках** (`git cat-file -t "$sha"`); строки из отчётов в shell-слова не подставлять. Имена веток из отчётов для сведения не использовать вовсе; если всё же нужны — только строгий шаблон `^fix/[A-Za-z0-9][A-Za-z0-9._-]*$` + кавычки. Семантические проверки перед сведением: `git cat-file -t <sha>` = `commit` (после hex-проверки); `git merge-base --is-ancestor <base> <sha>` (sha — потомок base); `git diff --quiet <base> <sha>` должно быть **ложно** (дельта непуста); `<sha> != <base>`. `Already up to date` (пустая дельта) ⇒ статус **`not_consolidated`**, не `closed`; нет `commit` или `workspace` в отчёте ⇒ **`not_closed`** (precondition root'а). Ветку/коммиты без отчёта `closed` не сливать; `closed` и доказательство приёмки (after/stand) — на сведённом commit SHA; worktree не удалять до подтверждённого переноса коммита, после — `git worktree remove --force` и осознанное решение по ветке. Отчёт не пришёл (`no_report`: фиксер упал/таймаут) — root фиксирует в своде работу (worktree, ветка, base SHA) и `no_report`; приоритет — resume в том же worktree, иначе убрать worktree и решить судьбу ветки; частичный коммит без отчёта в основную ветку не вливается.
|
|
113
116
|
- Вызов фиксера:
|
|
114
117
|
```
|
|
115
118
|
subagent({agent:"fix-runner", prompt:"К закрытию:\n1) <id | локация | механизм |
|
|
116
119
|
класс/область (пути + правило поиска однотипных мест) | repro-до |
|
|
117
|
-
критерий-после | severity>\nГраницы:
|
|
118
|
-
|
|
120
|
+
критерий-после | severity>\nГраницы: набор файлов: <пути>; рабочее пространство:
|
|
121
|
+
<абс. путь worktree вне дерева проекта>; ветка: fix/<round>-<n>; base: <sha>;
|
|
122
|
+
бюджет/лимит: <время, число правок>; коммит: <да/нет, сообщение fix-<round>>.
|
|
123
|
+
Верни fix-runner/v1."})
|
|
119
124
|
```
|
|
120
|
-
- Root проверяет дельту (`git
|
|
121
|
-
- Стенд раунда (если в комплекте поставляется проверочный стенд —
|
|
125
|
+
- Root проверяет дельту (`git diff <round-base>..HEAD --stat` + ключевой прогон) до re-audit и обновляет реестр.
|
|
126
|
+
- Стенд раунда (если в комплекте поставляется проверочный стенд — инструмент `tools/kit-verify` (верификатор) каталога комплекта; в раундах по другим проектам — только если стенд явно включён в границы вызова): baseline → правки → повторный прогон стенда; дельта baseline→after прикладывается к отчёту (поле `stand`). Стенд поставляется в git-чекауте комплекта: в npm-пакете каталог `tools/` не поставляется → `stand: unavailable` и собственные воспроизводимые проверки.
|
|
122
127
|
- **re-audit = resume тех же сессий**:
|
|
123
128
|
```
|
|
124
129
|
subagent({agent:"empiric", sessionID:<emp>, background:true,
|
|
@@ -165,7 +170,7 @@ subagent({agent:"redteam", background:true,
|
|
|
165
170
|
| пара → root | полные SGR-отчёты | JSON-блоки |
|
|
166
171
|
| root → арбитр | только спорные пункты + evidence обеих сторон | список |
|
|
167
172
|
| root → верификатор | находка MAJOR (id, текст, evidence автора, ревизия состояния) | текст |
|
|
168
|
-
| root → fix-runner | список «к закрытию» (id, локация, класс/область, repro-до, критерий-после, severity) +
|
|
173
|
+
| root → fix-runner | список «к закрытию» (id, локация, класс/область, repro-до, критерий-после, severity) + границы (набор файлов, рабочее пространство, ветка, base) и бюджет | текст |
|
|
169
174
|
| root → re-audit | дельта правок + фактический список изменённых файлов + список закрытого | текст |
|
|
170
175
|
| root → redteam | заявка + свод всех кругов | текст |
|
|
171
176
|
| redteam → root | SGR | JSON |
|
|
@@ -194,6 +199,7 @@ subagent({agent:"redteam", background:true,
|
|
|
194
199
|
## 5. Отказы и восстановление
|
|
195
200
|
|
|
196
201
|
- Субагент упал/прерван → resume с «продолжи с места остановки, не переделывай»; при `Agent not found` — проверить файлы агентов. Источники не смешивать: определения агентов — каталог комплекта (`agents/`), копии — глобальный каталог агентов вашего харнесса (путь зависит от ОС) и проектный каталог агентов харнесса; снимок/восстановление конфигурации — `tools/config-guard` (хранитель конфигурации комплекта, если поставляется), не агенты.
|
|
202
|
+
- Повторный запуск фиксера после инцидента в том же пространстве — resume в том же worktree либо явное удаление/переименование ветки решением root'а (повторный `git worktree add -b fix/<round>-<n>` падает, пока ветка занята); `locked` worktree — `git worktree unlock` либо `git worktree remove -f -f`.
|
|
197
203
|
- Модель/провайдер недоступны → подождать/сменить модель по решению владельца, сессии сохранить.
|
|
198
204
|
- Ограниченная среда (Code Mode: действия только через `execute`+каталог, `subagent` недоступен) → аудиторский цикл не запускается; итог — «готово; штатный аудит отложен» с ревизией и планом прогона (§8).
|
|
199
205
|
- `arbiter` недоступен или бюджет исчерпан → root разбирает спорный пункт поштучно по фактам/цитатам и фиксирует как риск и эскалирует владельцу.
|
|
@@ -216,7 +222,8 @@ subagent({agent:"redteam", background:true,
|
|
|
216
222
|
|
|
217
223
|
## 7. Инварианты процесса
|
|
218
224
|
|
|
219
|
-
- root не аудирует и не подменяет вердикты; писатель
|
|
225
|
+
- root не аудирует и не подменяет вердикты; писатель один **на рабочее пространство**: root (микроправки) или `fix-runner` (средние/крупные); несколько `fix-runner` — параллельно только в отдельных worktree и с последующим сведением root'ом.
|
|
226
|
+
- Не запускай двух писателей, чьи **наборы файлов** могут пересечься, — безусловно, независимо от рабочего пространства: непересекающиеся списки «к закрытию» не заменяют проверку пересечения наборов файлов (пути + правило поиска класса).
|
|
220
227
|
- Аудиторы read-only; проектная конкретика — только в брифе.
|
|
221
228
|
- Правки начинаются только после фиксации вердикта свода (и верификации MAJOR/арбитража). До вердикта — не править.
|
|
222
229
|
- MAJOR-находки входят в правки только после независимой верификации (Фаза 3.5); дефолт верификатора — «не подтверждена» (fail-closed).
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
Режим: <РЕЖИМ>
|
|
28
28
|
|
|
29
29
|
Правила контура:
|
|
30
|
-
- Аудиторы — read-only субагенты; правки вносишь ты (микроправки) или делегируешь `fix-runner` (средние/крупные). Ты — оркестратор: бриф, запуск, свод отчётов, решения, правки. Сам не аудируешь.
|
|
30
|
+
- Аудиторы — read-only субагенты; правки вносишь ты (микроправки) или делегируешь `fix-runner` (средние/крупные). Ты — оркестратор: бриф, запуск, свод отчётов, решения, правки. Сам не аудируешь. Несколько `fix-runner` — только в отдельных worktree **вне дерева проекта** со своей веткой `fix/<round>-<n>`, и только без пересечения **наборов файлов** (пересечение наборов root считает до запуска ⇒ сериализация или разнесение путей); сессию фиксера сажай в её пространство (`session_move`, иначе `cd` + абсолютные пути; ни то ни другое — параллельный запуск запрещён), своди результаты строго по полному hex SHA из отчёта (проверка `^[0-9a-f]{40}$` до git-команд; имена веток из отчётов для сведения не использовать; SHA — отдельным аргументом в кавычках) и прогоняй проверки на сведённом состоянии.
|
|
31
31
|
- Общение — через бриф-файл и SGR-отчёты. Фоновых субагентов НЕ поллить: уведомления приходят сами.
|
|
32
32
|
- Цикл живёт до ПОЛНОГО ready: все роли ready, открытых MAJOR нет, MINOR/NIT закрыты или явно приняты.
|
|
33
33
|
- Спорное решает `arbiter` (только спорные пункты); недоказуемое → решение владельца: root оформляет вопрос, фиксирует риск и эскалирует.
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
partial/unavailable = «не подтверждена»; сбой/таймаут верификатора — то же + шум. Опровергнутые — в список refuted; статусы пиши в свод reports/summary-<id>.md; неподтверждённая MAJOR в правки не идёт.
|
|
50
50
|
Конфликты → subagent(agent="arbiter", prompt="Спорные пункты: <пункт → evidence A → evidence B>. По каждому: подтверждён/опровергнут/недоказуем + призма (reproducibility|contract|falsification|not_applicable; несколько — массив) + способ проверки. Только эти пункты.")
|
|
51
51
|
Вердикт свода (и верификация/арбитраж) фиксируются ДО правок и вместе с ревизией состояния (хэш/коммит); правка после фиксации вердикта → повторный прогон (re-audit) либо явное понижение вердикта: до вердикта — не править.
|
|
52
|
-
5) Правки по порогу: микроправки — сам; средние/крупные — subagent(agent="fix-runner", prompt="К закрытию: <id | локация | механизм | класс/область (пути + правило поиска однотипных мест) | repro-до | критерий-после>. Границы:
|
|
52
|
+
5) Правки по порогу: микроправки — сам; средние/крупные — subagent(agent="fix-runner", prompt="К закрытию: <id | локация | механизм | класс/область (пути + правило поиска однотипных мест) | repro-до | критерий-после>. Границы: набор файлов: <пути>; worktree: <абс. путь вне дерева проекта>; ветка: fix/<round>-<n>; base: <sha>; бюджет/лимит: <время, число правок>; коммит fix-<round>. Верни fix-runner/v1."). Перед re-audit проверь дельту (git diff <round-base>..HEAD --stat + ключевой прогон) и сведение по полному hex SHA (^[0-9a-f]{40}$ до git-команд + git cat-file -t <sha> = commit, git merge-base --is-ancestor <base> <sha>, непустая дельта: git diff --quiet <base> <sha> ложно; пустая дельта / Already up to date ⇒ not_consolidated, не closed); обнови risks.md (closed / accepted-risk с причиной).
|
|
53
53
|
6) Re-audit — resume ТЕХ ЖЕ сессий (background, один блок):
|
|
54
54
|
subagent(agent="empiric", sessionID="<emp>", background=true, prompt="Правки: <диапазон + фактический список изменённых файлов>; закрыто: <…>. Проверь закрытие своих находок и отсутствие регрессий на новом состоянии; верни обновлённый SGR.")
|
|
55
55
|
subagent(agent="invariant", sessionID="<inv>", background=true, prompt="… то же …")
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
│ для коммита/PR: SHIP, если правок после вердикта не было (ред-тим и мета не обязательны)
|
|
23
23
|
└─ НЕТ ─► [2] ПРАВКИ (закрыть MAJOR; MINOR/NIT — закрыть или явно принять с обоснованием, запись в risks-<id>.md)
|
|
24
24
|
▼
|
|
25
|
+
[2a] СВЕДЕНИЕ (если фиксеров было несколько: сведение веток по commit SHA → проверки на сведённом состоянии)
|
|
26
|
+
▼
|
|
25
27
|
[3] НОВОЕ СОСТОЯНИЕ X1 (фиксы закоммичены/зафиксированы)
|
|
26
28
|
▼
|
|
27
29
|
повтор [1] на X1 (оба аудитора, тот же бриф)
|
|
@@ -39,7 +41,7 @@
|
|
|
39
41
|
1. Все роли (эмпирик, инвариантщик; на воротах — и ред-тим) — `verdict=ready`.
|
|
40
42
|
2. Открытых MAJOR нет. MINOR/NIT: закрыты либо **явно приняты** с причиной (запись в `risks-<id>.md`; в сводке/PR: «принято как долг: …»).
|
|
41
43
|
3. Все расхождения разрешены арбитром; недоказуемое → решение владельца: root оформляет вопрос, фиксирует риск и эскалирует.
|
|
42
|
-
4. Повторный прогон идёт **по состоянию после правок**, а не по старому диффу. Никаких «переголосований» на том же снапшоте.
|
|
44
|
+
4. Повторный прогон идёт **по состоянию после правок**, а не по старому диффу. Никаких «переголосований» на том же снапшоте. При нескольких фиксерах X1 достигается сведением их веток по commit SHA и проверками на сведённом состоянии; `closed` ставится после сведения.
|
|
43
45
|
5. На релизных воротах — `meta` = pass (`gate`=pass), `closing_list` пуст/закрыт, `coverage_gaps` приняты владельцем (запись в `risks-<id>.md`). **SHIP ⟺ `meta.gate`=pass ∧ `closing_list` пуст/закрыт ∧ `coverage_gaps` приняты владельцем (запись в `risks-<id>.md`); иначе ship запрещён.**
|
|
44
46
|
6. MAJOR-находки входят в правки только после независимой верификации; дефолт верификатора — «не подтверждена» (fail-closed).
|
|
45
47
|
7. Вердикт и верификация фиксируются вместе с ревизией состояния (хэш каталога/коммит); любая правка после фиксации вердикта → повторный прогон (re-audit) либо явное понижение вердикта.
|
package/docs/ru/root-runbook.md
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
3. **Свод root'ом**: распарсить JSON-блоки; вычислить вердикты (ready/not_ready), открытые MAJOR, пересечения и расхождения (по location+механизму). Материализовать свод-артефакт `reports/summary-<id>.md` (определение — `call-mechanics.md` §4): по каждой MAJOR — {id, verifier_role, verdict, evidence}; refuted-список (явный, даже пустой); принятые риски; ревизия состояния (хэш каталога/коммит).
|
|
27
27
|
4. **Верификация MAJOR (перекрёстная, fail-closed)**: каждая MAJOR-находка подтверждается независимо **сессией противоположной роли** (не автором находки) — `subagent(agent="<sibling>", sessionID=<сессия противоположной роли>, prompt="Находка <id> контрагента: <текст>. Evidence автора: <команда→вывод>. Ревизия состояния: <хэш каталога/коммит>. Подтверди или опровергни своим воспроизведением; дефолт — не подтверждена. Верни: {id, verifier_role, verdict: confirmed|refuted|partial|unavailable, evidence}.")`. `verifier_role` ≠ роль автора; `partial`/`unavailable` = «не подтверждена»; `refuted` — в список refuted для сводки; сбой/таймаут верификатора — «не подтверждена» + шум. Не подтверждена → в правки не идёт (спор → шаг 5, арбитр); статусы верификации пишутся в свод `reports/summary-<id>.md` (шаг 3). Если MAJOR-находка независимо воспроизведена обеими ролями в их собственных прогонах — взаимное подтверждение засчитывается как верификация (обе стороны в свод), отдельный прогон не требуется. sessionID при вызове обязателен: без него это новая сессия, а не продолжение — дефект шага.
|
|
28
28
|
5. **Арбитр — только при расхождениях**: `subagent(agent="arbiter", prompt="Спорные пункты: <пункт → evidence A → evidence B>. По каждому: подтверждён/опровергнут/недоказуем + команда/цитата.")`. Вердикт свода (и верификация/арбитраж) фиксируются ДО правок и **вместе с ревизией состояния** (хэш каталога/коммит); любая правка после фиксации вердикта → повторный прогон (re-audit) либо явное понижение вердикта: до вердикта — не править.
|
|
29
|
-
6. **Правки** по порогу: микро — root сам; средние/крупные — `fix-runner` (вход: список «к закрытию» — id, локация, механизм, **класс/область** (пути + правило поиска однотипных мест), repro-до, критерий-после, severity — плюс границы и **бюджет/лимит (время, число правок)**; выход: fix-runner/v1 с before/after/`proof`/тестами). Отдельные коммиты `fix-<round>`; обновить реестр: `closed` / `accepted-risk` (с причиной). Root проверяет дельту до re-audit. Стенд раунда (если в комплекте поставляется проверочный стенд —
|
|
30
|
-
7. **Re-audit = resume те же сессии** (sessionID из шага 2): «Правки: <диапазон + фактический список изменённых файлов>. Проверь закрытие своих находок и отсутствие регрессий; верни обновлённый SGR.» — исторически так и делалось (продолжение сессий GLM/DS). sessionID обязателен: вызов без него (новая сессия вместо продолжения) — дефект шага.
|
|
29
|
+
6. **Правки** по порогу: микро — root сам; средние/крупные — `fix-runner` (вход: список «к закрытию» — id, локация, механизм, **класс/область** (пути + правило поиска однотипных мест), repro-до, критерий-после, severity — плюс границы и **бюджет/лимит (время, число правок)**; выход: fix-runner/v1 с before/after/`proof`/тестами). Отдельные коммиты `fix-<round>`; обновить реестр: `closed` / `accepted-risk` (с причиной). Root проверяет дельту раунда — `git diff <round-base>..HEAD --stat` (не только `git show --stat`) — до re-audit. Стенд раунда (если в комплекте поставляется проверочный стенд — инструмент `tools/kit-verify` каталога комплекта; в раундах по другим проектам — только если стенд явно включён в границы вызова): baseline стенда в промпт фиксера (если вывод передан — фиксер не дублирует прогон); закрытие — повторный прогон стенда; оба вывода — в поле `stand` отчёта. Стенд поставляется в git-чекауте комплекта: в npm-пакете каталог `tools/` не поставляется → `stand: unavailable` и собственные воспроизводимые проверки. Статусы `fix-runner`: `needs_decision` → решение root (при споре — арбитр; если решение за владельцем — эскалация) с фиксацией в реестре рисков; `not_closed` → повторная итерация с тем же id; `budget_exceeded` → расширить бюджет решением владельца либо разбить список и продолжить с того же id (коммиты и worktree сохранить до закрытия или уборки); поле `out_of_scope` (замечено вне списка) → включить в следующий список «к закрытию» или эскалировать. Статус **`no_report`** (отчёта нет: фиксер упал/таймаут) — root фиксирует в своде выполненную работу (worktree, ветка, base SHA) и `no_report`; ветку/коммиты НЕ сливать; приоритет — resume в том же worktree; иначе убрать: `git worktree remove --force` и решить судьбу ветки. Частичный коммит без отчёта в основную ветку не вливается. Несколько `fix-runner` — только в отдельных worktree **вне дерева проекта** (у каждого своя ветка `fix/<round>-<n>`); путь пространства сверяется префиксами путей (`git -C <project> rev-parse --show-toplevel` не должен быть префиксом канонического пути пространства; иначе — `needs_decision`, пространство не создаётся и не используется); перед запуском root считает пересечение **наборов файлов** (пути + правило поиска класса), а не только списков: пересечение ⇒ сериализация или разнесение путей. Посадка сессии в её пространство: `session_move({sessionID, directory})` (если доступен), иначе `cd <абс. путь>` + абсолютные пути; ни то ни другое — параллельный запуск запрещён. В свежем worktree зависимости ставятся до прогонов (`npm ci` / `bun install`). После завершения root сводит результаты строго по **полному hex SHA** из отчёта: `commit` принимается только как `^[0-9a-f]{40}$` — проверка ДО любых git-команд (имена веток из отчётов для сведения не использовать; `check-ref-format` и `rev-parse --verify` барьером не являются — резолвят имена); SHA — отдельным аргументом в кавычках; семантические проверки: `git cat-file -t <sha>` = `commit`, `git merge-base --is-ancestor <base> <sha>`, `git diff --quiet <base> <sha>` ложно (дельта непуста), `<sha> != <base>`; `Already up to date` (пустая дельта) ⇒ `not_consolidated`, не `closed`; merge/cherry-pick по SHA, прогоняет проверки на сведённом состоянии и только тогда фиксирует `closed`; worktree не удалять до подтверждённого переноса коммита, после — `git worktree remove --force` и осознанное решение по ветке; повторный запуск после инцидента — resume в том же worktree либо явное удаление/переименование ветки решением root'а (повторный `git worktree add -b` падает при занятой ветке), `locked` worktree — `git worktree unlock` либо `git worktree remove -f -f`.
|
|
30
|
+
7. **Re-audit = resume те же сессии** (sessionID из шага 2): «Правки: <диапазон + фактический список изменённых файлов>. Проверь закрытие своих находок и отсутствие регрессий; верни обновлённый SGR.» — исторически так и делалось (продолжение сессий GLM/DS). sessionID обязателен: вызов без него (новая сессия вместо продолжения) — дефект шага. Re-audit и `closed` — на **сведённом** состоянии: доказательство приёмки (after/stand) перепривязывается к сведённому commit SHA; `closed` без отчёта фиксера (`no_report`) запрещён; нет ключа `commit` или `workspace` в отчёте фиксера ⇒ `not_closed`.
|
|
31
31
|
8. **Повторяй шаги 3–7, пока не достигнут полный ready** (число кругов не ограничено; все роли ready, MAJOR=0). С 3-го круга нерешённые пункты — арбитр поштучно; повторяющиеся ложные — снять и записать как шум аудита.
|
|
32
32
|
9. **Ворота (релиз; порядок: ред-тим → мета)**: замороженный коммит → `redteam` (мандат фальсификации + корпуса/стенды) → цикл по его находкам (MAJOR — с верификацией сессией противоположной роли) → `meta` (на вход: отчёты + свод `reports/summary-<id>.md`; evidence, полнота, отсутствие «зелёные тесты = проверено»).
|
|
33
33
|
- **SHIP ⟺ `meta.gate`=pass ∧ `closing_list` пуст/закрыт ∧ `coverage_gaps` приняты владельцем (запись в `risks-<id>.md`); иначе ship запрещён.**
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
- root → re-audit: диапазон правок + краткое «что закрыто»;
|
|
42
42
|
- root → ред-тим: заявка автора + свод всех предыдущих кругов (объект фальсификации);
|
|
43
43
|
- root → мета: отчёты (пути) + свод `reports/summary-<id>.md`;
|
|
44
|
-
- root → fix-runner: список «к закрытию» (с классом/областью) + границы + бюджет/лимит (время, число правок)
|
|
44
|
+
- root → fix-runner: список «к закрытию» (с классом/областью) + границы (включая **набор файлов**, worktree вне дерева проекта, ветку `fix/<round>-<n>`, base SHA) + бюджет/лимит (время, число правок); при параллельном запуске — каждому отдельное пространство и ветка, а наборы файлов root проверяет на пересечение до запуска;
|
|
45
45
|
|
|
46
46
|
## Тонкости
|
|
47
47
|
|
package/lib/tools.ts
CHANGED
|
@@ -46,25 +46,76 @@ interface SchemaDocument {
|
|
|
46
46
|
readonly schema: JsonSchemaNode
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function errorMessage(error: unknown): string {
|
|
50
|
+
return error instanceof Error ? error.message : String(error)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Typed failure of the schema registry (`schemas/*.json`): an unreadable
|
|
55
|
+
* directory, an unreadable file, malformed JSON or a document of the wrong
|
|
56
|
+
* shape. `loadSchemas` throws it on purpose — otherwise the plugin would
|
|
57
|
+
* validate against a silently smaller registry — and the `validate_report`
|
|
58
|
+
* executor converts it into an `E_SCHEMA_IO` refusal, so a caller always gets a
|
|
59
|
+
* structured code plus the directory/file instead of a raw IO/parse exception.
|
|
60
|
+
*/
|
|
61
|
+
export class SchemaIoError extends Error {
|
|
62
|
+
readonly code = "E_SCHEMA_IO"
|
|
63
|
+
readonly directory: string
|
|
64
|
+
readonly file: string | undefined
|
|
65
|
+
|
|
66
|
+
constructor(message: string, directory: string, file?: string) {
|
|
67
|
+
super(message)
|
|
68
|
+
this.name = "SchemaIoError"
|
|
69
|
+
this.directory = directory
|
|
70
|
+
this.file = file
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
/**
|
|
50
75
|
* Read every `schemas/*.json` file into an id → schema map. Files are read in
|
|
51
|
-
* sorted order; a malformed document throws
|
|
52
|
-
* validate against a silently smaller registry).
|
|
76
|
+
* sorted order; a malformed document throws a `SchemaIoError` (fail-closed, typed).
|
|
53
77
|
*/
|
|
54
78
|
export async function loadSchemas(directory: string = SCHEMAS_DIRECTORY): Promise<Map<string, JsonSchemaNode>> {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
let names: string[]
|
|
80
|
+
try {
|
|
81
|
+
const entries = await readdir(directory, { withFileTypes: true })
|
|
82
|
+
names = entries
|
|
83
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
84
|
+
.map((entry) => entry.name)
|
|
85
|
+
.sort()
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw new SchemaIoError(`cannot read schemas directory ${directory}: ${errorMessage(error)}`, directory)
|
|
88
|
+
}
|
|
60
89
|
const schemas = new Map<string, JsonSchemaNode>()
|
|
61
|
-
for (const file of
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
90
|
+
for (const file of names) {
|
|
91
|
+
const absolute = path.join(directory, file)
|
|
92
|
+
let source: string
|
|
93
|
+
try {
|
|
94
|
+
source = await readFile(absolute, "utf8")
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new SchemaIoError(`cannot read schemas/${file} (${absolute}): ${errorMessage(error)}`, directory, absolute)
|
|
97
|
+
}
|
|
98
|
+
let parsed: unknown
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(source)
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new SchemaIoError(
|
|
103
|
+
`schemas/${file} (${absolute}) is not valid JSON: ${errorMessage(error)}`,
|
|
104
|
+
directory,
|
|
105
|
+
absolute,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
const document = asRecord(parsed) as Partial<SchemaDocument> | undefined
|
|
109
|
+
const id = document?.id
|
|
110
|
+
const schema = document?.schema
|
|
111
|
+
if (typeof id !== "string" || typeof schema !== "object" || schema === null) {
|
|
112
|
+
throw new SchemaIoError(
|
|
113
|
+
`schemas/${file} (${absolute}): expected { id: string, schema: object }`,
|
|
114
|
+
directory,
|
|
115
|
+
absolute,
|
|
116
|
+
)
|
|
66
117
|
}
|
|
67
|
-
schemas.set(
|
|
118
|
+
schemas.set(id, schema as JsonSchemaNode)
|
|
68
119
|
}
|
|
69
120
|
return schemas
|
|
70
121
|
}
|
|
@@ -298,13 +349,24 @@ export function resolveRoot(directory: string, input: string): string {
|
|
|
298
349
|
return path.isAbsolute(input) ? path.normalize(input) : path.resolve(directory, input)
|
|
299
350
|
}
|
|
300
351
|
|
|
352
|
+
/** Optional seams for tests; the plugin calls this with no options. */
|
|
353
|
+
export interface RegisterAuditToolsOptions {
|
|
354
|
+
/** Directory to read `schemas/*.json` from. Defaults to the shipped `schemas/`. */
|
|
355
|
+
readonly schemasDirectory?: string
|
|
356
|
+
}
|
|
357
|
+
|
|
301
358
|
/**
|
|
302
|
-
* Register the audit tools on the plugin context.
|
|
303
|
-
*
|
|
304
|
-
*
|
|
359
|
+
* Register the audit tools on the plugin context. The session index is loaded
|
|
360
|
+
* before the transform callback, because transforms are synchronous; the report
|
|
361
|
+
* schemas are not: they are re-read on every `validate_report` call, so a schema
|
|
362
|
+
* edited while the plugin is live is applied without a reload (a price of a few
|
|
363
|
+
* small JSON reads per validation).
|
|
305
364
|
*/
|
|
306
|
-
export async function registerAuditTools(
|
|
307
|
-
|
|
365
|
+
export async function registerAuditTools(
|
|
366
|
+
context: Plugin.Context,
|
|
367
|
+
options: RegisterAuditToolsOptions = {},
|
|
368
|
+
): Promise<() => void> {
|
|
369
|
+
const schemasDirectory = options.schemasDirectory ?? SCHEMAS_DIRECTORY
|
|
308
370
|
const directory = context.location.directory
|
|
309
371
|
const session = context.session
|
|
310
372
|
const index = new SessionIndex(context.storage)
|
|
@@ -337,6 +399,20 @@ export async function registerAuditTools(context: Plugin.Context): Promise<() =>
|
|
|
337
399
|
}
|
|
338
400
|
}
|
|
339
401
|
if (toolContext.signal.aborted) return failure("E_ABORTED", "validation aborted")
|
|
402
|
+
let schemas: Map<string, JsonSchemaNode>
|
|
403
|
+
try {
|
|
404
|
+
schemas = await loadSchemas(schemasDirectory)
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (error instanceof SchemaIoError) {
|
|
407
|
+
return failure(error.code, error.message, {
|
|
408
|
+
directory: error.directory,
|
|
409
|
+
...(error.file === undefined ? {} : { file: error.file }),
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
return failure("E_SCHEMA_IO", `cannot load schemas from ${schemasDirectory}: ${errorMessage(error)}`, {
|
|
413
|
+
directory: schemasDirectory,
|
|
414
|
+
})
|
|
415
|
+
}
|
|
340
416
|
const report = checkReport(source, { ...(expectedSchema === undefined ? {} : { expectedSchema }), schemas })
|
|
341
417
|
return json(report)
|
|
342
418
|
}
|
|
@@ -479,7 +555,7 @@ export async function registerAuditTools(context: Plugin.Context): Promise<() =>
|
|
|
479
555
|
description:
|
|
480
556
|
"Validate an SGR report: extract the last ```json block, resolve its schema, validate it and list prompt-level warnings. " +
|
|
481
557
|
"Read-only. Returns JSON {valid, schema, errors, warnings} or a typed code " +
|
|
482
|
-
"(E_INPUT|E_NO_JSON|E_PARSE|E_UNKNOWN_SCHEMA|E_SCHEMA_MISMATCH|E_INVALID|E_ABORTED).",
|
|
558
|
+
"(E_INPUT|E_NO_JSON|E_PARSE|E_UNKNOWN_SCHEMA|E_SCHEMA_MISMATCH|E_INVALID|E_SCHEMA_IO|E_ABORTED).",
|
|
483
559
|
input: VALIDATE_REPORT_INPUT,
|
|
484
560
|
options: { namespace: TOOL_NAMESPACE, codemode: true },
|
|
485
561
|
execute: validateReport,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "opencode-orchestrator-uni",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Native OpenCode V2 plugin that registers the orchestrator agent kit (root orchestrator, audit subagents, fix-runner) and exposes the kit's process documents as the audit-kit reference.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"lint": "biome check .",
|
|
46
46
|
"lint:fix": "biome check --write .",
|
|
47
47
|
"check:locales": "bun tools/locale-check.ts",
|
|
48
|
+
"verify:kit": "bun tools/kit-verify.ts",
|
|
48
49
|
"eval": "bun eval/run.ts",
|
|
49
50
|
"eval:selfcheck": "bun eval/selfcheck.ts",
|
|
50
51
|
"eval:recompute": "bun eval/recompute.ts"
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
"schema": { "type": "string", "const": "fix-runner/v1" },
|
|
8
8
|
"round": { "type": "string" },
|
|
9
9
|
"commit": { "type": ["string", "null"] },
|
|
10
|
+
"workspace": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["branch", "worktree", "base_commit"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"branch": { "type": "string" },
|
|
15
|
+
"worktree": { "type": "string" },
|
|
16
|
+
"base_commit": { "type": "string" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
10
19
|
"results": {
|
|
11
20
|
"type": "array",
|
|
12
21
|
"items": {
|
|
@@ -14,10 +23,7 @@
|
|
|
14
23
|
"required": ["id", "status", "change", "before", "after", "proof", "stand", "tests"],
|
|
15
24
|
"properties": {
|
|
16
25
|
"id": { "type": "string" },
|
|
17
|
-
"status": {
|
|
18
|
-
"type": "string",
|
|
19
|
-
"enum": ["closed", "not_closed", "needs_decision", "budget_exceeded"]
|
|
20
|
-
},
|
|
26
|
+
"status": { "type": "string", "enum": ["closed", "not_closed", "needs_decision", "budget_exceeded"] },
|
|
21
27
|
"change": { "type": "string" },
|
|
22
28
|
"before": { "type": "string" },
|
|
23
29
|
"after": { "type": "string" },
|
|
@@ -33,10 +39,7 @@
|
|
|
33
39
|
"items": {
|
|
34
40
|
"type": "object",
|
|
35
41
|
"required": ["note", "why_suspicious"],
|
|
36
|
-
"properties": {
|
|
37
|
-
"note": { "type": "string" },
|
|
38
|
-
"why_suspicious": { "type": "string" }
|
|
39
|
-
}
|
|
42
|
+
"properties": { "note": { "type": "string" }, "why_suspicious": { "type": "string" } }
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
}
|