devcontour-harness 0.1.0-alpha.5 → 0.1.0-alpha.6

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.
Files changed (258) hide show
  1. package/README.md +24 -2
  2. package/docs/compatibility-versions.md +56 -0
  3. package/docs/compatibility.md +2 -2
  4. package/docs/connectors.md +323 -0
  5. package/docs/contracts.md +33 -5
  6. package/docs/integrations/hindsight.md +148 -0
  7. package/docs/quickstart.md +1 -1
  8. package/docs/release-notes/0.1.0-alpha.6.md +238 -0
  9. package/docs/review-probes.md +120 -0
  10. package/docs/review-protocol.md +251 -0
  11. package/docs/toolchains.md +167 -0
  12. package/docs/turbo-starter.md +269 -0
  13. package/docs/upgrade.md +48 -12
  14. package/examples/README.md +1 -0
  15. package/examples/toolchains/README.md +32 -0
  16. package/examples/toolchains/go/bad/label/label.go +9 -0
  17. package/examples/toolchains/go/good/label/label.go +9 -0
  18. package/examples/toolchains/go/policy.json +79 -0
  19. package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
  20. package/examples/toolchains/go/repo/_gitignore +1 -0
  21. package/examples/toolchains/go/repo/go.mod +5 -0
  22. package/examples/toolchains/go/repo/go.sum +2 -0
  23. package/examples/toolchains/go/repo/label/label.go +9 -0
  24. package/examples/toolchains/go/repo/label/label_test.go +27 -0
  25. package/examples/toolchains/go/repo/label/version.go +9 -0
  26. package/examples/toolchains/go/task.json +25 -0
  27. package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
  28. package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
  29. package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
  30. package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
  31. package/examples/toolchains/mixed-go/policy.json +126 -0
  32. package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
  33. package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
  34. package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
  35. package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
  36. package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
  37. package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
  38. package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
  39. package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
  40. package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
  41. package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
  42. package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
  43. package/examples/toolchains/mixed-go/task.json +25 -0
  44. package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
  45. package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
  46. package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
  47. package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
  48. package/examples/toolchains/mixed-python/policy.json +121 -0
  49. package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
  50. package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
  51. package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
  52. package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
  53. package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
  54. package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
  55. package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
  56. package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
  57. package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
  58. package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
  59. package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
  60. package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
  61. package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
  62. package/examples/toolchains/mixed-python/task.json +25 -0
  63. package/examples/toolchains/npm/bad/src/label.js +4 -0
  64. package/examples/toolchains/npm/good/src/label.js +4 -0
  65. package/examples/toolchains/npm/policy.json +82 -0
  66. package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
  67. package/examples/toolchains/npm/repo/_gitignore +4 -0
  68. package/examples/toolchains/npm/repo/package-lock.json +22 -0
  69. package/examples/toolchains/npm/repo/package.json +13 -0
  70. package/examples/toolchains/npm/repo/src/label.js +4 -0
  71. package/examples/toolchains/npm/repo/test/label.test.js +13 -0
  72. package/examples/toolchains/npm/task.json +25 -0
  73. package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
  74. package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
  75. package/examples/toolchains/pnpm/policy.json +85 -0
  76. package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
  77. package/examples/toolchains/pnpm/repo/_gitignore +4 -0
  78. package/examples/toolchains/pnpm/repo/package.json +10 -0
  79. package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
  80. package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
  81. package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
  82. package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
  83. package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
  84. package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
  85. package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
  86. package/examples/toolchains/pnpm/task.json +25 -0
  87. package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
  88. package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
  89. package/examples/toolchains/python/policy.json +80 -0
  90. package/examples/toolchains/python/repo/.python-version +1 -0
  91. package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
  92. package/examples/toolchains/python/repo/_gitignore +5 -0
  93. package/examples/toolchains/python/repo/pyproject.toml +19 -0
  94. package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
  95. package/examples/toolchains/python/repo/tests/test_label.py +13 -0
  96. package/examples/toolchains/python/repo/uv.lock +108 -0
  97. package/examples/toolchains/python/task.json +25 -0
  98. package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
  99. package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
  100. package/examples/toolchains/turbo/policy.json +110 -0
  101. package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
  102. package/examples/toolchains/turbo/repo/_gitignore +4 -0
  103. package/examples/toolchains/turbo/repo/package.json +13 -0
  104. package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
  105. package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
  106. package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
  107. package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
  108. package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
  109. package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
  110. package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
  111. package/examples/toolchains/turbo/repo/turbo.json +7 -0
  112. package/examples/toolchains/turbo/task.json +25 -0
  113. package/examples/turbo-starter/README.md +16 -0
  114. package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
  115. package/examples/turbo-starter/changes/issued-at.patch +130 -0
  116. package/examples/turbo-starter/cleanup.mjs +58 -0
  117. package/examples/turbo-starter/graph.json +32 -0
  118. package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
  119. package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
  120. package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
  121. package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
  122. package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
  123. package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
  124. package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
  125. package/examples/turbo-starter/harness/env/backend.env.template +7 -0
  126. package/examples/turbo-starter/harness/env/compose.env.template +6 -0
  127. package/examples/turbo-starter/policy.json +109 -0
  128. package/examples/turbo-starter/policy.lifecycle.json +114 -0
  129. package/examples/turbo-starter/setup.mjs +352 -0
  130. package/lib/agents/cli-adapter.js +2 -0
  131. package/lib/agents/review-probe.d.ts +179 -7
  132. package/lib/agents/review-probe.js +648 -55
  133. package/lib/agents/schemas.d.ts +3 -1
  134. package/lib/agents/schemas.js +7 -3
  135. package/lib/cli.js +167 -7
  136. package/lib/contracts/cli.d.ts +820 -11
  137. package/lib/contracts/cli.js +243 -4
  138. package/lib/contracts/errors.d.ts +3 -1
  139. package/lib/contracts/errors.js +2 -0
  140. package/lib/contracts/events.d.ts +37 -5
  141. package/lib/contracts/events.js +9 -0
  142. package/lib/contracts/execution.d.ts +176 -8
  143. package/lib/contracts/execution.js +9 -1
  144. package/lib/contracts/failure.js +5 -0
  145. package/lib/contracts/index.d.ts +1 -0
  146. package/lib/contracts/index.js +1 -0
  147. package/lib/contracts/policy.d.ts +15 -0
  148. package/lib/contracts/policy.js +10 -0
  149. package/lib/contracts/ports.d.ts +50 -0
  150. package/lib/contracts/profiles.d.ts +14 -0
  151. package/lib/contracts/profiles.js +4 -0
  152. package/lib/contracts/published.d.ts +230 -7
  153. package/lib/contracts/result.d.ts +171 -0
  154. package/lib/contracts/result.js +39 -1
  155. package/lib/contracts/review.d.ts +231 -0
  156. package/lib/contracts/review.js +326 -2
  157. package/lib/contracts/toolchain.d.ts +96 -0
  158. package/lib/contracts/toolchain.js +89 -0
  159. package/lib/control/doctor.d.ts +16 -1
  160. package/lib/control/doctor.js +45 -3
  161. package/lib/control/harness.d.ts +52 -6
  162. package/lib/control/harness.js +28 -1
  163. package/lib/control/recovery.d.ts +3 -1
  164. package/lib/control/review.js +14 -6
  165. package/lib/engine/classify.js +1 -0
  166. package/lib/engine/engine.d.ts +39 -15
  167. package/lib/engine/prompt.d.ts +29 -1
  168. package/lib/engine/prompt.js +57 -0
  169. package/lib/engine/property-recovery.js +2 -0
  170. package/lib/engine/result.js +52 -0
  171. package/lib/engine/reuse.js +6 -1
  172. package/lib/engine/runner.d.ts +25 -0
  173. package/lib/engine/runner.js +491 -122
  174. package/lib/environment/lifecycle.js +6 -0
  175. package/lib/environment/live-calls.js +3 -2
  176. package/lib/environment/os-tools.d.ts +16 -0
  177. package/lib/environment/os-tools.js +35 -0
  178. package/lib/environment/process.js +5 -4
  179. package/lib/integrations/connector/commands.d.ts +358 -0
  180. package/lib/integrations/connector/commands.js +428 -0
  181. package/lib/integrations/connector/config.d.ts +215 -0
  182. package/lib/integrations/connector/config.js +214 -0
  183. package/lib/integrations/connector/delivery.d.ts +107 -0
  184. package/lib/integrations/connector/delivery.js +591 -0
  185. package/lib/integrations/connector/fake.d.ts +291 -0
  186. package/lib/integrations/connector/fake.js +786 -0
  187. package/lib/integrations/connector/history.d.ts +124 -0
  188. package/lib/integrations/connector/history.js +497 -0
  189. package/lib/integrations/connector/index.d.ts +16 -0
  190. package/lib/integrations/connector/index.js +16 -0
  191. package/lib/integrations/connector/redact.d.ts +19 -0
  192. package/lib/integrations/connector/redact.js +91 -0
  193. package/lib/integrations/connector/registry.d.ts +15 -0
  194. package/lib/integrations/connector/registry.js +43 -0
  195. package/lib/integrations/connector/snapshot.d.ts +131 -0
  196. package/lib/integrations/connector/snapshot.js +422 -0
  197. package/lib/integrations/connector/source.d.ts +37 -0
  198. package/lib/integrations/connector/source.js +73 -0
  199. package/lib/integrations/connector/steps.d.ts +71 -0
  200. package/lib/integrations/connector/steps.js +188 -0
  201. package/lib/integrations/connector/store.d.ts +140 -0
  202. package/lib/integrations/connector/store.js +277 -0
  203. package/lib/integrations/connector/summary.d.ts +38 -0
  204. package/lib/integrations/connector/summary.js +59 -0
  205. package/lib/integrations/connector/testing-worker.d.ts +1 -0
  206. package/lib/integrations/connector/testing-worker.js +81 -0
  207. package/lib/integrations/connector/testing.d.ts +205 -0
  208. package/lib/integrations/connector/testing.js +1615 -0
  209. package/lib/integrations/connector/transport.d.ts +240 -0
  210. package/lib/integrations/connector/transport.js +28 -0
  211. package/lib/integrations/memory/context.d.ts +120 -0
  212. package/lib/integrations/memory/context.js +239 -0
  213. package/lib/integrations/memory/index.d.ts +8 -0
  214. package/lib/integrations/memory/index.js +8 -0
  215. package/lib/integrations/memory/provider.d.ts +79 -0
  216. package/lib/integrations/memory/provider.js +22 -0
  217. package/lib/integrations/memory/sink.d.ts +56 -0
  218. package/lib/integrations/memory/sink.js +116 -0
  219. package/lib/integrations/memory/testing.d.ts +44 -0
  220. package/lib/integrations/memory/testing.js +61 -0
  221. package/lib/integrations/relay-v1/index.d.ts +122 -0
  222. package/lib/integrations/relay-v1/index.js +860 -0
  223. package/lib/integrations/relay-v2/index.d.ts +145 -0
  224. package/lib/integrations/relay-v2/index.js +752 -0
  225. package/lib/integrations/relay-v2/process.d.ts +32 -0
  226. package/lib/integrations/relay-v2/process.js +112 -0
  227. package/lib/integrations/relay-v2/protocol.d.ts +172 -0
  228. package/lib/integrations/relay-v2/protocol.js +142 -0
  229. package/lib/profiles/resolve.d.ts +5 -1
  230. package/lib/profiles/resolve.js +31 -0
  231. package/lib/storage/document.d.ts +60 -12
  232. package/lib/toolchains/index.d.ts +9 -0
  233. package/lib/toolchains/index.js +9 -0
  234. package/lib/toolchains/inspect.d.ts +105 -0
  235. package/lib/toolchains/inspect.js +831 -0
  236. package/lib/toolchains/manifests.d.ts +67 -0
  237. package/lib/toolchains/manifests.js +238 -0
  238. package/lib/toolchains/probe.d.ts +28 -0
  239. package/lib/toolchains/probe.js +54 -0
  240. package/lib/toolchains/recipes.d.ts +33 -0
  241. package/lib/toolchains/recipes.js +51 -0
  242. package/lib/verification/acceptance.d.ts +16 -0
  243. package/lib/verification/acceptance.js +16 -0
  244. package/lib/verification/gates.d.ts +16 -0
  245. package/lib/verification/gates.js +44 -3
  246. package/package.json +22 -1
  247. package/profiles/go-gotestsum.json +21 -0
  248. package/profiles/npm-ci.json +22 -0
  249. package/profiles/pnpm-frozen.json +24 -0
  250. package/profiles/python-uv.json +26 -0
  251. package/schemas/execution-document.schema.json +155 -0
  252. package/schemas/execution-policy.schema.json +48 -0
  253. package/schemas/result-envelope.schema.json +213 -1
  254. package/schemas/run-event.schema.json +93 -0
  255. package/schemas/run-result.schema.json +207 -0
  256. package/schemas/stack-profile.schema.json +43 -0
  257. package/templates/controller-mode/AGENTS.controller.md +58 -0
  258. package/templates/controller-mode/README.md +44 -0
@@ -0,0 +1,269 @@
1
+ # Turbo Starter: рецепт и lifecycle-мост
2
+
3
+ Это первый продуктовый сценарий harness на настоящем pnpm/Turborepo-проекте.
4
+ В копию [Turbo Starter](https://github.com/oim-dev/turbo-starter) на commit
5
+ `78f1e76b80f76ae9d4289e0b0994df8a6f2b89cc` вносится небольшое изменение
6
+ контракта backend. OpenAPI и SDK генерируются штатной командой starter, затем
7
+ проверяется frontend-потребитель. Всё, чего нет в upstream (приёмочные
8
+ проверки с JUnit, политика, граф, мост Docker, шаблоны env), лежит в
9
+ `examples/turbo-starter/` пакета. Upstream не патчится.
10
+
11
+ | Файл | Назначение |
12
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
13
+ | `examples/turbo-starter/policy.json` | Политика основного сценария: `prepare` (`pnpm install --frozen-lockfile --offline`), gates, защищённые пути, toolchain (`pnpm 11.25.0`, `node >=24.3`) |
14
+ | `examples/turbo-starter/graph.json` | Поручение `issued-at`: область записи, gates, контекст `controller-mode` |
15
+ | `examples/turbo-starter/policy.lifecycle.json` | Политика с lifecycle `compose-bridge` (PostgreSQL на попытку и фазу) |
16
+ | `examples/turbo-starter/harness/` | Копируется в `.harness/` проекта: `compose-bridge.mjs`, `compose.acceptance.yml`, `env/*.template`, `acceptance/*.test.mjs` |
17
+ | `examples/turbo-starter/changes/*.patch` | Подготовленные изменения для fixture writer: `issued-at.patch` (верное, вместе с выходом генератора) и `issued-at-consumer-only.patch` (несовместимое) |
18
+ | `templates/controller-mode/` | Оверлей `AGENTS.controller.md` v1 и инструкция включения |
19
+
20
+ ## Как повторить
21
+
22
+ Всё нужное лежит в npm-архиве (`examples/turbo-starter/`, `templates/controller-mode/`);
23
+ checkout исходников harness не требуется. После `npm install devcontour-harness`:
24
+
25
+ ```sh
26
+ # 1. Копия starter + настройка (отдельный baseline-коммит) + pnpm 11.25.0 в <work>/tools,
27
+ # рядом — policy.json, graph.json и context/controller-mode/ (оверлей с provenance)
28
+ node node_modules/devcontour-harness/examples/turbo-starter/setup.mjs \
29
+ --work ./ts --source https://github.com/oim-dev/turbo-starter
30
+ # 2. Обычный путь harness (writer/reviewer — настоящие codex/claude из PATH)
31
+ cd ts && export PATH=$PWD/tools/node_modules/.bin:$PATH # npx найдёт harness проекта выше по дереву
32
+ npx harness validate --graph graph.json --policy policy.json
33
+ npx harness run --graph graph.json --policy policy.json --state state --sink file:results
34
+ npx harness result --state state --work issued-at
35
+ ```
36
+
37
+ Источник можно заменить локальным зеркалом (`--source <path>` или
38
+ `STARTER_SOURCE`). Копия клонируется с `--no-local`, после clone у неё
39
+ удаляется `origin`, поэтому источник не меняется. Настройка из `.harness/`
40
+ фиксируется отдельным коммитом `fixture: devcontour-harness
41
+ turbo-starter-setup/v1` с фиксированными автором и датой. При одинаковых
42
+ входах получается один и тот же baseline SHA. Манифест копии
43
+ (`<work>/fixture.json`) записывает upstream, baseline и sha256 каждого
44
+ файла настройки.
45
+
46
+ `--warm` один раз прогревает pnpm store и dlx-кэш генератора
47
+ (`@gromlab/rest-api-codegen@5.2.4`) на основном checkout копии. Без
48
+ прогрева `pnpm install --offline` в попытке на холодной машине упадёт с явной
49
+ ошибкой.
50
+
51
+ ### Библиотека подготовки (для W01 и своих сценариев)
52
+
53
+ `examples/turbo-starter/setup.mjs` (только встроенные модули Node, запускается
54
+ обычным `node`, импортируется как ES-модуль):
55
+
56
+ - `prepareStarterFixture({ workDir, source?, commit?, installPnpm?, warm? })`
57
+ возвращает `{ repo, baselineSha, upstream, pathPrepend, env, manifest }`.
58
+ - `starterPolicy(fixture, { template?, targetRef?, scenario?, repoPath?, edit? })`
59
+ возвращает политику примера. `scenario` подключает fixture runtime
60
+ (`FIXTURE_SCENARIO`).
61
+ - `starterGraph({ edit? })` возвращает граф примера, например для
62
+ отображения поручения Relay.
63
+ - `changeFiles(fixture, 'issued-at.patch')` возвращает файлы изменения
64
+ (`путь → содержимое`) для шага fixture writer.
65
+ - `controllerContext(fixture, dir)` создаёт источник контекста
66
+ `controller-mode` с provenance; `writeLayout(fixture, options?)` пишет
67
+ `policy.json`, `graph.json` и контекст рядом с копией.
68
+ - `approve()` и `implement(files)` возвращают шаги сценария управляемых
69
+ (fixture) runtime для повторяемых прогонов без модели.
70
+
71
+ <!-- repo-only -->
72
+
73
+ В checkout harness типы библиотеки — `scripts/acceptance/starter-fixture.d.mts`;
74
+ `scripts/acceptance/starter-fixture.mjs` реэкспортирует её для сценариев
75
+ приёмки, fixture runtime — `tests/fixtures/bin/fake-*`. Это инструменты
76
+ разработки harness, в архив они не входят.
77
+
78
+ <!-- /repo-only -->
79
+
80
+ ## Gates и кэш
81
+
82
+ | Gate | Команда | Что доказывает |
83
+ | ----------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
84
+ | `backend-types` | `pnpm --filter @oim/nest-backend check-types` | Backend компилируется (`prisma generate && tsc --noEmit`) |
85
+ | `sdk-generation` (test) | `node --test --test-reporter=junit … .harness/acceptance/generation.test.mjs` | Закоммиченные OpenAPI и оба SDK совпадают с выходом `pnpm rest-api-sdk:generate` |
86
+ | `sdk-build` | `pnpm --filter @oim/client-rest-api-sdk run build` | Web импортирует SDK из `dist` |
87
+ | `web-types` | `pnpm --filter @oim/next-web-app run check-types` | Потребитель frontend типизирован против SDK |
88
+ | `acceptance` (test) | `node --test --test-reporter=junit … .harness/acceptance/issued-at.test.mjs` | 5 testcases: поле в OpenAPI и SDK обоих realm, потребитель |
89
+
90
+ - JUnit пишет встроенный reporter Node 24, отдельные зависимости не нужны.
91
+ Отчёты лежат в `.artifacts/junit/` (ignored в starter). Harness удаляет
92
+ старый отчёт до запуска и принимает только свежий непустой отчёт на
93
+ проверяемом SHA.
94
+ - Turbo-кэш не участвует. Gates вызывают pnpm-фильтры напрямую, поэтому
95
+ результата из кэша (cache hit) нет. Каждая попытка ставит свои
96
+ `node_modules` в своём worktree. Общими между попытками остаются только
97
+ неизменяемый pnpm store и dlx-кэш. Если gate вызывает `turbo run`,
98
+ передавайте `--force`, иначе cache hit не докажет свежести.
99
+ `pnpm exec turbo` предварительная проверка toolchain сейчас отклоняет:
100
+ `turbo ^2.11.6` в package.json starter не закреплён, хотя lockfile
101
+ закрепляет 2.11.6.
102
+ - Gate `acceptance` поставляется примером, а не upstream:
103
+ `apps/nest-backend/AGENTS.md` запрещает добавлять тесты в backend.
104
+
105
+ ## Генерация и область записи (S02)
106
+
107
+ `generatedPaths` в harness означает «защищено»: путь из этого списка не
108
+ даёт права его менять. Поэтому выходы генерации OpenAPI и SDK туда **не**
109
+ входят. Схема такая:
110
+
111
+ 1. **Явное разрешение поручения.** `writeScope` поручения включает исходники
112
+ изменения и выходы генерации:
113
+ `apps/nest-backend/openapi`, `packages/{client,admin}-rest-api-sdk/src/generated`.
114
+ Без этого изменение генерации отклоняется как выход за область.
115
+ 2. **Защита входов генератора.** `protectedPaths` включает
116
+ `apps/nest-backend/src/export-openapi.ts`, `package.json` обоих SDK, где
117
+ закреплена версия codegen, lockfile, `.harness/` и AGENTS.md. Попытка не
118
+ может подменить генератор или проверку.
119
+ 3. **Доказательство детерминизма.** Gate `sdk-generation` на candidate и на
120
+ integration SHA запускает штатную генерацию и сверяет `git diff` и
121
+ неотслеживаемые файлы по каждому пути выхода. Затем он возвращает
122
+ дерево к проверяемому коммиту. Устаревший или вручную правленый выход
123
+ отклоняется с указанием пути.
124
+ 4. **`generatedPaths` по назначению.** Там остаётся только
125
+ `apps/nest-backend/src/generated`, клиент Prisma. Его никто не правит и не
126
+ коммитит (он ignored).
127
+
128
+ Проверки области и защищённых путей не выключаются. Защищённый путь важнее
129
+ `writeScope`.
130
+
131
+ ## Lifecycle-мост для существующего worktree (S03, S04, S06)
132
+
133
+ Starter-команды `worktree:create`/`worktree:up` требуют собственный реестр
134
+ `~/worktrees/.state` и свой каталог worktree. Harness их не вызывает и реестр
135
+ не подделывает. Worktree создаёт harness. `compose-bridge.mjs` поднимает
136
+ среду для уже существующего дерева через шаги lifecycle политики:
137
+
138
+ ```json
139
+ "lifecycle": {
140
+ "setup": [{ "id": "compose-setup", "command": ["node", ".harness/compose-bridge.mjs", "setup", "--port", "POSTGRES_PORT", "--env", ".harness/env/compose.env.template:compose.env", "--secret", "HX_DB_PASSWORD"] }],
141
+ "ready": [{ "id": "compose-ready", "command": ["node", ".harness/compose-bridge.mjs", "ready", "--timeout-ms", "120000"] }],
142
+ "teardown": [{ "id": "compose-teardown", "command": ["node", ".harness/compose-bridge.mjs", "teardown"] }]
143
+ }
144
+ ```
145
+
146
+ Gates, которым нужна база, запускаются через
147
+ `node .harness/compose-bridge.mjs exec -- <команда>`. Команда получает
148
+ переменные env-файлов среды и `HX_PROJECT`.
149
+
150
+ - **Каталог среды.** Harness передаёт шагам lifecycle, `prepare` и gates
151
+ фазы переменную `DEVCONTOUR_ENVIRONMENT_DIR` (псевдоним
152
+ `HARNESS_ENVIRONMENT_DIR`): `<state>/artifacts/<run>/<phase>/environment`.
153
+ Это каталог вне worktree, рядом с receipt harness `environment.json`.
154
+ Переменная сохраняется в receipt, поэтому повторная очистка в новом
155
+ процессе видит тот же каталог. Состояние моста лежит в `bridge/`:
156
+ `receipt.json`, `compose.json` (модель без подстановки значений),
157
+ `labels.json`, `env/*.env` (0600) и `diagnostics/`.
158
+ - **Уникальность.** Проект называется
159
+ `hx-<runId 12>-<phase>-<repository>`, своё имя на каждую попытку и фазу.
160
+ Порты выделяются свободными на `127.0.0.1` и отмечаются в реестре моста
161
+ (`$TMPDIR/devcontour-compose-bridge/ports`). Это защищает от гонки
162
+ «порт выделен, но ещё не занят». Тома и сеть принадлежат проекту.
163
+ - **Владение.** Каждый сервис, том и сеть получают метки
164
+ `devcontour.harness.run`, `devcontour.harness.phase`,
165
+ `devcontour.harness.repository` и `devcontour.harness.project`. Модель
166
+ проверяется до создания: `container_name`, bind mount, внешние тома и
167
+ сети, `network_mode: host` и `privileged` отклоняются.
168
+ - **Порядок.** setup (намерение в receipt → порты → env → `compose create`)
169
+ → ready (`up -d --wait --wait-timeout`) → `prepare` → gates → ревью →
170
+ teardown. До ready gates не запускаются. При отказе ready сохраняются
171
+ `ps` и `logs` с замаскированными секретами.
172
+ - **Очистка.** Сначала выполняется `compose down --volumes --remove-orphans`
173
+ по снимку модели. Затем то, что осталось, удаляется по меткам, причём
174
+ только при совпадении обеих меток, run и project. `docker prune` не
175
+ вызывается, контекст Docker не меняется, чужие контейнеры и тома не
176
+ трогаются. Env-файлы удаляются при любом исходе teardown. Receipt
177
+ сохраняет, что удалено и что осталось.
178
+ - **Окружение и секреты.** Шаги и gates получают только `inherit/values/secrets`
179
+ политики, переменные harness `DEVCONTOUR_*` и `HARNESS_*` и минимальную
180
+ базу (`PATH`, `HOME`, `TMPDIR`, `USER`, `LOGNAME`, `CI`); test-gates
181
+ дополнительно получают от harness `TURBO_FORCE` и `GOFLAGS=-count=1`
182
+ (свежесть, не секреты). Секрет задаётся
183
+ именем переменной оператора (`"secrets": {"HX_DB_PASSWORD": "HX_DB_PASSWORD_SOURCE"}`).
184
+ Его значение маскируют harness (логи шагов и gates, evidence, итог) и мост
185
+ (диагностика). В receipt попадают только имена переменных env-файлов.
186
+
187
+ ### Порядок принятия и очистки (S04)
188
+
189
+ Harness принимает попытку **после** teardown. Teardown candidate и integration
190
+ выполняются до CAS-продвижения target ref. Отсюда:
191
+
192
+ - сбой setup, ready или gates означает, что попытка не принята, а
193
+ teardown выполняется всё равно;
194
+ - сбой teardown тоже означает отказ (`CleanupFailure`). Target ref не
195
+ двигается, и Git-история не переписывается. Receipt получает статус
196
+ `cleanup-failed`, а в `cleanup-pending.json` перечисляются сохранённые
197
+ worktree. Ресурсы среды остаются, и это видно;
198
+ - если процесс убит при активной среде, receipt остаётся `active`. Новый
199
+ процесс с тем же state вызывает `Harness.cleanup(runId)`
200
+ (`Runner.finishCleanup`). Тот истекает попытку с просроченной арендой,
201
+ повторяет teardown по receipt только при мёртвом владельце и убирает
202
+ worktree. Повторный вызов безопасен и возвращает `nothing-pending`.
203
+ Evidence и логи сохраняются. Пример запуска (поставляется в пакете):
204
+ `node node_modules/devcontour-harness/examples/turbo-starter/cleanup.mjs --state <state> --run <runId>`.
205
+
206
+ Состояния «принято» и «очистка не завершена» поэтому не совпадают по
207
+ времени: принятая попытка уже прошла очистку своих сред. Остаётся
208
+ единственный случай, когда после принятия не удаляется worktree попытки
209
+ (`reason: remove-failed`). Он доводится той же очисткой и не трогает
210
+ принятый ref.
211
+
212
+ ## Controller mode (S05)
213
+
214
+ AGENTS.md starter обязывают агента самому создавать worktree, коммитить и
215
+ сливать изменения и вести историю Relay. Под harness эти операции выполняет
216
+ controller. Оверлей `templates/controller-mode/AGENTS.controller.md` v1
217
+ подключается **источником контекста**, а не файлом в репозитории (starter
218
+ запрещает новые `.md` файлы):
219
+
220
+ - `context/controller-mode/AGENTS.controller.md` содержит неизменённый
221
+ оверлей. Он вкладывается в запросы writer и обоих reviewer;
222
+ - в `context/controller-mode/references/**/AGENTS.md` лежат копии product
223
+ AGENTS.md. Они закреплены sha256, но в запрос не вкладываются: worker
224
+ читает живые файлы своего worktree;
225
+ - `context/controller-mode/provenance.json` содержит версию и sha256
226
+ оверлея, baseline SHA и blob каждого product AGENTS.md;
227
+ - в каждом поручении указан `"context": ["controller-mode"]`.
228
+
229
+ Оверлей перечисляет правила, делегированные controller-у: worktree, commit,
230
+ merge, история Relay, `pnpm run setup`, вложенные оркестраторы и `harness`.
231
+ Остальные правила продукта продолжают действовать (архитектура, запрет
232
+ ручных правок generated, lockfile). В запросе worker есть только запрет
233
+ делать commit. Обязанности коммитить в нём нет. Harness проверяет это
234
+ независимо от текста: commit worker-а или смена HEAD дают
235
+ `scope-violation`, и сценарий это проверяет.
236
+
237
+ ## Известные ограничения upstream
238
+
239
+ - `scripts/check-env.mjs` (`pnpm env:check`, часть корневого `pnpm lint`)
240
+ падает на Node 24.3.0: `fs.globSync` не находит dotfiles. На Node 24.22.1
241
+ проверка проходит. Пример корневой `pnpm lint` не использует и обходится
242
+ явными pnpm-фильтрами. Патч upstream не нужен.
243
+ - `react-admin-panel` `check-types` скачивает `@gromlab/svg-sprites` через
244
+ `npx --yes`, поэтому нужна сеть. В gates примера этот пакет не участвует.
245
+ - Изоляция gates в примере выключена (`isolation.mode: none`). pnpm store,
246
+ dlx-кэш и Docker socket лежат вне worktree. Песочница ОС для них
247
+ потребует отдельного списка путей и доменов.
248
+
249
+ <!-- repo-only -->
250
+
251
+ ## Сценарии приёмки
252
+
253
+ | ID | Скрипт | Что выполняется |
254
+ | ------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
255
+ | S01, S02, S05 | `scripts/acceptance/starter-s01.mjs` | Позитивный прогон через operator approval (`review-plan`, `approve`, `run`); несовместимый потребитель; область записи, защищённый путь, устаревший и вручную правленый выход; commit worker-а |
256
+ | S03 | `scripts/acceptance/starter-s03.mjs` | Две параллельные попытки с отдельными базами, барьер read-back, контрольный контейнер fixture |
257
+ | S04 | `scripts/acceptance/starter-s04.mjs` | Контрольный прогон, сбой setup после create, таймаут ready, сбой gate, сбой teardown, SIGKILL и очистка новым процессом |
258
+ | S06 | `scripts/acceptance/starter-s06.mjs` | Canary-значения, отказ с выводом секрета, явный повтор, доставка, поиск canary во всех артефактах |
259
+
260
+ Обёртка `tests/integration/starter-acceptance.test.ts` проверяет каждый
261
+ сценарий отдельным тестом. Без Docker или источника upstream тест падает с
262
+ `BLOCKED`, а не пропускается. Быстрые тесты без Docker и pnpm лежат в
263
+ `tests/starter-{setup,bridge,lifecycle}.test.ts`.
264
+
265
+ W01 (оба Relay) — `scripts/acceptance/w01.mjs relay-v1|relay-v2` и
266
+ `tests/integration/w01-relay-{v1,v2}.test.ts`: тот же Starter, публичный CLI
267
+ `source-*`, восстановление доставки и конфликт постановки.
268
+
269
+ <!-- /repo-only -->
package/docs/upgrade.md CHANGED
@@ -21,19 +21,21 @@
21
21
 
22
22
  ## Матрица форматов
23
23
 
24
- | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Пишет поверх | Примечание |
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
- | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
33
-
34
- Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion`. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
24
+ | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Читает 0.1.0-alpha.6 | Пишет поверх | Примечание |
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
+ | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
35
+
36
+ Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи). Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
35
37
  формата до записи проверяют тесты совместимости пакета
36
- (`tests/compatibility.test.ts`).
38
+ <!-- repo-only -->(`tests/compatibility.test.ts`)<!-- /repo-only -->.
37
39
 
38
40
  Новые версии добавляют только необязательные поля и новые пространства
39
41
  локальных записей. Схемы состояния строгие: прежняя версия отклоняет
@@ -99,6 +101,40 @@ alpha.4 (новых полей нет).
99
101
  `0.1.0-alpha.5` и обновление lock; содержимое alpha.4 под своим номером не
100
102
  меняется.
101
103
 
104
+ ### alpha.6: протокол ревью, доставка `uncertain`, toolchains
105
+
106
+ Формат `stateVersion: 1` прежний; добавлены только необязательные поля, но
107
+ строгие схемы прежних версий их не знают:
108
+
109
+ - записи ревью кода несут `reviewRound` и `findings[].id/status` (протокол
110
+ ревью, [review-protocol](review-protocol.md)); такая запись появляется после
111
+ первого ревью кода новой версией;
112
+ - запись доставки может иметь статус `uncertain` и поле `operation`; в журнале —
113
+ событие `delivery.uncertain`.
114
+
115
+ Состояние alpha.5 alpha.6 читает и продолжает без преобразования: прежние
116
+ записи ревью — legacy-раунды, прежние доставки и receipts не переписываются.
117
+ Standalone-состояние, в котором есть новые поля, alpha.5 и alpha.4 не читают:
118
+ отказ `STATE_VERSION_UNSUPPORTED` до любой записи. Прежний хост DevContour
119
+ (0.4.0-alpha.7 с адаптером alpha.4) такого отказа **не даёт**: состояние alpha.6
120
+ — и без записей ревью нового протокола, и с ними (`reviewRound`,
121
+ `findings[].id/status`) — он открывает и может записать в него роль controller,
122
+ события и документ, а при готовых поручениях — выдать по ним работу; новые поля при этом сохраняются, но гарантии строгой схемы
123
+ нет. Пока действует роль controller alpha.6, прежний writer её не перехватывает
124
+ и ничего не пишет. Защиты от запуска прежнего DevContour на состоянии alpha.6
125
+ нет, поэтому откат с alpha.6 — только восстановлением копии состояния,
126
+ сделанной до обновления; прежнюю версию на текущее состояние не запускать.
127
+
128
+ Правила `engineVersion` (alpha.2–alpha.5) действуют и между alpha.5 и alpha.6:
129
+ живая роль и активные попытки alpha.5 не продолжаются до истечения срока и
130
+ recovery.
131
+
132
+ Политики: новые необязательные поля `repositories[].toolchain` и
133
+ `reviewProtocolRetries`. Политика без них сохраняет прежние `policyDigest`,
134
+ `executionDigest` и закрепления; политика с ними получает новое основание
135
+ (новое согласование). Встроенные профили alpha.5 побайтно прежние; новые
136
+ профили — новые id (`npm-ci`, `pnpm-frozen`, `go-gotestsum`, `python-uv`).
137
+
102
138
  ## Перед обновлением
103
139
 
104
140
  1. **Остановить записывающих.** Запретить новые выдачи (`harness pause`),
@@ -19,6 +19,7 @@ npx harness validate --graph files-only/graph.json --policy files-only/policy.js
19
19
  | `one-vendor/` | writer и reviewer одного вендора — только с явным `same-vendor-allowed` |
20
20
  | `files-only/` | поручение, контекст и результат только файлами, без внешних сервисов |
21
21
  | `external-connector/` | источник поручений и получатель результата через порты библиотеки |
22
+ | `turbo-starter/` | pnpm/Turborepo-проект с генерацией SDK и Docker lifecycle (см. README) |
22
23
 
23
24
  Reviewer задаётся правилом ревью явно; неявного «противоположного runtime»
24
25
  нет. При `different-vendor` (умолчание) reviewer того же вендора, что автор,
@@ -0,0 +1,32 @@
1
+ # Toolchains: семь вариантов target-проекта
2
+
3
+ Каждый каталог — маленький target-проект с детерминированной функцией `label(n)` и поручением
4
+ дополнять номер нулями до трёх знаков (`item-007`). Тесты проекта уже описывают новое
5
+ поведение и защищены от записи.
6
+
7
+ | Каталог | Проект | Установка | Проверки |
8
+ | -------------- | ----------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------- |
9
+ | `npm` | Node + npm | `npm ci` | `npm run check`, `npm run test` (node --test, JUnit) |
10
+ | `pnpm` | pnpm workspace, локальный бинарник `fx-check` | `pnpm install --frozen-lockfile` | `pnpm run check`, `pnpm run test` (фильтр `@fx/label`) |
11
+ | `turbo` | pnpm + Turborepo 2.11.10 | `pnpm install --frozen-lockfile` | `turbo run check/test --filter=@fx/label`, test — `--force` |
12
+ | `go` | Go-модуль без package.json | `go mod download` | `go vet`, `gotestsum … -- -count=1` |
13
+ | `python` | uv-проект без package.json | `uv sync --locked` | `ruff check`, `pytest --junitxml` |
14
+ | `mixed-go` | один Git root: pnpm `frontend/` + Go `backend/` + `contract/` | оба рецепта | gates с `cwd`, проверка контракта |
15
+ | `mixed-python` | один Git root: pnpm `frontend/` + Python `backend/` + `contract/` | оба рецепта | gates с `cwd`, проверка контракта |
16
+
17
+ Состав каталога: `repo/` — target-проект (`_gitignore` при подготовке становится
18
+ `.gitignore`), `policy.json` — политика (`path: "repo"`), `task.json` — поручение и требования
19
+ с `testId`, `good/` и `bad/` — файлы, которые пишет fixture writer в положительном и
20
+ отрицательном прогоне.
21
+
22
+ <!-- repo-only -->
23
+
24
+ В репозитории harness (не входит в пакет). Сквозной прогон через обычный путь harness (нужны npm, pnpm, go + gotestsum, uv):
25
+
26
+ ```sh
27
+ node --import tsx scripts/acceptance/t01.mjs
28
+ ```
29
+
30
+ <!-- /repo-only -->
31
+
32
+ Подробности — `docs/toolchains.md`.
@@ -0,0 +1,9 @@
1
+ // Package label форматирует метки позиций заказа.
2
+ package label
3
+
4
+ import "fmt"
5
+
6
+ // Неверная реализация для отрицательного прогона: два знака.
7
+ func Label(n int) string {
8
+ return fmt.Sprintf("item-%02d", n)
9
+ }
@@ -0,0 +1,9 @@
1
+ // Package label форматирует метки позиций заказа.
2
+ package label
3
+
4
+ import "fmt"
5
+
6
+ // Label возвращает метку позиции: номер дополняется нулями до трёх знаков.
7
+ func Label(n int) string {
8
+ return fmt.Sprintf("item-%03d", n)
9
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "policyVersion": 1,
3
+ "repositories": [
4
+ {
5
+ "id": "app",
6
+ "path": "repo",
7
+ "targetRef": "refs/heads/harness/accepted",
8
+ "prepare": [
9
+ {
10
+ "id": "install",
11
+ "command": ["go", "mod", "download"],
12
+ "timeoutMs": 600000
13
+ }
14
+ ],
15
+ "gates": [
16
+ {
17
+ "id": "vet",
18
+ "kind": "check",
19
+ "command": ["go", "vet", "./..."],
20
+ "timeoutMs": 240000
21
+ },
22
+ {
23
+ "id": "acceptance",
24
+ "kind": "test",
25
+ "command": ["gotestsum", "--junitfile", ".reports/junit.xml", "--", "-count=1", "./..."],
26
+ "timeoutMs": 240000,
27
+ "dependsOn": ["vet"],
28
+ "report": {
29
+ "type": "junit",
30
+ "path": ".reports/junit.xml"
31
+ }
32
+ }
33
+ ],
34
+ "toolchain": {
35
+ "tools": {
36
+ "gotestsum": "1.12.3"
37
+ }
38
+ },
39
+ "protectedPaths": [
40
+ ".github/",
41
+ ".githooks/",
42
+ "go.mod",
43
+ "go.sum",
44
+ "label/label_test.go",
45
+ "REQUIREMENTS.md"
46
+ ]
47
+ }
48
+ ],
49
+ "roles": {
50
+ "developer": {
51
+ "writer": {
52
+ "runtime": "claude"
53
+ }
54
+ }
55
+ },
56
+ "review": {
57
+ "code": {
58
+ "reviewer": {
59
+ "runtime": "codex"
60
+ }
61
+ },
62
+ "plan": {
63
+ "reviewer": {
64
+ "runtime": "codex"
65
+ }
66
+ },
67
+ "contract": {
68
+ "reviewer": {
69
+ "runtime": "codex"
70
+ }
71
+ }
72
+ },
73
+ "isolation": {
74
+ "mode": "none",
75
+ "domains": []
76
+ },
77
+ "maxAttempts": 2,
78
+ "runTimeoutMs": 900000
79
+ }
@@ -0,0 +1,9 @@
1
+ # Requirements
2
+
3
+ ## REQ-LABEL-PAD
4
+
5
+ label(n) pads the number to three digits: label(7) is "item-007", label(42) is "item-042".
6
+
7
+ ## REQ-LABEL-LONG
8
+
9
+ label(n) keeps numbers longer than three digits unchanged: label(1234) is "item-1234".
@@ -0,0 +1 @@
1
+ .reports/
@@ -0,0 +1,5 @@
1
+ module example.com/fxlabel
2
+
3
+ go 1.24
4
+
5
+ require golang.org/x/mod v0.25.0
@@ -0,0 +1,2 @@
1
+ golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
2
+ golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
@@ -0,0 +1,9 @@
1
+ // Package label форматирует метки позиций заказа.
2
+ package label
3
+
4
+ import "fmt"
5
+
6
+ // Label возвращает метку позиции.
7
+ func Label(n int) string {
8
+ return fmt.Sprintf("item-%d", n)
9
+ }
@@ -0,0 +1,27 @@
1
+ package label
2
+
3
+ import "testing"
4
+
5
+ func TestLabelPadsOneDigit(t *testing.T) {
6
+ if got := Label(7); got != "item-007" {
7
+ t.Fatalf("Label(7) = %q, want item-007", got)
8
+ }
9
+ }
10
+
11
+ func TestLabelPadsTwoDigits(t *testing.T) {
12
+ if got := Label(42); got != "item-042" {
13
+ t.Fatalf("Label(42) = %q, want item-042", got)
14
+ }
15
+ }
16
+
17
+ func TestLabelKeepsLong(t *testing.T) {
18
+ if got := Label(1234); got != "item-1234" {
19
+ t.Fatalf("Label(1234) = %q, want item-1234", got)
20
+ }
21
+ }
22
+
23
+ func TestFormatVersion(t *testing.T) {
24
+ if !ValidFormat() {
25
+ t.Fatal("format version is not semver")
26
+ }
27
+ }
@@ -0,0 +1,9 @@
1
+ package label
2
+
3
+ import "golang.org/x/mod/semver"
4
+
5
+ // FormatVersion — версия формата меток (внешняя зависимость модуля проверяется go.sum).
6
+ const FormatVersion = "v1.0.0"
7
+
8
+ // ValidFormat сообщает, что версия формата — корректная semver.
9
+ func ValidFormat() bool { return semver.IsValid(FormatVersion) }
@@ -0,0 +1,25 @@
1
+ {
2
+ "title": "Pad item labels to three digits",
3
+ "goal": "Change label(n) in label/label.go so the number is zero-padded to three digits (item-007), keeping longer numbers unchanged.",
4
+ "acceptance": [
5
+ "label(7) is item-007 and label(42) is item-042",
6
+ "label(1234) is item-1234",
7
+ "All gates pass with fresh JUnit reports"
8
+ ],
9
+ "writeScope": ["label"],
10
+ "gates": ["vet", "acceptance"],
11
+ "requirements": [
12
+ {
13
+ "id": "REQ-LABEL-PAD",
14
+ "gate": "acceptance",
15
+ "testId": "example.com/fxlabel/label.TestLabelPadsOneDigit",
16
+ "scenario": "example.com/fxlabel/label.TestLabelPadsOneDigit runs and passes on the accepted SHA"
17
+ },
18
+ {
19
+ "id": "REQ-LABEL-LONG",
20
+ "gate": "acceptance",
21
+ "testId": "example.com/fxlabel/label.TestLabelKeepsLong",
22
+ "scenario": "example.com/fxlabel/label.TestLabelKeepsLong runs and passes on the accepted SHA"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,9 @@
1
+ // Package label форматирует метки позиций заказа.
2
+ package label
3
+
4
+ import "fmt"
5
+
6
+ // Неверная реализация для отрицательного прогона: два знака.
7
+ func Label(n int) string {
8
+ return fmt.Sprintf("item-%02d", n)
9
+ }
@@ -0,0 +1,4 @@
1
+ // Метка позиции заказа: номер дополняется нулями до трёх знаков.
2
+ export function label(n) {
3
+ return `item-${String(n).padStart(3, '0')}`;
4
+ }