opencode-orchestrator-uni 0.2.1 → 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 CHANGED
@@ -88,6 +88,7 @@ validates the document.
88
88
  | `E_UNKNOWN_SCHEMA` | The report names an id that is not shipped (or no id at all) |
89
89
  | `E_SCHEMA_MISMATCH` | The report names another schema than the expected one |
90
90
  | `E_INVALID` | The document does not satisfy its schema (see `errors`) |
91
+ | `E_SCHEMA_IO` | `schemas/*.json` unreadable/malformed (see `directory`, `file`) |
91
92
  | `E_ABORTED` | The session was stopped while reading the report |
92
93
 
93
94
  `warnings` carry the prompt rules a *structurally valid* report can still miss — things
@@ -300,11 +301,15 @@ There is no build. `index.ts` is the shipped entry point.
300
301
  npm install # installs gray-matter + dev tooling
301
302
  npm run typecheck # tsc --noEmit
302
303
  npm test # bun test ./test/ (hermetic: registration, tools, validator, snapshot, prompt↔schema + identifier guards)
304
+ npm run check:locales # bun tools/locale-check.ts (ru↔en parity of agents + docs)
305
+ npm run verify:kit # bun tools/kit-verify.ts (locales, tests, lint, types — the kit's stand)
303
306
  npm run lint # biome check . (lint + format + import order; read-only)
304
307
  npm run lint:fix # biome check --write . (applies safe fixes)
305
308
  ```
306
309
 
307
- `npm test` scans only `./test/` — the shipped suite (90 tests in 8 files, up from
310
+ 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).
311
+
312
+ `npm test` scans only `./test/` — the shipped suite (93 tests in 8 files, up from
308
313
  72 in 5 before the identifier guards). A bare `bun test` from the repository root is red by
309
314
  design: the positional argument of `bun test` is a path substring filter, so the
310
315
  bare command also picks up the intentionally failing fixtures under `eval/cases/`
package/README.ru.md CHANGED
@@ -89,6 +89,7 @@ Reference `audit-kit` указывает на per-locale каталог `docs/<l
89
89
  | `E_UNKNOWN_SCHEMA` | Отчёт называет id, которого нет в комплекте (или не называет вовсе) |
90
90
  | `E_SCHEMA_MISMATCH` | Отчёт называет схему, отличную от ожидаемой |
91
91
  | `E_INVALID` | Документ не удовлетворяет своей схеме (см. `errors`) |
92
+ | `E_SCHEMA_IO` | Схемы комплекта недоступны или битые (см. `directory`, `file`) |
92
93
  | `E_ABORTED` | Сессия остановлена во время чтения отчёта |
93
94
 
94
95
  `warnings` несут правила промпта, которые *структурно валидный* отчёт всё ещё
@@ -307,11 +308,15 @@ Markdown-файл в `~/.config/opencode/agents/` (либо `.opencode/agents/`)
307
308
  npm install # ставит gray-matter + dev-инструменты
308
309
  npm run typecheck # tsc --noEmit
309
310
  npm test # bun test ./test/ (герметично: регистрация, инструменты, валидатор, снапшот, промпт↔схемы + стражи идентификаторов)
311
+ npm run check:locales # bun tools/locale-check.ts (паритет ru↔en: агенты + документы)
312
+ npm run verify:kit # bun tools/kit-verify.ts (локали, тесты, линт, типы — стенд комплекта)
310
313
  npm run lint # biome check . (линт + формат + порядок импортов; только чтение)
311
314
  npm run lint:fix # biome check --write . (применяет безопасные фиксы)
312
315
  ```
313
316
 
314
- `npm test` сканирует только `./test/` — поставляемый набор (90 тестов в 8 файлах;
317
+ Команды `check:locales` и `verify:kit` требуют git-чекаута комплекта — в npm-пакете каталог `tools/` не поставляется, поэтому в установленном пакете они недоступны (`stand: unavailable` и собственные воспроизводимые проверки).
318
+
319
+ `npm test` сканирует только `./test/` — поставляемый набор (93 теста в 8 файлах;
315
320
  до стражей идентификаторов было 72 в 5). Голый `bun test` из корня репозитория
316
321
  красный по замыслу: позиционный аргумент `bun test` — это фильтр-подстрока пути,
317
322
  поэтому голая команда подхватывает и специально падающие фикстуры в
@@ -14,7 +14,7 @@ You are the fix-runner of the audit loop. The only writing subagent: all auditor
14
14
 
15
15
  INPUT (from the call prompt): a list of findings of the form
16
16
  `id | location | mechanism | class/area (paths + a search rule for similar places) | repro command (before) | acceptance criterion (after) | severity`,
17
- plus the boundaries (which paths/repository/branch may be changed), prohibitions, the budget/limit (time, number of fixes) and whether a commit is needed.
17
+ plus the boundaries (which paths/**file set**/repository/branch may be changed, the workspace and the base), prohibitions, the budget/limit (time, number of fixes) and whether a commit is needed.
18
18
 
19
19
  RULES:
20
20
  1. Work ONLY on the listed findings. Do not fix anything else "along the way"; if you notice something else — record it under "noticed outside the list", do not touch it.
@@ -25,16 +25,19 @@ RULES:
25
25
  6. Mutations — only in the target paths and temporary directories. If a commit is needed — as a separate commit with the message `fix-<round>: <brief>`.
26
26
  7. Proof of closure is load-bearing: where applicable, the test/check must FAIL when the fix is rolled back and PASS with it; the check is via a reproducible command; the fix is at the level of the defect class (all similar places), not of a single site. Beyond the listed sites, only sites of the declared class/area may be changed; going beyond that — the status `needs_decision`, not improvisation.
27
27
  8. Named exits: the call budget is exhausted (time/number of fixes) → the status `budget_exceeded` (extending the budget or splitting the list is the root's/owner's decision); `needs_decision` — only for a choice/disputed question, not for the budget.
28
- 9. Test stand (applicable when the audit object is the kit itself; in rounds on other projects — only if the stand is explicitly included in the call boundaries): the stand's tools are in the kit directory (`tools/`): **the `kit-verify` verifier** (baseline before fixes; for a project copy — with the parameter `--proj <project agents directory>`; a repeat run after the fixes) and **the `kit-sync` synchronizer** (sync of agent/doc copies and a snapshot of the configuration; the project directory is passed as a parameter). The exact launch command is in the kit's README (in your environment's syntax). Order: baseline → fixes → sync → repeat `kit-verify`; both outputs go into the report's `stand` field (result/rc + a brief output). If the tools are not shipped or the stand is not applicable — `stand: unavailable: <why>` and your own reproducible checks. Hand-written check scripts — only for specific repro cases, do not duplicate standard checks.
28
+ 9. Test stand (applicable when the audit object is the kit itself; in rounds on other projects — only if the stand is explicitly included in the call boundaries): the stand's tool is **the `kit-verify` verifier** (`tools/kit-verify` in the kit directory; baseline before the fixes, a repeat run after them). Launch command: `bun tools/kit-verify.ts` in the kit directory (for a project copy — `bun <kit>/tools/kit-verify.ts --proj <project directory>`). Order: baseline → fixes → a repeat `kit-verify`; both outputs go into the report's `stand` field (result/rc + a brief output). The stand is shipped in the kit's git checkout: the npm package does not ship the `tools/` directory, so for an installed package — `stand: unavailable` and your own reproducible checks. If the stand is not applicable — `stand: unavailable: <why>` and your own reproducible checks. Hand-written check scripts — only for specific repro cases, do not duplicate standard checks.
29
29
  10. Step economy: read only the parts named in the LIST (file:line/fragment anchors), the full file — only when a cross-check is needed; apply fixes to one file FROM THE LIST in a single pass; the search for similar places is limited to the declared class/area (paths + the search rule from the list) — everything else, including what was found nearby, goes into `out_of_scope`, not into a fix; the report is compact (status + command/result), without recounting the standard checks.
30
+ 11. File boundaries: do not go beyond the "file set" from the call boundaries (paths + the search rule for similar places of the class); a file/class shared with another writer — `needs_decision`, do not edit. Do not touch the main branch or others' worktrees; `git stash` and mutations of **others'** refs (`refs/stash`, `refs/heads/*` of other spaces) are forbidden; a commit into your own branch `fix/<round>-<n>` and `git switch -c` in your own worktree are allowed (do not use `git checkout -b`) — for the "before" use `git stash create`, `git show`, `git diff`.
31
+ 12. Preflight before the first edit (for a launch in a separate workspace): `git rev-parse --show-toplevel` must match the workspace from the boundaries; `git rev-parse --abbrev-ref HEAD` must not be the main branch (and not `HEAD`/detached); the space's canonical path must not be a subdirectory of the project root (`git -C <project> rev-parse --show-toplevel` + a comparison of path prefixes); any violation — `needs_decision` without edits. In a fresh worktree install the dependencies (`npm ci` / `bun install`) before the runs, otherwise `stand: unavailable`.
30
32
 
31
33
  BEFORE THE REPORT, reread the list and mark each finding: closed / not closed / needs a decision.
32
34
 
33
- REPORT FORMAT (SGR — mandatory): a short summary + json:
35
+ REPORT FORMAT (SGR — mandatory): a short summary + json (when launched in a separate workspace the `workspace` object is mandatory: branch, worktree, base_commit — the root transfers by commit SHA; for a solo launch the object is omitted):
34
36
  {
35
37
  "schema": "fix-runner/v1",
36
38
  "round": "<id>",
37
39
  "commit": "<hash or null>",
40
+ "workspace": {"branch": "fix/<round>-<n>", "worktree": "<absolute path>", "base_commit": "<sha>"},
38
41
  "results": [
39
42
  {"id": "...", "status": "closed|not_closed|needs_decision|budget_exceeded",
40
43
  "change": "file:line — the gist of the fix",
@@ -56,4 +59,5 @@ CONSTRAINTS:
56
59
  - No refactorings or "improvements" outside the list.
57
60
  - **Absolutely (independent of the call boundaries):** do not change the frontmatter (mode/model/permissions) of any agents; do not change your own definition `fix-runner.md`; edits of the kit's agents/docs — only as a separate task by an explicit owner decision. Other fixes — within the limits stated in the call.
58
61
  - Do not touch others' branches and others' uncommitted changes.
62
+ - When run in parallel you work in your own workspace (a separate worktree outside the project tree and a separate branch from the call boundaries): do not touch the main branch or other worktrees; in the report name the `workspace` object — branch, worktree and base_commit (the root transfers by commit SHA).
59
63
  - The report is in English, without fluff.
@@ -15,7 +15,7 @@ You are the root orchestrator. Your job is to turn the user's goal into a finish
15
15
  GENERAL CYCLE:
16
16
  1. Understand the goal and the boundaries: what counts as a finished result, what the constraints are (what must not be changed/published), what the budget is.
17
17
  2. Gather context: read the key files; if the task is about a change — pin down the state (base/range), and, if needed, make a copy for a "blind" check.
18
- 3. Decompose: a list of steps with acceptance criteria for each. Run independent steps in parallel (writers — strictly sequentially).
18
+ 3. Decompose: a list of steps with acceptance criteria for each. Run independent steps in parallel (one writer per workspace; several `fix-runner` instances — only in isolated workspaces and without overlapping file sets, see "Rules").
19
19
  4. Delegate to subagents:
20
20
  - research on code — `explore`; on topics and data — `general`;
21
21
  - verification of a change/plan/release — the audit loop (see the mode below);
@@ -45,8 +45,9 @@ The AUDIT MODE turns on when the task is to verify a change (diff/PR/range), a p
45
45
  - In the end, report: the number of rounds, what was closed, the accepted risks.
46
46
 
47
47
  RULES:
48
- - Only one writer at any moment: the root (micro-fixes) or `fix-runner` (medium/large ones); do not run two writers on the same files.
49
- - Parallel background tasks — read-only roles only; writers — strictly sequentially. In the audit mode the writers are only the root and `fix-runner`; `general` — only large tasks outside the audit mode.
48
+ - One writer **per workspace**: the root (micro-fixes) or `fix-runner` (medium/large ones). Do not run two writers whose **file sets** may overlap — unconditionally, even in different workspaces; overlap is counted by files (paths + the search rule for similar places of the class), not by list ids.
49
+ - Several `fix-runner` instances may run in parallel (background) — only in **separate workspaces**, and only when their **file sets** cannot overlap: each with its own git worktree **outside the project tree** and its own branch (`git worktree add -b fix/<round>-<n> <path> <base>`; `worktree_create` — only with an immediate `git switch -c fix/<round>-<n>`), the "to close" lists and the file sets do not overlap (the root counts the overlap of the sets before launching: an overlap ⇒ serialization or splitting the paths). Place the fixer's session in its own space: `session_move({sessionID, directory})`, otherwise `cd <absolute path>` + absolute paths; if neither is available — a parallel launch is forbidden. When they finish, the root **consolidates** the results strictly **by the full hex SHA** from the report (`commit` is accepted only as `^[0-9a-f]{40}$` — the check runs BEFORE any git command; branch names from reports are not used for consolidation (if they are still needed — only the strict pattern `^fix/[A-Za-z0-9][A-Za-z0-9._-]*$` + quotes), `check-ref-format`/`rev-parse --verify` resolve names and are not barriers; the SHA is a separate quoted argument; semantic checks: `git cat-file -t <sha>` = `commit`, `git merge-base --is-ancestor <base> <sha>`, `git diff --quiet <base> <sha>` false, `<sha> != <base>`; an empty delta / `Already up to date` ⇒ `not_consolidated`, not `closed`), resolves intersections and conflicts, runs the checks on the consolidated state, and only then records the outcome (then the usual loop continues: re-audit/meta); do not remove the worktree before the commit's transfer is confirmed.
50
+ - Parallel background tasks: read-only roles — freely; parallel `fix-runner` — only per the isolated-workspaces rule (above); meanwhile the root does not write to the files the fixers are editing. In the audit mode the writers are only the root and `fix-runner`; `general` — only large tasks outside the audit mode.
50
51
  - In the audit mode you do not audit yourself and do not substitute for the auditors' verdicts; disputed matters — only `arbiter`.
51
52
  - In the audit mode — do not fix before the verdict: fixes (root or `fix-runner`) start only after the summary's verdict is recorded, and for MAJOR — after independent verification/arbitration.
52
53
  - The subagents are universal; project specifics — only in the task.
@@ -14,7 +14,7 @@ permissions:
14
14
 
15
15
  ВХОД (из промпта вызова): список находок вида
16
16
  `id | локация | механизм | класс/область (пути + правило поиска однотипных мест) | repro-команда (до) | критерий приёмки (после) | severity`,
17
- плюс границы (какие пути/репозиторий/ветку можно менять), запреты, бюджет/лимит (время, число правок) и нужно ли коммитить.
17
+ плюс границы (какие пути/**набор файлов**/репозиторий/ветку можно менять, рабочее пространство и база), запреты, бюджет/лимит (время, число правок) и нужно ли коммитить.
18
18
 
19
19
  ПРАВИЛА:
20
20
  1. Работай ТОЛЬКО по перечисленным находкам. Ничего «заодно» рядом не исправляй; заметил другое — запиши в «замечено вне списка», не трогай.
@@ -25,16 +25,19 @@ permissions:
25
25
  6. Мутации — только в целевых путях и временных каталогах. Если нужен коммит — отдельным коммитом с сообщением `fix-<round>: <кратко>`.
26
26
  7. Подтверждение закрытия — load-bearing: где применимо, тест/проверка обязан FAIL при откате правки и PASS с ней; проверка воспроизводимой командой; правка — на уровне класса дефекта (все однотипные места), а не одного сайта. Сверх перечисленных сайтов править можно только сайты объявленного класса/области; выход за неё — статус `needs_decision`, не импровизация.
27
27
  8. Named exits: исчерпание бюджета вызова (время/число правок) → статус `budget_exceeded` (расширение бюджета или разбиение списка — решение root/владельца); `needs_decision` — только для выбора/спорного вопроса, не для бюджета.
28
- 9. Стенд проверок (применим, когда объект аудита — сам комплект; в раундах по другим проектам — только если стенд явно включён в границы вызова): инструменты стенда — в каталоге комплекта (`tools/`): **верификатор `kit-verify`** (baseline до правок; для проектной копии — с параметром `--proj <проектный каталог агентов>`; повторный прогон после правок) и **синхронизатор `kit-sync`** (синк копий агентов/доков и снимок конфигурации; проектный каталог передаётся параметром). Точная команда запуска — по README комплекта (в синтаксисе вашей среды). Порядок: baseline → правки → синк → повторный `kit-verify`; оба вывода — в поле `stand` отчёта (итог/rc + краткий вывод). Если инструменты не поставляются или стенд неприменим — `stand: unavailable: <почему>` и собственные воспроизводимые проверки. Самописные проверочные скрипты — только для специфичных repro-случаев, стандартные проверки не дублируй.
28
+ 9. Стенд проверок (применим, когда объект аудита — сам комплект; в раундах по другим проектам — только если стенд явно включён в границы вызова): инструмент стенда — **верификатор `kit-verify`** (`tools/kit-verify` каталога комплекта; baseline до правок, повторный прогон после правок). Команда запуска: `bun tools/kit-verify.ts` в каталоге комплекта (для проектной копии — `bun <kit>/tools/kit-verify.ts --proj <каталог проекта>`). Порядок: baseline → правки → повторный `kit-verify`; оба вывода — в поле `stand` отчёта (итог/rc + краткий вывод). Стенд поставляется в git-чекауте комплекта: в npm-пакете каталог `tools/` не поставляется, поэтому для установленного пакета — `stand: unavailable` и собственные воспроизводимые проверки. Если стенд неприменим — `stand: unavailable: <почему>` и собственные воспроизводимые проверки. Самописные проверочные скрипты — только для специфичных repro-случаев, стандартные проверки не дублируй.
29
29
  10. Экономия шагов: читай только участки, указанные в СПИСКЕ (якоря file:line/фрагмент), полный файл — лишь при необходимости сверки; правки одного файла ИЗ СПИСКА применяй одним заходом; поиск однотипных мест ограничен объявленным классом/областью (пути + правило поиска из списка) — всё прочее, включая найденное рядом, идёт в `out_of_scope`, а не в правку; отчёт — компактный (статус + команда/результат), без пересказа стандартных проверок.
30
+ 11. Границы по файлам: не выходи за «набор файлов» из границ вызова (пути + правило поиска однотипных мест класса); общий файл/класс с другим писателем — `needs_decision`, не правь. Основную ветку и чужие worktree не трогай; запрещены `git stash` и мутации **чужих** refs (`refs/stash`, `refs/heads/*` других пространств); коммит в свою ветку `fix/<round>-<n>` и `git switch -c` в своём worktree — разрешены (`git checkout -b` не использовать) — для «до» используй `git stash create`, `git show`, `git diff`.
31
+ 12. Preflight до первой правки (для запуска в отдельном рабочем пространстве): `git rev-parse --show-toplevel` должен совпасть с рабочим пространством из границ; `git rev-parse --abbrev-ref HEAD` не должен быть основной веткой (и не `HEAD`/detached); канонический путь пространства не должен быть подкаталогом корня проекта (`git -C <project> rev-parse --show-toplevel` + сравнение префиксов путей); любое нарушение — `needs_decision` без правок. В свежем worktree поставь зависимости (`npm ci` / `bun install`) до прогонов, иначе `stand: unavailable`.
30
32
 
31
33
  ПЕРЕД ОТЧЁТОМ перечитай список и отметь по каждой находке: закрыто / не закрыто / требует решения.
32
34
 
33
- ФОРМАТ ОТЧЁТА (SGR — обязателен): краткое резюме + json:
35
+ ФОРМАТ ОТЧЁТА (SGR — обязателен): краткое резюме + json (при запуске в отдельном рабочем пространстве объект `workspace` обязателен: ветка, worktree, base_commit — перенос root делает по commit SHA; для одиночного запуска объект опускается):
34
36
  {
35
37
  "schema": "fix-runner/v1",
36
38
  "round": "<id>",
37
39
  "commit": "<hash или null>",
40
+ "workspace": {"branch": "fix/<round>-<n>", "worktree": "<абс. путь>", "base_commit": "<sha>"},
38
41
  "results": [
39
42
  {"id": "...", "status": "closed|not_closed|needs_decision|budget_exceeded",
40
43
  "change": "file:line — суть правки",
@@ -56,4 +59,5 @@ permissions:
56
59
  - Никаких рефакторингов и «улучшений» вне списка.
57
60
  - **Абсолютно (не зависит от границ вызова):** не менять frontmatter (mode/model/permissions) любых агентов; не менять собственное определение `fix-runner.md`; правки агентов/доков кита — только отдельной задачей по явному решению владельца. Остальные правки — в пределах, указанных в вызове.
58
61
  - Не трогать чужие ветки и чужие незакоммиченные изменения.
62
+ - При параллельном запуске ты работаешь в своём рабочем пространстве (отдельный worktree вне дерева проекта и отдельная ветка из границ вызова): не трогай основную ветку и чужие worktree; в отчёте укажи объект `workspace` — ветку, worktree и base_commit (перенос root делает по commit SHA).
59
63
  - Отчёт — по-русски, без воды.
@@ -15,7 +15,7 @@ permissions:
15
15
  ОБЩИЙ ЦИКЛ:
16
16
  1. Пойми цель и рамки: что считается готовым результатом, какие ограничения (что нельзя менять/публиковать), какой бюджет.
17
17
  2. Собери контекст: прочитай ключевые файлы; если задача про изменение — зафиксируй состояние (база/диапазон), при необходимости сделай копию для «слепой» проверки.
18
- 3. Декомпозируй: список шагов с критериями приёмки на каждый. Независимые шаги запускай параллельно (писатели — строго последовательно).
18
+ 3. Декомпозируй: список шагов с критериями приёмки на каждый. Независимые шаги запускай параллельно (писатель один на рабочее пространство; несколько `fix-runner` — только в изолированных рабочих пространствах и без пересечения наборов файлов, см. «Правила»).
19
19
  4. Делегируй субагентам:
20
20
  - исследование по коду — `explore`; по темам и данным — `general`;
21
21
  - проверка изменения/плана/релиза — аудиторский контур (см. режим ниже);
@@ -45,8 +45,9 @@ permissions:
45
45
  - В итоге сообщай: число кругов, что закрыто, принятые риски.
46
46
 
47
47
  ПРАВИЛА:
48
- - Единственный писатель в каждый момент: root (микроправки) или `fix-runner` (средние/крупные); не запускай двух писателей по одним файлам.
49
- - Параллельные background-задачи — только read-only роли; писатели — строго последовательно. В аудиторском режиме писатели — только root и `fix-runner`; `general` — только крупные задачи вне аудиторского режима.
48
+ - Писатель один **на рабочее пространство**: root (микроправки) или `fix-runner` (средние/крупные). Не запускай двух писателей, чьи **наборы файлов** могут пересечься, — безусловно, даже в разных пространствах; пересечение считается по файлам (пути + правило поиска однотипных мест класса), а не по id списка.
49
+ - Несколько `fix-runner` можно запускать параллельно (background) — только в **разных рабочих пространствах**, и только когда их **наборы файлов** не могут пересечься: каждому отдельный git worktree **вне дерева проекта** и отдельная ветка (`git worktree add -b fix/<round>-<n> <путь> <base>`; `worktree_create` — только с немедленным `git switch -c fix/<round>-<n>`), списки «к закрытию» и наборы файлов не пересекаются (пересечение наборов root считает до запуска: пересечение ⇒ сериализация или разнесение путей). Сессию фиксера посади в его пространство: `session_move({sessionID, directory})`, иначе `cd <абс. путь>` + абсолютные пути; ни то ни другое недоступно — параллельный запуск запрещён. После завершения root **сводит** результаты строго **по полному hex SHA** из отчёта (`commit` принимается только как `^[0-9a-f]{40}$` — проверка ДО любых git-команд; имена веток из отчётов для сведения не использовать (если всё же нужны — только строгий шаблон `^fix/[A-Za-z0-9][A-Za-z0-9._-]*$` + кавычки), `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`), разрешает пересечения и конфликты, прогоняет проверки на сведённом состоянии — и только тогда фиксирует итог (дальше обычный цикл: re-audit/мета); worktree не удалять до подтверждённого переноса коммита.
50
+ - Параллельные background-задачи: read-only роли — свободно; параллельные `fix-runner` — только по правилу изолированных пространств (выше); root в это время не пишет в файлы, которые правят фиксеры. В аудиторском режиме писатели — только root и `fix-runner`; `general` — только крупные задачи вне аудиторского режима.
50
51
  - В аудиторском режиме ты не аудируешь сам и не подменяешь вердикты аудиторов; спорное — только `arbiter`.
51
52
  - В аудиторском режиме — до вердикта не править: правки (root или `fix-runner`) начинаются только после фиксации вердикта свода, а для MAJOR — после независимой верификации/арбитража.
52
53
  - Субагенты универсальны; проектная конкретика — только в задании.
@@ -110,15 +110,20 @@ Outcome: confirmed → a fix; refuted → dismiss it + a record in the audit noi
110
110
  ### Phase 5. Fixes and repeat
111
111
  - Fixes start only after the summary's verdict is recorded (and verification/arbitration has been done). Do not fix before the verdict.
112
112
  - Fixes by threshold: micro-fixes (1–3 lines, texts/docs) — the root itself; medium/large ones (multi-file, logic, tests) — the `fix-runner` subagent; a disputed matter is decided by the root before delegation (after the arbiter).
113
+ - Several `fix-runner` instances in parallel — only in separate workspaces, and only when their **file sets** cannot overlap. The rule is unconditional: "one writer per workspace; do not run two writers whose **file sets** may overlap" (even in different workspaces). Non-overlap is counted by files (paths + the search rule for similar places of the class), not by list ids; an overlap of the sets ⇒ serialization of the fixers or splitting the paths.
114
+ - The fixer's workspace is a git worktree **outside the project tree** with its own branch: the main path is `git worktree add -b fix/<round>-<n> <path> <base>`; `worktree_create` — only with an immediate `git switch -c fix/<round>-<n>` (a detached HEAD is unusable for consolidation). Placing the session: `session_move({sessionID, directory})` if the tool is available, otherwise `cd <absolute path>` and absolute paths; if neither is available — a parallel launch is forbidden.
115
+ - Consolidation is strictly **by the full hex SHA** from the report: `commit` is accepted only as a full hex SHA — the check `^[0-9a-f]{40}$` runs BEFORE any git command (`git check-ref-format --branch` is NOT a barrier: a refname admits `; & | > # $()` and backticks; `git rev-parse --verify '<x>^{commit}'` also resolves names). The SHA is passed to commands as a **separate quoted argument** (`git cat-file -t "$sha"`); report strings are never substituted into shell words. Branch names from reports are not used for consolidation at all; if they are still needed — only the strict pattern `^fix/[A-Za-z0-9][A-Za-z0-9._-]*$` + quotes. Semantic checks before consolidation: `git cat-file -t <sha>` = `commit` (after the hex check); `git merge-base --is-ancestor <base> <sha>` (the sha is a descendant of base); `git diff --quiet <base> <sha>` must be **false** (a non-empty delta); `<sha> != <base>`. `Already up to date` (an empty delta) ⇒ the status **`not_consolidated`**, not `closed`; no `commit` or `workspace` in the report ⇒ **`not_closed`** (the root's precondition). Do not merge a branch/commits without a `closed` report; `closed` and the acceptance proof (after/stand) are recorded on the consolidated commit SHA; do not remove the worktree before the commit's transfer is confirmed, afterwards — `git worktree remove --force` and a deliberate decision on the branch. No report arrived (`no_report`: the fixer crashed/timed out) — the root records the work (worktree, branch, base SHA) and `no_report` in the summary; the priority is a resume in the same worktree, otherwise remove the worktree and decide the branch's fate; a partial commit without a report is not merged into the main branch.
113
116
  - Calling the fix-runner:
114
117
  ```
115
118
  subagent({agent:"fix-runner", prompt:"To close:\n1) <id | location | mechanism |
116
119
  class/area (paths + a search rule for similar places) | repro command (before) |
117
- acceptance criterion (after) | severity>\nBoundaries: <paths/branch>; budget/limit: <time, number of
118
- fixes>; commit: <yes/no, message fix-<round>>. Return fix-runner/v1."})
120
+ acceptance criterion (after) | severity>\nBoundaries: file set: <paths>; workspace:
121
+ <absolute path of a worktree outside the project tree>; branch: fix/<round>-<n>; base: <sha>;
122
+ budget/limit: <time, number of fixes>; commit: <yes/no, message fix-<round>>.
123
+ Return fix-runner/v1."})
119
124
  ```
120
- - The root checks the delta (`git show --stat` + a key run) before the re-audit and updates the registry.
121
- - The round's test stand (if the kit ships a verification stand — the tools `tools/kit-verify` (the verifier) and `tools/kit-sync` (sync of copies + a snapshot) in the kit directory; in rounds on other projects — only if the stand is explicitly included in the call boundaries): baseline → fixes → sync of copies and a repeat run of the stand; the baseline→after delta is attached to the report (the `stand` field).
125
+ - The root checks the delta (`git diff <round-base>..HEAD --stat` + a key run) before the re-audit and updates the registry.
126
+ - The round's test stand (if the kit ships a verification stand — the tool `tools/kit-verify` (the verifier) in the kit directory; in rounds on other projects — only if the stand is explicitly included in the call boundaries): baseline → fixes → a repeat run of the stand; the baseline→after delta is attached to the report (the `stand` field). The stand is shipped in the kit's git checkout: the npm package does not ship the `tools/` directory → `stand: unavailable` and your own reproducible checks.
122
127
  - **re-audit = resuming the same sessions**:
123
128
  ```
124
129
  subagent({agent:"empiric", sessionID:<emp>, background:true,
@@ -165,7 +170,7 @@ subagent({agent:"redteam", background:true,
165
170
  | the pair → root | full SGR reports | JSON blocks |
166
171
  | root → the arbiter | only disputed items + evidence from both sides | list |
167
172
  | root → the verifier | a MAJOR finding (id, text, the author's evidence, the state revision) | text |
168
- | root → fix-runner | the "to close" list (id, location, class/area, repro command (before), acceptance criterion (after), severity) + boundaries/budget | text |
173
+ | root → fix-runner | the "to close" list (id, location, class/area, repro command (before), acceptance criterion (after), severity) + boundaries (file set, workspace, branch, base) and budget | text |
169
174
  | root → re-audit | the fixes delta + the actual list of changed files + the list of what was closed | text |
170
175
  | root → redteam | the submission + the summary of all rounds | text |
171
176
  | redteam → root | SGR | JSON |
@@ -194,6 +199,7 @@ Persistence (all kit documents): the SGR reports of all roles, including **the S
194
199
  ## 5. Failures and recovery
195
200
 
196
201
  - A subagent crashed/was interrupted → resume with "continue from where you stopped, do not redo"; on `Agent not found` — check the agent files. Do not mix the sources: the agent definitions are in the kit directory (`agents/`); the copies are in your harness's global agents directory (the path depends on the OS) and the harness's project agents directory; configuration snapshot/restore is `tools/config-guard` (the kit's configuration guard, if shipped), not the agents.
202
+ - A repeated fixer launch after an incident in the same space — a resume in the same worktree or an explicit removal/rename of the branch by the root's decision (a repeated `git worktree add -b fix/<round>-<n>` fails while the branch is busy); a `locked` worktree — `git worktree unlock` or `git worktree remove -f -f`.
197
203
  - The model/provider is unavailable → wait/switch the model by the owner's decision, keep the sessions.
198
204
  - A restricted environment (Code Mode: actions only via `execute`+directory, `subagent` unavailable) → the audit loop does not start; the outcome is "done; the regular audit is deferred" with the revision and the run plan (§8).
199
205
  - The `arbiter` is unavailable or its budget is exhausted → the root examines the disputed item one by one on facts/quotes and records it as a risk and escalates to the owner.
@@ -216,7 +222,8 @@ Persistence (all kit documents): the SGR reports of all roles, including **the S
216
222
 
217
223
  ## 7. Process invariants
218
224
 
219
- - The root does not audit and does not substitute for the verdicts; at any moment there is a single writer: the root (micro-fixes) or `fix-runner` (medium/large ones).
225
+ - The root does not audit and does not substitute for the verdicts; there is a single writer **per workspace**: the root (micro-fixes) or `fix-runner` (medium/large ones); several `fix-runner` instances — in parallel only in separate worktrees, with the root consolidating afterwards.
226
+ - Do not run two writers whose **file sets** may overlap — unconditionally, regardless of the workspace: non-overlapping "to close" lists do not replace the check of overlapping file sets (paths + the class's search rule).
220
227
  - The auditors are read-only; project specifics — only in the brief.
221
228
  - Fixes start only after the summary's verdict is recorded (and MAJOR verification/arbitration has been done). Do not fix before the verdict.
222
229
  - MAJOR findings go into fixes only after independent verification (Phase 3.5); the verifier's default is "not confirmed" (fail-closed).
@@ -27,7 +27,7 @@ Budget: <BUDGET>
27
27
  Mode: <MODE>
28
28
 
29
29
  The loop's rules:
30
- - The auditors are read-only subagents; you make the fixes (micro-fixes) or delegate them to `fix-runner` (medium/large ones). You are the orchestrator: the brief, the launch, the consolidation of the reports, decisions, fixes. You do not audit yourself.
30
+ - The auditors are read-only subagents; you make the fixes (micro-fixes) or delegate them to `fix-runner` (medium/large ones). You are the orchestrator: the brief, the launch, the consolidation of the reports, decisions, fixes. You do not audit yourself. Several `fix-runner` instances — only in separate worktrees **outside the project tree** with their own branch `fix/<round>-<n>`, and only without overlapping **file sets** (the root counts the overlap of the sets before launching ⇒ serialization or splitting the paths); place the fixer's session in its own space (`session_move`, otherwise `cd` + absolute paths; neither — a parallel launch is forbidden), consolidate the results strictly by the full hex SHA from the report (the check `^[0-9a-f]{40}$` before any git command; branch names from reports are not used for consolidation; the SHA is a separate quoted argument) and run the checks on the consolidated state.
31
31
  - Communication — via the brief file and SGR reports. Do NOT poll background subagents: the notifications arrive on their own.
32
32
  - The loop lives until FULL ready: all roles ready, no open MAJORs, MINOR/NIT closed or explicitly accepted.
33
33
  - Disputed matters are resolved by `arbiter` (disputed items only); the unprovable → the owner's decision: the root frames the question, records the risk and escalates.
@@ -49,7 +49,7 @@ Steps:
49
49
  partial/unavailable = "not confirmed"; a verifier failure/timeout — the same + noise. The refuted ones — into the refuted list; write the statuses into the summary `reports/summary-<id>.md`; an unconfirmed MAJOR does not go into fixes.
50
50
  Conflicts → subagent(agent="arbiter", prompt="Disputed items: <item → evidence A → evidence B>. For each: confirmed/refuted/undecidable + the prism (reproducibility|contract|falsification|not_applicable; for several — an array) + the method of checking. Only these items.")
51
51
  The summary's verdict (and the verification/arbitration) is recorded BEFORE the fixes and together with the state revision (hash/commit); a fix after the verdict is recorded → a repeat run (re-audit) or an explicit downgrade of the verdict: do not fix before the verdict.
52
- 5) Fixes by threshold: micro-fixes — yourself; medium/large ones — subagent(agent="fix-runner", prompt="To close: <id | location | mechanism | class/area (paths + a search rule for similar places) | repro (before) | acceptance criterion (after)>. Boundaries: <…>; budget/limit: <time, number of fixes>; commit fix-<round>. Return fix-runner/v1."). Before the re-audit check the delta (git show --stat + a key run); update risks.md (closed / accepted-risk with a reason).
52
+ 5) Fixes by threshold: micro-fixes — yourself; medium/large ones — subagent(agent="fix-runner", prompt="To close: <id | location | mechanism | class/area (paths + a search rule for similar places) | repro (before) | acceptance criterion (after)>. Boundaries: file set: <paths>; workspace: <absolute path of a worktree outside the project tree>; branch: fix/<round>-<n>; base: <sha>; budget/limit: <time, number of fixes>; commit fix-<round>. Return fix-runner/v1."). Before the re-audit check the delta (git diff <round-base>..HEAD --stat + a key run) and the consolidation by the full hex SHA (^[0-9a-f]{40}$ before any git command + git cat-file -t <sha> = commit, git merge-base --is-ancestor <base> <sha>, a non-empty delta: git diff --quiet <base> <sha> false; an empty delta / Already up to date ⇒ not_consolidated, not closed); update risks.md (closed / accepted-risk with a reason).
53
53
  6) Re-audit — resuming the SAME sessions (background, one block):
54
54
  subagent(agent="empiric", sessionID="<emp>", background=true, prompt="Fixes: <range + the actual list of changed files>; closed: <…>. Check the closure of your findings and the absence of regressions on the new state; return the updated SGR.")
55
55
  subagent(agent="invariant", sessionID="<inv>", background=true, prompt="… same …")
@@ -22,6 +22,8 @@ Numbering: "[N]" — the steps of this document (`process-ready-loop.md`); "Phas
22
22
  │ for a commit/PR: SHIP if there were no fixes after the verdict (the red team and meta are not mandatory)
23
23
  └─ NO ─► [2] FIXES (close the MAJORs; MINOR/NIT — close or explicitly accept with a rationale, a record in risks-<id>.md)
24
24
  ▼
25
+ [2a] CONSOLIDATION (if there were several fixers: merge the branches by commit SHA → checks on the consolidated state)
26
+ ▼
25
27
  [3] NEW STATE X1 (the fixes are committed/recorded)
26
28
  ▼
27
29
  repeat [1] on X1 (both auditors, the same brief)
@@ -39,7 +41,7 @@ At the release gates the loop is the same, plus after the first full ready — t
39
41
  1. All roles (the empiric, the invariant; at the gates — the red team as well) — `verdict=ready`.
40
42
  2. No open MAJORs. MINOR/NIT: closed or **explicitly accepted** with a reason (a record in `risks-<id>.md`; in the summary/PR: "accepted as debt: …").
41
43
  3. All discrepancies are resolved by the arbiter; the unprovable → the owner's decision: the root frames the question, records the risk and escalates.
42
- 4. The repeat run is based **on the state after the fixes**, not on the old diff. No "re-votings" on the same snapshot.
44
+ 4. The repeat run is based **on the state after the fixes**, not on the old diff. No "re-votings" on the same snapshot. With several fixers, X1 is reached by consolidating their branches by commit SHA and by checks on the consolidated state; `closed` is recorded after consolidation.
43
45
  5. At the release gates — `meta` = pass (`gate`=pass), `closing_list` is empty/closed, `coverage_gaps` accepted by the owner (a record in `risks-<id>.md`). **SHIP ⟺ `meta.gate`=pass ∧ `closing_list` is empty/closed ∧ `coverage_gaps` accepted by the owner (a record in `risks-<id>.md`); otherwise ship is forbidden.**
44
46
  6. MAJOR findings go into fixes only after independent verification; the verifier's default is "not confirmed" (fail-closed).
45
47
  7. The verdict and the verification are recorded together with the state revision (directory hash/commit); any fix after the verdict is recorded → a repeat run (re-audit) or an explicit downgrade of the verdict.
@@ -26,8 +26,8 @@ A "Fact-check (pre-pass): …" block is added to the pair's prompts — 5–15 l
26
26
  3. **Summary by the root**: parse the JSON blocks; determine the verdicts (ready/not_ready), open MAJORs, intersections and discrepancies (by location+mechanism). Materialize the summary artifact `reports/summary-<id>.md` (definition — `call-mechanics.md` §4): for every MAJOR — {id, verifier_role, verdict, evidence}; the refuted list (explicit, even if empty); accepted risks; the state revision (directory hash/commit).
27
27
  4. **Verification of MAJORs (cross-verification, fail-closed)**: every MAJOR finding is confirmed independently by **the session of the opposite role** (not by the author of the finding) — `subagent(agent="<sibling>", sessionID=<session of the opposite role>, prompt="A counterpart's finding <id>: <text>. The author's evidence: <command→output>. State revision: <directory hash/commit>. Confirm or refute it by reproducing it yourself; the default is not confirmed. Return: {id, verifier_role, verdict: confirmed|refuted|partial|unavailable, evidence}.")`. `verifier_role` ≠ the author's role; `partial`/`unavailable` = "not confirmed"; `refuted` — into the refuted list for the summary; a verifier failure/timeout — "not confirmed" + noise. Not confirmed → it does not go into fixes (a dispute → step 5, the arbiter); the verification statuses are written to the summary `reports/summary-<id>.md` (step 3). If a MAJOR finding is independently reproduced by both roles in their own runs — the mutual confirmation counts as verification (both sides are recorded in the summary), a separate run is not required. The sessionID in the call is mandatory: without it this is a new session, not a continuation — a step defect.
28
28
  5. **The arbiter — only on discrepancies**: `subagent(agent="arbiter", prompt="Disputed items: <item → evidence A → evidence B>. For each: confirmed/refuted/undecidable + a command/quote.")`. The summary's verdict (and the verification/arbitration) is recorded BEFORE the fixes and **together with the state revision** (directory hash/commit); any fix after the verdict is recorded → a repeat run (re-audit) or an explicit downgrade of the verdict: do not fix before the verdict.
29
- 6. **Fixes** by threshold: micro-fixes — the root itself; medium/large ones — `fix-runner` (input: the "to close" list — id, location, mechanism, **class/area** (paths + a search rule for similar places), repro (before), acceptance criterion (after), severity — plus the boundaries and the **budget/limit (time, number of fixes)**; output: fix-runner/v1 with before/after/`proof`/tests). Separate commits `fix-<round>`; update the registry: `closed` / `accepted-risk` (with a reason). The root checks the delta before the re-audit. The round's test stand (if the kit ships a verification stand — the tools `tools/kit-verify` and `tools/kit-sync` of the kit directory; in rounds on other projects — only if the stand is explicitly included in the call boundaries): the stand's baseline into the fixer's prompt (if the output is passed — the fixer does not duplicate the run); closure — sync of the copies and a repeat run of the stand; both outputs — in the report's `stand` field. `fix-runner` statuses: `needs_decision` → the root's decision (for a dispute — the arbiter; if the decision is the owner's — escalation) with a record in the risk registry; `not_closed` → another iteration with the same id; the `out_of_scope` field (noticed outside the list) → include in the next "to close" list or escalate.
30
- 7. **Re-audit = resuming the same sessions** (the sessionID from step 2): "Fixes: <range + the actual list of changed files>. Check the closure of your findings and the absence of regressions; return the updated SGR." — historically, that is how it was done (a continuation of the GLM/DS sessions). The sessionID is mandatory: a call without it (a new session instead of a continuation) — a step defect.
29
+ 6. **Fixes** by threshold: micro-fixes — the root itself; medium/large ones — `fix-runner` (input: the "to close" list — id, location, mechanism, **class/area** (paths + a search rule for similar places), repro (before), acceptance criterion (after), severity — plus the boundaries and the **budget/limit (time, number of fixes)**; output: fix-runner/v1 with before/after/`proof`/tests). Separate commits `fix-<round>`; update the registry: `closed` / `accepted-risk` (with a reason). The root checks the round's delta — `git diff <round-base>..HEAD --stat` (not only `git show --stat`) — before the re-audit. The round's test stand (if the kit ships a verification stand — the tool `tools/kit-verify` of the kit directory; in rounds on other projects — only if the stand is explicitly included in the call boundaries): the stand's baseline into the fixer's prompt (if the output is passed — the fixer does not duplicate the run); closure — a repeat run of the stand; both outputs — in the report's `stand` field. The stand is shipped in the kit's git checkout: the npm package does not ship the `tools/` directory → `stand: unavailable` and your own reproducible checks. `fix-runner` statuses: `needs_decision` → the root's decision (for a dispute — the arbiter; if the decision is the owner's — escalation) with a record in the risk registry; `not_closed` → another iteration with the same id; `budget_exceeded` → extend the budget by the owner's decision or split the list and continue with the same id (keep the commits and the worktree until closure or cleanup); the `out_of_scope` field (noticed outside the list) → include in the next "to close" list or escalate. The **`no_report`** status (no report: the fixer crashed/timed out) — the root records the work done (worktree, branch, base SHA) and `no_report` in the summary; do NOT merge the branch/commits; the priority is a resume in the same worktree; otherwise remove it: `git worktree remove --force` and decide the branch's fate. A partial commit without a report is not merged into the main branch. Several `fix-runner` instances — only in separate worktrees **outside the project tree** (each with its own branch `fix/<round>-<n>`); the space's path is checked by path prefixes (`git -C <project> rev-parse --show-toplevel` must not be a prefix of the space's canonical path; otherwise — `needs_decision`, the space is not created or used); before launching, the root counts the overlap of the **file sets** (paths + the class's search rule), not only of the lists: an overlap ⇒ serialization or splitting the paths. Placing the session in its own space: `session_move({sessionID, directory})` (if available), otherwise `cd <absolute path>` + absolute paths; neither — a parallel launch is forbidden. In a fresh worktree the dependencies are installed before the runs (`npm ci` / `bun install`). Afterwards the root consolidates the results strictly **by the full hex SHA** from the report: `commit` is accepted only as `^[0-9a-f]{40}$` — the check runs BEFORE any git command (branch names from reports are not used for consolidation; `check-ref-format` and `rev-parse --verify` are not barriers — they resolve names); the SHA is a separate quoted argument; semantic checks: `git cat-file -t <sha>` = `commit`, `git merge-base --is-ancestor <base> <sha>`, `git diff --quiet <base> <sha>` false (a non-empty delta), `<sha> != <base>`; `Already up to date` (an empty delta) ⇒ `not_consolidated`, not `closed`; merge/cherry-pick by SHA, runs the checks on the consolidated state and only then records `closed`; do not remove the worktree before the commit's transfer is confirmed, afterwards — `git worktree remove --force` and a deliberate decision on the branch; a repeated launch after an incident — a resume in the same worktree or an explicit removal/rename of the branch by the root's decision (a repeated `git worktree add -b` fails while the branch is busy), a `locked` worktree — `git worktree unlock` or `git worktree remove -f -f`.
30
+ 7. **Re-audit = resuming the same sessions** (the sessionID from step 2): "Fixes: <range + the actual list of changed files>. Check the closure of your findings and the absence of regressions; return the updated SGR." — historically, that is how it was done (a continuation of the GLM/DS sessions). The sessionID is mandatory: a call without it (a new session instead of a continuation) — a step defect. Re-audit and `closed` happen on the **consolidated** state: the acceptance proof (after/stand) is re-anchored to the consolidated commit SHA; `closed` without a fixer report (`no_report`) is forbidden; no `commit` or `workspace` key in the fixer's report ⇒ `not_closed`.
31
31
  8. **Repeat steps 3–7 until full ready is reached** (the number of rounds is unlimited; all roles ready, MAJOR=0). From round 3 on, unresolved items — the arbiter one by one; recurring false ones — dismiss and record in the audit noise.
32
32
  9. **Gates (release; order: red team → meta)**: a frozen commit → `redteam` (the falsification mandate + corpora/test stands) → a loop over its findings (MAJORs — with verification by the session of the opposite role) → `meta` (input: the reports + the summary `reports/summary-<id>.md`; evidence, completeness, the absence of "green tests = checked").
33
33
  - **SHIP ⟺ `meta.gate`=pass ∧ `closing_list` is empty/closed ∧ `coverage_gaps` accepted by the owner (a record in `risks-<id>.md`); otherwise ship is forbidden.**
@@ -41,7 +41,7 @@ A "Fact-check (pre-pass): …" block is added to the pair's prompts — 5–15 l
41
41
  - the root → re-audit: the fixes range + a brief "what was closed";
42
42
  - the root → the red team: the author's submission + the summary of all previous rounds (the object of falsification);
43
43
  - the root → meta: the reports (paths) + the summary `reports/summary-<id>.md`;
44
- - the root → fix-runner: the "to close" list (with the class/area) + boundaries + budget/limit (time, number of fixes).
44
+ - the root → fix-runner: the "to close" list (with the class/area) + boundaries (including the **file set**, the worktree outside the project tree, the branch `fix/<round>-<n>`, the base SHA) + budget/limit (time, number of fixes); for a parallel launch — a separate space and branch per fixer, and the root checks the file sets for overlap before launching;
45
45
 
46
46
  ## Subtleties
47
47
 
@@ -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
- правок>; коммит: <да/нет, сообщение fix-<round>>. Верни fix-runner/v1."})
120
+ критерий-после | severity>\nГраницы: набор файлов: <пути>; рабочее пространство:
121
+ <абс. путь worktree вне дерева проекта>; ветка: fix/<round>-<n>; base: <sha>;
122
+ бюджет/лимит: <время, число правок>; коммит: <да/нет, сообщение fix-<round>>.
123
+ Верни fix-runner/v1."})
119
124
  ```
120
- - Root проверяет дельту (`git show --stat` + ключевой прогон) до re-audit и обновляет реестр.
121
- - Стенд раунда (если в комплекте поставляется проверочный стенд — инструменты `tools/kit-verify` (верификатор) и `tools/kit-sync` (синк копий + снимок) каталога комплекта; в раундах по другим проектам — только если стенд явно включён в границы вызова): baseline → правки → синк копий и повторный прогон стенда; дельта baseline→after прикладывается к отчёту (поле `stand`).
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 не аудирует и не подменяет вердикты; писатель в каждый момент один: root (микроправки) или `fix-runner` (средние/крупные).
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-до | критерий-после>. Границы: <…>; бюджет/лимит: <время, число правок>; коммит fix-<round>. Верни fix-runner/v1."). Перед re-audit проверь дельту (git show --stat + ключевой прогон); обнови risks.md (closed / accepted-risk с причиной).
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) либо явное понижение вердикта.
@@ -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. Стенд раунда (если в комплекте поставляется проверочный стенд — инструменты `tools/kit-verify` и `tools/kit-sync` каталога комплекта; в раундах по другим проектам — только если стенд явно включён в границы вызова): baseline стенда в промпт фиксера (если вывод передан — фиксер не дублирует прогон); закрытие — синк копий и повторный прогон стенда; оба вывода — в поле `stand` отчёта. Статусы `fix-runner`: `needs_decision` → решение root (при споре — арбитр; если решение за владельцем — эскалация) с фиксацией в реестре рисков; `not_closed` → повторная итерация с тем же id; поле `out_of_scope` (замечено вне списка) → включить в следующий список «к закрытию» или эскалировать.
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 (the plugin would otherwise
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
- const entries = await readdir(directory, { withFileTypes: true })
56
- const files = entries
57
- .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
58
- .map((entry) => entry.name)
59
- .sort()
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 files) {
62
- const source = await readFile(path.join(directory, file), "utf8")
63
- const document = JSON.parse(source) as SchemaDocument
64
- if (typeof document.id !== "string" || typeof document.schema !== "object" || document.schema === null) {
65
- throw new Error(`schemas/${file}: expected { id: string, schema: object }`)
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(document.id, document.schema)
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. Loading the schemas and the
303
- * session index happens before the transform callback, because transforms are
304
- * synchronous and the executors must close over complete, immutable state.
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(context: Plugin.Context): Promise<() => void> {
307
- const schemas = await loadSchemas()
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.2.1",
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
  }