devcontour-harness 0.1.0-alpha.11 → 0.1.0-alpha.13

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
@@ -5,16 +5,17 @@ Headless-движок исполнения инженерных поручени
5
5
  интеграция с принятой веткой через compare-and-swap и машинный результат с SHA
6
6
  и доказательствами.
7
7
 
8
- > Статус: `0.1.0-alpha.11` — предварительный выпуск (alpha). Договоры
8
+ > Статус: `0.1.0-alpha.13` — предварительный выпуск (alpha). Договоры
9
9
  > `v1alpha1` могут измениться между alpha-версиями; закрепляйте точную версию.
10
10
  > Файл → ревью плана → кандидат → интеграция → принятие → результат JSON, с
11
11
  > ограниченным восстановлением, повторным использованием кандидата, бюджетами,
12
12
  > потребителями, свойствами и корпусом, совместной проверкой репозиториев и
13
13
  > явным режимом одного вендора.
14
14
 
15
- Пакет включает встроенные профили стеков (`profiles/`), skill
16
- `skills/harness/SKILL.md` и шаблоны политики, графа и инструкции агентам
17
- (`templates/`).
15
+ Пакет включает встроенные профили стеков (`profiles/`), skills
16
+ `skills/harness/SKILL.md` (работа с поручениями) и
17
+ `skills/harness-setup/SKILL.md` (установка, первая настройка и обновление
18
+ версии), шаблоны политики, графа и инструкции агентам (`templates/`).
18
19
 
19
20
  ## Требования
20
21
 
@@ -27,7 +28,7 @@ Headless-движок исполнения инженерных поручени
27
28
  Из npm — точная версия (воспроизводимо) или последняя alpha по тегу:
28
29
 
29
30
  ```sh
30
- npm install --save-exact devcontour-harness@0.1.0-alpha.11 # точная версия
31
+ npm install --save-exact devcontour-harness@0.1.0-alpha.13 # точная версия
31
32
  # или: npm install --save-exact devcontour-harness@alpha # текущая alpha
32
33
  npx harness --version
33
34
  ```
@@ -35,6 +36,11 @@ npx harness --version
35
36
  Выпуски публикуются под тегом `alpha`; ставьте точную версию или `@alpha`,
36
37
  а не версию по умолчанию без тега.
37
38
 
39
+ Чтобы не менять зависимости и lockfile существующего проекта, пакет ставят в
40
+ отдельный каталог инструментов — снаружи репозитория или в `.harness-tools/`
41
+ внутри него ([standalone](docs/standalone.md#установка), skill
42
+ [harness-setup](skills/harness-setup/SKILL.md)).
43
+
38
44
  Из локального архива `devcontour-harness-<version>.tgz` (поддерживаемый
39
45
  способ без registry):
40
46
 
@@ -117,6 +123,31 @@ PASS» нет. Адаптеры хоста реализуют порты `Execut
117
123
  `devcontour-harness/schemas/<имя>.schema.json`. Декларации используют типы
118
124
  Node.js: TypeScript-потребителю нужен `@types/node` (или `skipLibCheck`).
119
125
 
126
+ ## Выпуск 0.1.0-alpha.13
127
+
128
+ Controller mode для задач Relay и установка без лишних вопросов. Подробно —
129
+ [заметки выпуска](docs/release-notes/0.1.0-alpha.13.md).
130
+
131
+ - `tasks[].context` и `contextDir` коннектора: локальные пакеты контекста
132
+ (оверлей controller-mode) закрепляются в снимке задач Relay.
133
+ - Skill `harness-setup` решает рутинные выборы сам и спрашивает только на
134
+ развилках (вендоры writer/reviewer, недостающие факты Relay, изоляция,
135
+ Docker, конфликты с правилами проекта, коммит, запись в Relay, живые
136
+ вызовы); маршрут для pnpm/Turborepo-монорепо (Turbo Starter): свой
137
+ профиль, gates из команд проекта, controller mode.
138
+
139
+ ## Выпуск 0.1.0-alpha.12
140
+
141
+ Skill установки и обновления. Подробно —
142
+ [заметки выпуска](docs/release-notes/0.1.0-alpha.12.md).
143
+
144
+ - `skills/harness-setup/SKILL.md` — агент один раз спрашивает решения первой
145
+ настройки (новый или существующий проект, ветка, каталог установки —
146
+ `.harness-tools/` в проекте или снаружи, источник пакета, writer и reviewer
147
+ с моделями, согласование, источник задач: файлы или Relay), ставит точную
148
+ версию, выполняет `init` и обновляет версию с копией state и проверкой
149
+ совместимости. Движок, договоры и формат состояния не менялись.
150
+
120
151
  ## Выпуск 0.1.0-alpha.11
121
152
 
122
153
  Профили в форме хоста. Подробно —
@@ -226,6 +257,7 @@ alpha.5 не читает; прежний DevContour его открывает
226
257
  ## Документация
227
258
 
228
259
  - [Быстрый старт по шагам](docs/quickstart.md)
260
+ - [Установка, первая настройка и обновление версии — skill harness-setup](skills/harness-setup/SKILL.md)
229
261
  - [Самостоятельный проект: init, планирование (plan-request, plan-check), explain, capabilities, проекции](docs/standalone.md)
230
262
  - [Архитектура и границы доверия](docs/architecture.md)
231
263
  - [Договоры: входы, выходы, порты, ошибки, CLI](docs/contracts.md)
@@ -74,8 +74,8 @@ RunResult (SHA, evidence, ограничения) -> ResultSink
74
74
  Таблица перечисляет каталоги `src/`. Git-операции — worktrees, snapshots,
75
75
  проверка области, candidate/integration, CAS и recovery — отдельного каталога
76
76
  не образуют: они в `engine` (`runner.ts`, `joint-runner.ts`), `control`
77
- (`base-move.ts`) и `verification`. Поставляемый skill с инструкциями и
78
- шаблонами лежит в корневом каталоге пакета `skills/`; исполняемые гарантии
77
+ (`base-move.ts`) и `verification`. Поставляемые skills лежат в корневом
78
+ каталоге пакета `skills/`, шаблоны — в `templates/`; исполняемые гарантии
79
79
  остаются в движке.
80
80
 
81
81
  Модули `setup`, `planning`, `explain`, `projections`, `catalog` и разрешение
@@ -413,9 +413,10 @@ runtime-граф пакета.
413
413
 
414
414
  ## Профили, skills и шаблоны
415
415
 
416
- Встроенные профили (`profiles/`), skill (`skills/harness/SKILL.md`) и шаблоны
417
- (`templates/`: политика, граф, инструкция агентам репозитория) входят в пакет.
418
- Skill и шаблоны только объясняют использование: движок, runner и проверки их
416
+ Встроенные профили (`profiles/`), skills (`skills/harness/SKILL.md` — работа
417
+ с поручениями, `skills/harness-setup/SKILL.md` — установка, первая настройка
418
+ и обновление версии) и шаблоны (`templates/`: политика, граф, инструкция
419
+ агентам репозитория) входят в пакет. Skills и шаблоны только объясняют использование: движок, runner и проверки их
419
420
  не читают, и удаление каталога skills не отключает ни gates, ни ревью, ни
420
421
  область записи, ни fencing — это проверяет smoke установленного архива.
421
422
  Профиль — вход для сборки политики, а не исполняемый объект: разрешение не
@@ -109,14 +109,40 @@ CLI: 0 — успех, 2 — неверный вход, 3 — отказ/бло
109
109
  | `projectId` | Ожидаемая идентичность проекта backend; несовпадение блокирует чтение снимка и любые мутации |
110
110
  | `actor` | Автор всех записей коннектора |
111
111
  | `stateDir` | Каталог состояния коннектора (относительно файла); без него — `--state` |
112
+ | `contextDir` | Каталог локальных пакетов контекста `tasks[].context` (относительно файла); по умолчанию `work/context` |
112
113
  | `repository` | ID репозитория политики (и путь для сверки) |
113
114
  | `tasks[]` | Задачи: `ref` (постоянный ID или ключ), роль, `writeScope`, `gates`, сопоставление критериев, обязательные документы |
114
115
  | `tasks[].criteria[]` | Критерий (`id` или точный `title`) → `gate` и `testId` JUnit; `evidence: "gate"` — достаточно прошедшего gate (явный выбор) |
116
+ | `tasks[].context` | Локальные пакеты контекста поручения — каталоги `<contextDir>/<id>` (например, `controller-mode`); закрепляются в снимке |
115
117
  | `completion` | `complete` — закрыть по правилам backend (по умолчанию в `done`); `review-only` — перевести в `review`/`column` и оставить человеку; `historicalComment` — при конфликте постановки записать явно исторический комментарий |
116
118
  | `eligibility.columns` | Колонки, из которых задача допускается к запуску (`done`/`cancelled` — никогда) |
117
119
  | `limits` | Пределы документов и постановки, число повторных чтений, срок владения доставкой |
118
120
  | `history` | Запись истории работы (корневая и дочерние сессии) |
119
121
 
122
+ ### Локальные пакеты контекста
123
+
124
+ `tasks[].context` добавляет к поручению задачи пакеты из `<contextDir>/<id>` —
125
+ те же каталоги, что у файлового графа: файлы вкладываются в запросы writer и
126
+ reviewer, `references/` закрепляется digest без вложения. Так к задачам Relay
127
+ подключается оверлей controller-mode
128
+ (`templates/controller-mode/`): `"context": ["controller-mode"]`.
129
+
130
+ - Пакеты закрепляются в `context.json` снимка; поручение получает
131
+ `context: [<контекст задачи>, ...пакеты]`. `source-snapshot` и manifest
132
+ называют их в `localContext` (ID, digest, число файлов и справок, байты).
133
+ - Digest пакетов и их распределение по задачам (какие у какой, в каком
134
+ порядке) входят в ревизию набора задач: изменённый, новый или перенесённый
135
+ пакет — новый `source-snapshot`, ревью плана и согласование (`run --source`
136
+ иначе отказывает `APPROVAL_STALE`). Semantic digest задачи от пакетов не
137
+ зависит: включение поля или изменение пакета не делает доставку уже
138
+ принятого результата конфликтом.
139
+ - Нет каталога или пакета — `NOT_READY` до чтения backend; пакет через
140
+ symlink — `INVALID_INPUT`. `source-check` называет то и другое в `problems`.
141
+ - Пределы контекста (файл, сумма, не больше 30 источников вместе с
142
+ контекстами задач) проверяются при снимке: превышение — `LIMIT_EXCEEDED`, и
143
+ задача не допускается к запуску.
144
+ - Без поля снимок, ревизия и digest задачи те же, что в прежних версиях.
145
+
120
146
  ### Идентичность и снимок
121
147
 
122
148
  - Идентичность задачи — `<provider>:<projectId>:<постоянный ID>`; ID поручения
@@ -13,7 +13,7 @@
13
13
  ```sh
14
14
  mkdir harness-demo && cd harness-demo
15
15
  npm init -y >/dev/null
16
- npm install --save-exact devcontour-harness@0.1.0-alpha.11
16
+ npm install --save-exact devcontour-harness@0.1.0-alpha.13
17
17
  npx harness --version
18
18
  npx harness doctor
19
19
  ```
@@ -0,0 +1,66 @@
1
+ # devcontour-harness 0.1.0-alpha.12
2
+
3
+ Skill установки, первой настройки и обновления версии. Движок, CLI, договоры
4
+ `v1alpha1`, политика, профили и формат состояния не менялись; gates,
5
+ `maxAttempts`, бюджеты попыток и повторов, правила находок, изоляция и
6
+ принятие target ref — прежние.
7
+
8
+ ## Что нового
9
+
10
+ - **`skills/harness-setup/SKILL.md`** — маршрут для агента пользователя:
11
+ - один раунд вопросов: существующий или новый проект, ветка подготовки,
12
+ каталог установки (`.harness-tools/` внутри проекта или каталог
13
+ снаружи), источник пакета (npm `@alpha`, сведённый к точной версии,
14
+ точная версия или архив с sha256), writer и reviewer, модели и effort,
15
+ ревью тем же вендором (только явным «да»), режим согласования плана,
16
+ источник задач (файлы графа, Relay v1, Relay-2), коммит подготовки;
17
+ - новый проект: Git, `package.json` со scripts `check` и `test` (JUnit в
18
+ `.reports/junit.xml`) и lockfile — только с согласия пользователя;
19
+ - установка в проект: версию закрепляют `.harness-tools/package.json` и
20
+ `package-lock.json`, `node_modules/` не коммитится, каталог добавляется в
21
+ `protectedPaths` политики; из архива — каталог целиком в
22
+ `.git/info/exclude`;
23
+ - `init` и правки политики по ответам: `model`, `effort` (только для
24
+ codex), reviewer того же вендора для ревью кода и контракта
25
+ (`same-vendor-allowed`), `approvalMode`; проверка `doctor --policy`
26
+ (схема, runtime, toolchain), `init --plan` без конфликтов и `validate`
27
+ первого графа (перекрёстные проверки политики);
28
+ - `connector.json` для Relay и проверки без записи в Relay
29
+ (`source-check`, `source-snapshot`);
30
+ - обновление: остановка writers, копия state, проверка копии новой
31
+ версией, установка, `doctor --state`, `init --plan` (изменённый профиль,
32
+ перенос репозитория или версия API — решение человека; manifest
33
+ пересоздаётся с прежним state, политика не перегенерируется), `validate`
34
+ графов, `resume`; откат по [upgrade](../upgrade.md);
35
+ - восстановление закреплённой версии в новом клоне — `npm ci --prefix
36
+ .harness-tools`.
37
+ - `skills/harness/SKILL.md` по-прежнему ведёт работу с поручениями; новый
38
+ skill передаёт ему управление после подготовки. В нём уточнены каталог
39
+ установки (снаружи или `.harness-tools/`) и причины `content-differs` на
40
+ `project.json` после обновления или переноса.
41
+
42
+ ## Документация
43
+
44
+ - [standalone](../standalone.md): установка вне проекта или в
45
+ `.harness-tools/`, закрепление версии файлом, обновление.
46
+ - [architecture](../architecture.md): состав поставляемых skills.
47
+ - [upgrade](../upgrade.md): столбец alpha.12 в матрице форматов.
48
+
49
+ ## Совместимость
50
+
51
+ Формат состояния не менялся: alpha.12 читает всё, что читает alpha.11, а
52
+ alpha.11 — всё, что пишет alpha.12. Обновление и откат — по
53
+ [upgrade](../upgrade.md).
54
+
55
+ ## Проверка выпуска
56
+
57
+ `npm run check` (lint, typecheck, schemas, build, весь набор тестов, package
58
+ smoke из установленного архива) на macOS arm64, Node.js 24.3. Тест skill
59
+ сверяет с договором CLI каждую команду маршрута: команду, флаги и
60
+ обязательные аргументы полных вызовов; smoke установленного архива требует
61
+ оба skill. Маршрут установки в `.harness-tools/` пройден вручную на свежем
62
+ npm-репозитории с опубликованной alpha.11: `init --plan`, `init`, правки
63
+ политики, `doctor --policy`, отказ `validate` для `effort` не у codex,
64
+ пересоздание manifest с прежним state. Живые модели не вызывались;
65
+ проверка DevContour для alpha.12 не повторялась (движок не менялся,
66
+ [проверенные версии](../compatibility-versions.md) относятся к alpha.11).
@@ -0,0 +1,91 @@
1
+ # devcontour-harness 0.1.0-alpha.13
2
+
3
+ Controller mode для задач Relay и маршрут установки, который спрашивает только
4
+ на развилках. Gates, `maxAttempts`, бюджеты попыток и повторов, правила
5
+ находок, изоляция, принятие target ref и формат состояния не менялись.
6
+
7
+ ## Что нового
8
+
9
+ - **`tasks[].context` и `contextDir` коннектора.** Задача Relay получает,
10
+ кроме своего контекста, локальные пакеты из `<contextDir>/<id>` (по
11
+ умолчанию `work/context` рядом с `connector.json`) — например, оверлей
12
+ controller-mode из `templates/controller-mode/`. Пакет закрепляется в
13
+ `context.json` снимка теми же правилами, что файловый контекст графа
14
+ (`references/` — справки без вложения). Digest пакетов и их распределение
15
+ по задачам входят в ревизию набора задач: изменённый пакет требует нового `source-snapshot` и
16
+ согласования (`run --source` иначе отказывает `APPROVAL_STALE`). Semantic
17
+ digest задачи от пакетов не зависит: включение поля или изменение пакета не
18
+ делает доставку принятого результата конфликтом. Нет каталога или пакета —
19
+ `NOT_READY` до чтения backend; `source-check` называет его в `problems`
20
+ ([connectors](../connectors.md#локальные-пакеты-контекста)).
21
+ - **Пределы контекста при снимке.** `source-snapshot` проверяет пределы
22
+ снимка контекста (размер файла, сумма, не больше 30 источников) и сообщает
23
+ превышение как `LIMIT_EXCEEDED`, а не только отказом при запуске;
24
+ `source-check` проверяет пределы локальных пакетов. Вывод
25
+ `source-snapshot` и manifest называют закреплённые пакеты (`localContext`).
26
+ - **`controllerContext`** (`examples/turbo-starter/setup.mjs`) пересобирает
27
+ пакет с пустого каталога и не берёт в справки `.harness/` и сам каталог
28
+ пакета: повторная сборка внутри репозитория не вкладывает копии пакета и
29
+ не оставляет справки удалённых AGENTS.md. `upstream: null` — сборка для
30
+ обычного репозитория проекта.
31
+ - **Skill `harness-setup`:**
32
+ - таблица «решить или спросить»: рутинные выборы (ветка, каталог
33
+ установки, источник пакета, профиль, gates, согласование, completion)
34
+ агент делает сам и объясняет в отчёте; спрашивает о вендорах
35
+ writer/reviewer, ревью тем же вендором, controller mode (оверлей
36
+ главнее правил продукта), пропуске проверки проекта или CI, недостающих
37
+ фактах Relay, изоляции, Docker, конфликтах с правилами проекта, коммите,
38
+ записи в Relay и живых вызовах;
39
+ - pnpm/Turborepo-монорепо (Turbo Starter): локальный `.harness/profile.json`
40
+ из команд проекта (`pnpm --filter`), test-gates с JUnit без новых
41
+ зависимостей или приёмочные проверки в `.harness/acceptance/`, gate
42
+ детерминизма генерации, защищённые пути, среда с Docker только по
43
+ согласию;
44
+ - controller mode: пакет собирается `controllerContext`, в Git не
45
+ коммитится (`.git/info/exclude`, пересборка в новом клоне) и
46
+ подключается к файловым графам и к задачам Relay (`tasks[].context`);
47
+ - при обновлении — предложить новые возможности выпуска, не применяя их.
48
+
49
+ ## Изменения договора
50
+
51
+ Добавления в конфигурацию коннектора: `contextDir` (строка) и
52
+ `tasks[].context` (1–10 ID); в manifest снимка и ответ `source-snapshot` —
53
+ `localContext` (только при пакетах). Без них снимок, ревизия набора, semantic
54
+ digest задачи, digest графа и контекста те же, что в alpha.12. Пакет с именем
55
+ вида ID контекста задачи (`c` + 20 hex) — `INVALID_INPUT`.
56
+
57
+ Изменение отказа: корень источника контекста через symlink (в файловом
58
+ графе и в пакетах коннектора) — `INVALID_INPUT` «Контекст не читается через
59
+ symlink» вместо `NOT_READY` «не найден»; symlink внутри источника и раньше
60
+ давал `INVALID_INPUT`.
61
+
62
+ ## Совместимость
63
+
64
+ Формат состояния не менялся: alpha.13 читает всё, что читает alpha.12, а
65
+ alpha.12 — всё, что пишет alpha.13. Конфигурация коннектора с новыми полями
66
+ alpha.12 не принимает (строгая схема). Обновление и откат — по
67
+ [upgrade](../upgrade.md).
68
+
69
+ ## Проверка выпуска
70
+
71
+ `npm run check` (lint, typecheck, schemas, build, весь набор тестов, package
72
+ smoke из установленного архива) на macOS arm64, Node.js 24.3. Тесты
73
+ коннектора на подделке backend (`tests/connector-context.test.ts`):
74
+
75
+ - пакет закреплён в снимке и поручении, его байты в контексте попытки
76
+ writer; поручение принято и доставлено;
77
+ - без поля ревизия набора, digest задачи, графа и контекста совпадают со
78
+ значениями alpha.12 (golden-тест, сверен с кодом тега);
79
+ - изменённый пакет — `APPROVAL_STALE` до запуска; перенос пакета между
80
+ задачами и смена порядка — новая ревизия; больше 30 источников —
81
+ `LIMIT_EXCEEDED`; доставка принятого
82
+ результата после изменения пакета и после включения поля — не конфликт;
83
+ - нет каталога — отказ без обращений к backend; нет пакета, свой
84
+ `contextDir`, конфликт имени, symlink-пакет, превышение пределов в снимке
85
+ и `source-check`, `localContext` в manifest.
86
+
87
+ Пересборка `controllerContext` после коммита пакета и удаления AGENTS.md —
88
+ `tests/starter-setup.test.ts`. Тест skill сверяет команды, флаги,
89
+ обязательные аргументы и пути `<pkg>/…` с поставкой. Живые модели и
90
+ настоящие Relay не вызывались; W01 на настоящих Relay для alpha.13 не
91
+ повторялся.
@@ -4,20 +4,41 @@
4
4
  без внешнего хоста: пользователь называет репозиторий, цель и ограничения,
5
5
  остальное агент получает от `harness init`, а план составляет сам через
6
6
  `harness plan-request` и `harness plan-check`. Пошаговый маршрут для агента —
7
- skill [`skills/harness/SKILL.md`](../skills/harness/SKILL.md).
7
+ skill [`skills/harness/SKILL.md`](../skills/harness/SKILL.md); установку,
8
+ первую настройку и обновление версии ведёт skill
9
+ [`skills/harness-setup/SKILL.md`](../skills/harness-setup/SKILL.md).
8
10
 
9
- ## Установка вне проекта
11
+ ## Установка
10
12
 
11
13
  Пакет ставится в отдельный каталог инструментов, а не в зависимости проекта:
12
- `package.json` и lockfile проекта не меняются.
14
+ `package.json` и lockfile проекта не меняются. Каталог — снаружи
15
+ репозитория или внутри него, в `.harness-tools/`.
13
16
 
14
17
  ```sh
15
- npm install --prefix <tooldir> --omit=dev --ignore-scripts ./devcontour-harness-<version>.tgz
18
+ npm install --prefix <tooldir> --save-exact --omit=dev --ignore-scripts devcontour-harness@<version>
19
+ # или из архива: npm install --prefix <tooldir> --omit=dev --ignore-scripts ./devcontour-harness-<version>.tgz
16
20
  H=<tooldir>/node_modules/.bin/harness
17
21
  $H --version
18
22
  $H doctor
19
23
  ```
20
24
 
25
+ Внутри проекта (`<tooldir>` = `<repo>/.harness-tools`) версию закрепляют
26
+ `.harness-tools/package.json` и `package-lock.json`: их коммитят вместе с
27
+ `.harness/`, а `node_modules/` исключает `.harness-tools/.gitignore`. Чтобы
28
+ поручение не могло изменить инструмент, которым его проверяют, каталог
29
+ добавляют в `protectedPaths` политики. Установка из архива записывает в
30
+ `package.json` локальный путь `file:` — такой каталог целиком исключают через
31
+ `.git/info/exclude`. Worktrees попыток создаются из Git, поэтому
32
+ неотслеживаемый каталог инструментов в них не попадает. Состояние исполнения
33
+ в любом случае остаётся вне репозитория.
34
+
35
+ Обновление — смена закреплённой версии в том же каталоге по процедуре
36
+ [upgrade](upgrade.md): остановка writers, копия state, проверка копии новой
37
+ версией, установка, `doctor --state`, `init --plan`, `validate` графов,
38
+ `resume`. Новая версия не перегенерирует политику: если изменился встроенный
39
+ профиль, `init --plan` сообщает `content-differs` на `project.json`, и решение
40
+ принимает человек.
41
+
21
42
  ## `harness init`
22
43
 
23
44
  ```sh
@@ -224,7 +224,8 @@ controller. Оверлей `templates/controller-mode/AGENTS.controller.md` v1
224
224
  читает живые файлы своего worktree;
225
225
  - `context/controller-mode/provenance.json` содержит версию и sha256
226
226
  оверлея, baseline SHA и blob каждого product AGENTS.md;
227
- - в каждом поручении указан `"context": ["controller-mode"]`.
227
+ - в каждом поручении указан `"context": ["controller-mode"]`; для задач
228
+ Relay — в `tasks[].context` коннектора ([connectors](connectors.md#локальные-пакеты-контекста)).
228
229
 
229
230
  Оверлей перечисляет правила, делегированные controller-у: worktree, commit,
230
231
  merge, история Relay, `pnpm run setup`, вложенные оркестраторы и `harness`.
package/docs/upgrade.md CHANGED
@@ -21,20 +21,20 @@
21
21
 
22
22
  ## Матрица форматов
23
23
 
24
- | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Читает 0.1.0-alpha.6 | Читает 0.1.0-alpha.7 | Читает 0.1.0-alpha.8 | Читает 0.1.0-alpha.9 | Читает 0.1.0-alpha.10 | Читает 0.1.0-alpha.11 | Пишет поверх | Примечание |
25
- | ------------------------------------------------------------------------------ | ------------------------ | ------------------------ | ----------------------- | -------------------- | -------------------- | -------------------- | --------------------- | --------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
26
- | standalone `stateVersion: 1` версией alpha.1 | да | да | да | да | да | да | да | да | да | одна база SQLite: документ, события, локальные записи |
27
- | хост, центральная база | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | данные не переносятся; новые поля необязательны |
28
- | хост, базы компонентов | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | нужна копия всех баз при остановленных writers |
29
- | alpha.2 после штатной остановки controller | да | да | да | да | да | да | да | да | да | роль controller снята — нового поля в состоянии нет |
30
- | alpha.2 с живой или брошенной ролью controller | **нет**, отказ до записи | да | да | да | да | да | да | да | — | роль хранит `engineVersion`; строгая схема alpha.1 его не знает → откат через копию |
31
- | alpha.4 с совместной проверкой | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | — | проверка хранит `engineVersion`; прежние версии его не знают → откат через копию |
32
- | alpha.6 standalone с записями ревью нового протокола или доставкой `uncertain` | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | — | `reviewRound`, `findings[].id/status`, статус доставки `uncertain`; alpha.4/alpha.5 отказывают `STATE_VERSION_UNSUPPORTED` до записи → откат через копию |
33
- | alpha.6, хост DevContour (адаптер alpha.4) | — | — | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | адаптер DevContour alpha.4 открывает такое состояние без отказа и пишет роль controller и события; новые поля сохраняются → для отката всё равно копия |
34
- | alpha.7 с новым `inspection` ревью кода | не проверялось | не проверялось | **нет** (строгая схема) | да | да | да | да | да | — | новые значения `source`, `exitCode: null`, `origins`/`context`/`excluded`; строгая схема alpha.6 их не принимает, поведение прежних версий при открытии не проверялось → откат через копию |
35
- | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
36
-
37
- Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи) и строки alpha.7. alpha.8, alpha.9, alpha.10 и alpha.11 формат состояния не меняют и читают всё, что читает alpha.7. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
24
+ | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Читает 0.1.0-alpha.6 | Читает 0.1.0-alpha.7 | Читает 0.1.0-alpha.8 | Читает 0.1.0-alpha.9 | Читает 0.1.0-alpha.10 | Читает 0.1.0-alpha.11 | Читает 0.1.0-alpha.12 | Читает 0.1.0-alpha.13 | Пишет поверх | Примечание |
25
+ | ------------------------------------------------------------------------------ | ------------------------ | ------------------------ | ----------------------- | -------------------- | -------------------- | -------------------- | --------------------- | --------------------- | --------------------- | --------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
26
+ | standalone `stateVersion: 1` версией alpha.1 | да | да | да | да | да | да | да | да | да | да | да | одна база SQLite: документ, события, локальные записи |
27
+ | хост, центральная база | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | данные не переносятся; новые поля необязательны |
28
+ | хост, базы компонентов | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | нужна копия всех баз при остановленных writers |
29
+ | alpha.2 после штатной остановки controller | да | да | да | да | да | да | да | да | да | да | да | роль controller снята — нового поля в состоянии нет |
30
+ | alpha.2 с живой или брошенной ролью controller | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | — | роль хранит `engineVersion`; строгая схема alpha.1 его не знает → откат через копию |
31
+ | alpha.4 с совместной проверкой | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | — | проверка хранит `engineVersion`; прежние версии его не знают → откат через копию |
32
+ | alpha.6 standalone с записями ревью нового протокола или доставкой `uncertain` | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | — | `reviewRound`, `findings[].id/status`, статус доставки `uncertain`; alpha.4/alpha.5 отказывают `STATE_VERSION_UNSUPPORTED` до записи → откат через копию |
33
+ | alpha.6, хост DevContour (адаптер alpha.4) | — | — | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | адаптер DevContour alpha.4 открывает такое состояние без отказа и пишет роль controller и события; новые поля сохраняются → для отката всё равно копия |
34
+ | alpha.7 с новым `inspection` ревью кода | не проверялось | не проверялось | **нет** (строгая схема) | да | да | да | да | да | да | да | — | новые значения `source`, `exitCode: null`, `origins`/`context`/`excluded`; строгая схема alpha.6 их не принимает, поведение прежних версий при открытии не проверялось → откат через копию |
35
+ | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
36
+
37
+ Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи) и строки alpha.7. alpha.8, alpha.9, alpha.10, alpha.11, alpha.12 и alpha.13 формат состояния не меняют и читают всё, что читает alpha.7. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
38
38
  формата до записи проверяют тесты совместимости пакета
39
39
  <!-- repo-only -->(`tests/compatibility.test.ts`)<!-- /repo-only -->.
40
40
 
@@ -34,7 +34,7 @@ import {
34
34
  writeFileSync,
35
35
  } from 'node:fs';
36
36
  import { tmpdir } from 'node:os';
37
- import { dirname, join, relative, resolve } from 'node:path';
37
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
38
38
  import { fileURLToPath } from 'node:url';
39
39
  import { parseArgs } from 'node:util';
40
40
 
@@ -257,10 +257,17 @@ export function changeFiles(fixture, patch) {
257
257
  */
258
258
  export function controllerContext(fixture, dir) {
259
259
  const target = join(dir, 'controller-mode');
260
+ // Пересборка начинается с пустого каталога: справки удалённых AGENTS.md не
261
+ // остаются, а закоммиченный пакет и .harness/ не попадают в свои же справки.
262
+ rmSync(target, { recursive: true, force: true });
260
263
  mkdirSync(join(target, 'references'), { recursive: true });
261
264
  const overlay = readFileSync(join(CONTROLLER_TEMPLATE, 'AGENTS.controller.md'));
262
265
  writeFileSync(join(target, 'AGENTS.controller.md'), overlay);
263
- const product = git(fixture.repo, ['ls-files', '--', 'AGENTS.md', '**/AGENTS.md'])
266
+ const inside = relative(fixture.repo, resolve(target));
267
+ const exclude = [':(exclude).harness/**'];
268
+ if (inside && !inside.startsWith('..') && !isAbsolute(inside))
269
+ exclude.push(`:(exclude)${inside.split(sep).join('/')}/**`);
270
+ const product = git(fixture.repo, ['ls-files', '--', 'AGENTS.md', '**/AGENTS.md', ...exclude])
264
271
  .split('\n')
265
272
  .filter(Boolean);
266
273
  const references = product.map((path) => {
@@ -71,6 +71,8 @@ export class FileContextSource {
71
71
  const base = realpathSync(this.dir);
72
72
  const sources = [...new Set(ids)].map((id) => {
73
73
  const root = resolve(base, id);
74
+ if (root.startsWith(base + sep) && existsSync(root) && lstatSync(root).isSymbolicLink())
75
+ throw new HarnessError('INVALID_INPUT', `Контекст не читается через symlink: ${id}`);
74
76
  if (!root.startsWith(base + sep) || !existsSync(root) || !lstatSync(root).isDirectory())
75
77
  throw new HarnessError('NOT_READY', `Источник контекста ${id} не найден в ${this.dir}`);
76
78
  const pin = (path) => {
@@ -63,6 +63,7 @@ export declare class Connector {
63
63
  requiredDocuments: string[];
64
64
  repositoryId?: string | undefined;
65
65
  gates?: string[] | undefined;
66
+ context?: string[] | undefined;
66
67
  }[];
67
68
  completion: {
68
69
  mode: "complete" | "review-only";
@@ -85,6 +86,7 @@ export declare class Connector {
85
86
  client: string;
86
87
  };
87
88
  stateDir?: string | undefined;
89
+ contextDir?: string | undefined;
88
90
  } | {
89
91
  provider: "relay-v2-cli";
90
92
  relay: Record<string, unknown> & {
@@ -115,6 +117,7 @@ export declare class Connector {
115
117
  requiredDocuments: string[];
116
118
  repositoryId?: string | undefined;
117
119
  gates?: string[] | undefined;
120
+ context?: string[] | undefined;
118
121
  }[];
119
122
  completion: {
120
123
  mode: "complete" | "review-only";
@@ -137,7 +140,10 @@ export declare class Connector {
137
140
  client: string;
138
141
  };
139
142
  stateDir?: string | undefined;
143
+ contextDir?: string | undefined;
140
144
  };
145
+ /** Каталог пакетов `tasks[].context`: `contextDir` конфигурации, иначе `work/context` рядом с ней. */
146
+ get contextDir(): string;
141
147
  /** Состояние коннектора (создаётся при первой записи). */
142
148
  get store(): ConnectorStore;
143
149
  /** Только чтение: без создания файла; нет состояния — `undefined`. */
@@ -186,10 +192,6 @@ export declare class Connector {
186
192
  * semantic digest: повтор неизменной постановки ничего не меняет.
187
193
  */
188
194
  snapshot(outDir?: string): Promise<{
189
- connector: string;
190
- graphId: string;
191
- graphDigest: string;
192
- contextDigest: string;
193
195
  revision: string;
194
196
  created: boolean;
195
197
  eligible: boolean;
@@ -209,6 +211,11 @@ export declare class Connector {
209
211
  manifest: string;
210
212
  };
211
213
  snapshot: import("./snapshot.ts").BuiltSnapshot;
214
+ localContext?: import("./snapshot.ts").LocalContextManifest[] | undefined;
215
+ connector: string;
216
+ graphId: string;
217
+ graphDigest: string;
218
+ contextDigest: string;
212
219
  }>;
213
220
  /**
214
221
  * Перед запуском — свежее чтение: semantic digest обязан совпасть с
@@ -1,12 +1,12 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
- import { HarnessError, } from "../../contracts/index.js";
3
+ import { API_VERSION, HarnessError, } from "../../contracts/index.js";
4
4
  import { redactor } from "../../environment/redaction.js";
5
5
  import { connectorIdOf, loadConfig, orderIdOf, resolveFrom, storeFileOf, } from "./config.js";
6
6
  import { RelaySink } from "./delivery.js";
7
7
  import { HistoryRecorder } from "./history.js";
8
8
  import { createTransportFor } from "./registry.js";
9
- import { assertEligible, checkIdentity, matchCriterion, readStable, semanticDigest, } from "./snapshot.js";
9
+ import { assertEligible, checkIdentity, contextLimitIssues, localContext, matchCriterion, readStable, semanticDigest, } from "./snapshot.js";
10
10
  import { RelayWorkSource } from "./source.js";
11
11
  import { redactingTransport, settingsSecrets } from "./redact.js";
12
12
  import { ConnectorStore } from "./store.js";
@@ -46,6 +46,10 @@ export class Connector {
46
46
  get config() {
47
47
  return this.loaded.config;
48
48
  }
49
+ /** Каталог пакетов `tasks[].context`: `contextDir` конфигурации, иначе `work/context` рядом с ней. */
50
+ get contextDir() {
51
+ return resolveFrom(this.loaded.baseDir, this.config.contextDir ?? 'work/context');
52
+ }
49
53
  /** Состояние коннектора (создаётся при первой записи). */
50
54
  get store() {
51
55
  this.storeInstance ??= new ConnectorStore(this.storeFile, { provider: this.config.provider, projectId: this.config.projectId }, this.options.onStoreWrite ? { onWrite: this.options.onStoreWrite } : {});
@@ -64,6 +68,7 @@ export class Connector {
64
68
  config: this.config,
65
69
  transport: this.transport,
66
70
  store: this.store,
71
+ contextDir: this.contextDir,
67
72
  });
68
73
  }
69
74
  get history() {
@@ -114,6 +119,14 @@ export class Connector {
114
119
  return this.checkReport(undefined, false, [message], []);
115
120
  }
116
121
  const mapping = [];
122
+ try {
123
+ const local = await localContext(config, this.contextDir);
124
+ if (local.length)
125
+ problems.push(...contextLimitIssues({ apiVersion: API_VERSION, sources: local, missing: [] }));
126
+ }
127
+ catch (error) {
128
+ problems.push(this.redact(error instanceof Error ? error.message : String(error)));
129
+ }
117
130
  try {
118
131
  const { reads } = await readStable(this.transport, config.tasks.map((t) => t.ref), config.limits.rereads);
119
132
  reads.forEach((read, i) => {
@@ -202,6 +215,7 @@ export class Connector {
202
215
  graphId: snapshot.graph.id,
203
216
  graphDigest: snapshot.manifest.graphDigest,
204
217
  contextDigest: snapshot.manifest.contextDigest,
218
+ ...(snapshot.manifest.localContext ? { localContext: snapshot.manifest.localContext } : {}),
205
219
  revision: snapshot.revision,
206
220
  created,
207
221
  eligible: snapshot.problems.length === 0,