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

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 (263) hide show
  1. package/README.md +42 -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/release-notes/0.1.0-alpha.7.md +71 -0
  10. package/docs/review-probes.md +199 -0
  11. package/docs/review-protocol.md +251 -0
  12. package/docs/toolchains.md +167 -0
  13. package/docs/turbo-starter.md +269 -0
  14. package/docs/upgrade.md +77 -24
  15. package/examples/README.md +1 -0
  16. package/examples/toolchains/README.md +32 -0
  17. package/examples/toolchains/go/bad/label/label.go +9 -0
  18. package/examples/toolchains/go/good/label/label.go +9 -0
  19. package/examples/toolchains/go/policy.json +79 -0
  20. package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
  21. package/examples/toolchains/go/repo/_gitignore +1 -0
  22. package/examples/toolchains/go/repo/go.mod +5 -0
  23. package/examples/toolchains/go/repo/go.sum +2 -0
  24. package/examples/toolchains/go/repo/label/label.go +9 -0
  25. package/examples/toolchains/go/repo/label/label_test.go +27 -0
  26. package/examples/toolchains/go/repo/label/version.go +9 -0
  27. package/examples/toolchains/go/task.json +25 -0
  28. package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
  29. package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
  30. package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
  31. package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
  32. package/examples/toolchains/mixed-go/policy.json +126 -0
  33. package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
  34. package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
  35. package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
  36. package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
  37. package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
  38. package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
  39. package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
  40. package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
  41. package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
  42. package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
  43. package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
  44. package/examples/toolchains/mixed-go/task.json +25 -0
  45. package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
  46. package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
  47. package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
  48. package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
  49. package/examples/toolchains/mixed-python/policy.json +121 -0
  50. package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
  51. package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
  52. package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
  53. package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
  54. package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
  55. package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
  56. package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
  57. package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
  58. package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
  59. package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
  60. package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
  61. package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
  62. package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
  63. package/examples/toolchains/mixed-python/task.json +25 -0
  64. package/examples/toolchains/npm/bad/src/label.js +4 -0
  65. package/examples/toolchains/npm/good/src/label.js +4 -0
  66. package/examples/toolchains/npm/policy.json +82 -0
  67. package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
  68. package/examples/toolchains/npm/repo/_gitignore +4 -0
  69. package/examples/toolchains/npm/repo/package-lock.json +22 -0
  70. package/examples/toolchains/npm/repo/package.json +13 -0
  71. package/examples/toolchains/npm/repo/src/label.js +4 -0
  72. package/examples/toolchains/npm/repo/test/label.test.js +13 -0
  73. package/examples/toolchains/npm/task.json +25 -0
  74. package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
  75. package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
  76. package/examples/toolchains/pnpm/policy.json +85 -0
  77. package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
  78. package/examples/toolchains/pnpm/repo/_gitignore +4 -0
  79. package/examples/toolchains/pnpm/repo/package.json +10 -0
  80. package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
  81. package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
  82. package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
  83. package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
  84. package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
  85. package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
  86. package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
  87. package/examples/toolchains/pnpm/task.json +25 -0
  88. package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
  89. package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
  90. package/examples/toolchains/python/policy.json +80 -0
  91. package/examples/toolchains/python/repo/.python-version +1 -0
  92. package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
  93. package/examples/toolchains/python/repo/_gitignore +5 -0
  94. package/examples/toolchains/python/repo/pyproject.toml +19 -0
  95. package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
  96. package/examples/toolchains/python/repo/tests/test_label.py +13 -0
  97. package/examples/toolchains/python/repo/uv.lock +108 -0
  98. package/examples/toolchains/python/task.json +25 -0
  99. package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
  100. package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
  101. package/examples/toolchains/turbo/policy.json +110 -0
  102. package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
  103. package/examples/toolchains/turbo/repo/_gitignore +4 -0
  104. package/examples/toolchains/turbo/repo/package.json +13 -0
  105. package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
  106. package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
  107. package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
  108. package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
  109. package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
  110. package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
  111. package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
  112. package/examples/toolchains/turbo/repo/turbo.json +7 -0
  113. package/examples/toolchains/turbo/task.json +25 -0
  114. package/examples/turbo-starter/README.md +16 -0
  115. package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
  116. package/examples/turbo-starter/changes/issued-at.patch +130 -0
  117. package/examples/turbo-starter/cleanup.mjs +58 -0
  118. package/examples/turbo-starter/graph.json +32 -0
  119. package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
  120. package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
  121. package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
  122. package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
  123. package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
  124. package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
  125. package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
  126. package/examples/turbo-starter/harness/env/backend.env.template +7 -0
  127. package/examples/turbo-starter/harness/env/compose.env.template +6 -0
  128. package/examples/turbo-starter/policy.json +109 -0
  129. package/examples/turbo-starter/policy.lifecycle.json +114 -0
  130. package/examples/turbo-starter/setup.mjs +352 -0
  131. package/lib/agents/cli-adapter.js +10 -1
  132. package/lib/agents/inspect.d.ts +56 -2
  133. package/lib/agents/inspect.js +264 -14
  134. package/lib/agents/review-probe.d.ts +229 -7
  135. package/lib/agents/review-probe.js +830 -72
  136. package/lib/agents/schemas.d.ts +3 -1
  137. package/lib/agents/schemas.js +7 -3
  138. package/lib/cli.js +167 -7
  139. package/lib/contracts/cli.d.ts +820 -11
  140. package/lib/contracts/cli.js +243 -4
  141. package/lib/contracts/errors.d.ts +3 -1
  142. package/lib/contracts/errors.js +2 -0
  143. package/lib/contracts/events.d.ts +37 -5
  144. package/lib/contracts/events.js +9 -0
  145. package/lib/contracts/execution.d.ts +389 -15
  146. package/lib/contracts/execution.js +76 -4
  147. package/lib/contracts/failure.js +5 -0
  148. package/lib/contracts/index.d.ts +1 -0
  149. package/lib/contracts/index.js +1 -0
  150. package/lib/contracts/policy.d.ts +15 -0
  151. package/lib/contracts/policy.js +10 -0
  152. package/lib/contracts/ports.d.ts +50 -0
  153. package/lib/contracts/profiles.d.ts +14 -0
  154. package/lib/contracts/profiles.js +4 -0
  155. package/lib/contracts/published.d.ts +279 -8
  156. package/lib/contracts/result.d.ts +171 -0
  157. package/lib/contracts/result.js +39 -1
  158. package/lib/contracts/review.d.ts +231 -0
  159. package/lib/contracts/review.js +326 -2
  160. package/lib/contracts/toolchain.d.ts +96 -0
  161. package/lib/contracts/toolchain.js +89 -0
  162. package/lib/control/doctor.d.ts +16 -1
  163. package/lib/control/doctor.js +45 -3
  164. package/lib/control/harness.d.ts +52 -6
  165. package/lib/control/harness.js +28 -1
  166. package/lib/control/recovery.d.ts +3 -1
  167. package/lib/control/review.js +23 -11
  168. package/lib/engine/budget.d.ts +3 -3
  169. package/lib/engine/classify.js +1 -0
  170. package/lib/engine/engine.d.ts +67 -17
  171. package/lib/engine/prompt.d.ts +29 -1
  172. package/lib/engine/prompt.js +57 -0
  173. package/lib/engine/property-recovery.js +2 -0
  174. package/lib/engine/result.js +52 -0
  175. package/lib/engine/reuse.js +6 -1
  176. package/lib/engine/runner.d.ts +25 -0
  177. package/lib/engine/runner.js +500 -123
  178. package/lib/environment/lifecycle.js +6 -0
  179. package/lib/environment/live-calls.js +3 -2
  180. package/lib/environment/os-tools.d.ts +16 -0
  181. package/lib/environment/os-tools.js +35 -0
  182. package/lib/environment/process.d.ts +8 -0
  183. package/lib/environment/process.js +13 -6
  184. package/lib/integrations/connector/commands.d.ts +358 -0
  185. package/lib/integrations/connector/commands.js +428 -0
  186. package/lib/integrations/connector/config.d.ts +215 -0
  187. package/lib/integrations/connector/config.js +214 -0
  188. package/lib/integrations/connector/delivery.d.ts +107 -0
  189. package/lib/integrations/connector/delivery.js +591 -0
  190. package/lib/integrations/connector/fake.d.ts +291 -0
  191. package/lib/integrations/connector/fake.js +786 -0
  192. package/lib/integrations/connector/history.d.ts +124 -0
  193. package/lib/integrations/connector/history.js +497 -0
  194. package/lib/integrations/connector/index.d.ts +16 -0
  195. package/lib/integrations/connector/index.js +16 -0
  196. package/lib/integrations/connector/redact.d.ts +19 -0
  197. package/lib/integrations/connector/redact.js +91 -0
  198. package/lib/integrations/connector/registry.d.ts +15 -0
  199. package/lib/integrations/connector/registry.js +43 -0
  200. package/lib/integrations/connector/snapshot.d.ts +131 -0
  201. package/lib/integrations/connector/snapshot.js +422 -0
  202. package/lib/integrations/connector/source.d.ts +37 -0
  203. package/lib/integrations/connector/source.js +73 -0
  204. package/lib/integrations/connector/steps.d.ts +71 -0
  205. package/lib/integrations/connector/steps.js +188 -0
  206. package/lib/integrations/connector/store.d.ts +140 -0
  207. package/lib/integrations/connector/store.js +277 -0
  208. package/lib/integrations/connector/summary.d.ts +38 -0
  209. package/lib/integrations/connector/summary.js +59 -0
  210. package/lib/integrations/connector/testing-worker.d.ts +1 -0
  211. package/lib/integrations/connector/testing-worker.js +81 -0
  212. package/lib/integrations/connector/testing.d.ts +205 -0
  213. package/lib/integrations/connector/testing.js +1615 -0
  214. package/lib/integrations/connector/transport.d.ts +240 -0
  215. package/lib/integrations/connector/transport.js +28 -0
  216. package/lib/integrations/memory/context.d.ts +120 -0
  217. package/lib/integrations/memory/context.js +239 -0
  218. package/lib/integrations/memory/index.d.ts +8 -0
  219. package/lib/integrations/memory/index.js +8 -0
  220. package/lib/integrations/memory/provider.d.ts +79 -0
  221. package/lib/integrations/memory/provider.js +22 -0
  222. package/lib/integrations/memory/sink.d.ts +56 -0
  223. package/lib/integrations/memory/sink.js +116 -0
  224. package/lib/integrations/memory/testing.d.ts +44 -0
  225. package/lib/integrations/memory/testing.js +61 -0
  226. package/lib/integrations/relay-v1/index.d.ts +122 -0
  227. package/lib/integrations/relay-v1/index.js +860 -0
  228. package/lib/integrations/relay-v2/index.d.ts +145 -0
  229. package/lib/integrations/relay-v2/index.js +752 -0
  230. package/lib/integrations/relay-v2/process.d.ts +32 -0
  231. package/lib/integrations/relay-v2/process.js +112 -0
  232. package/lib/integrations/relay-v2/protocol.d.ts +172 -0
  233. package/lib/integrations/relay-v2/protocol.js +142 -0
  234. package/lib/profiles/resolve.d.ts +5 -1
  235. package/lib/profiles/resolve.js +31 -0
  236. package/lib/storage/document.d.ts +116 -16
  237. package/lib/toolchains/index.d.ts +9 -0
  238. package/lib/toolchains/index.js +9 -0
  239. package/lib/toolchains/inspect.d.ts +105 -0
  240. package/lib/toolchains/inspect.js +831 -0
  241. package/lib/toolchains/manifests.d.ts +67 -0
  242. package/lib/toolchains/manifests.js +238 -0
  243. package/lib/toolchains/probe.d.ts +28 -0
  244. package/lib/toolchains/probe.js +54 -0
  245. package/lib/toolchains/recipes.d.ts +33 -0
  246. package/lib/toolchains/recipes.js +51 -0
  247. package/lib/verification/acceptance.d.ts +16 -0
  248. package/lib/verification/acceptance.js +16 -0
  249. package/lib/verification/gates.d.ts +38 -0
  250. package/lib/verification/gates.js +108 -26
  251. package/package.json +22 -1
  252. package/profiles/go-gotestsum.json +21 -0
  253. package/profiles/npm-ci.json +22 -0
  254. package/profiles/pnpm-frozen.json +24 -0
  255. package/profiles/python-uv.json +26 -0
  256. package/schemas/execution-document.schema.json +345 -4
  257. package/schemas/execution-policy.schema.json +48 -0
  258. package/schemas/result-envelope.schema.json +213 -1
  259. package/schemas/run-event.schema.json +93 -0
  260. package/schemas/run-result.schema.json +207 -0
  261. package/schemas/stack-profile.schema.json +43 -0
  262. package/templates/controller-mode/AGENTS.controller.md +58 -0
  263. package/templates/controller-mode/README.md +44 -0
@@ -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
+ }
@@ -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,4 @@
1
+ // Метка позиции заказа: номер дополняется нулями до трёх знаков.
2
+ export function label(n) {
3
+ return `item-${String(n).padStart(3, '0')}`;
4
+ }
@@ -0,0 +1,126 @@
1
+ {
2
+ "policyVersion": 1,
3
+ "repositories": [
4
+ {
5
+ "id": "app",
6
+ "path": "repo",
7
+ "targetRef": "refs/heads/harness/accepted",
8
+ "prepare": [
9
+ {
10
+ "id": "frontend-install",
11
+ "command": ["pnpm", "--dir", "frontend", "install", "--frozen-lockfile"],
12
+ "timeoutMs": 600000
13
+ },
14
+ {
15
+ "id": "backend-install",
16
+ "command": ["go", "-C", "backend", "mod", "download"],
17
+ "timeoutMs": 600000
18
+ }
19
+ ],
20
+ "toolchain": {
21
+ "tools": {
22
+ "pnpm": "10.12.3",
23
+ "gotestsum": "1.12.3"
24
+ },
25
+ "packageManagers": {
26
+ "frontend": "pnpm"
27
+ }
28
+ },
29
+ "gates": [
30
+ {
31
+ "id": "frontend-check",
32
+ "kind": "check",
33
+ "cwd": "frontend",
34
+ "command": ["pnpm", "run", "check"],
35
+ "timeoutMs": 240000
36
+ },
37
+ {
38
+ "id": "frontend-tests",
39
+ "kind": "test",
40
+ "cwd": "frontend",
41
+ "command": ["pnpm", "run", "test"],
42
+ "timeoutMs": 240000,
43
+ "dependsOn": ["frontend-check"],
44
+ "report": {
45
+ "type": "junit",
46
+ "path": ".reports/frontend.xml"
47
+ }
48
+ },
49
+ {
50
+ "id": "contract",
51
+ "kind": "check",
52
+ "cwd": "frontend",
53
+ "command": ["pnpm", "run", "contract"],
54
+ "timeoutMs": 60000
55
+ },
56
+ {
57
+ "id": "backend-vet",
58
+ "kind": "check",
59
+ "cwd": "backend",
60
+ "command": ["go", "vet", "./..."],
61
+ "timeoutMs": 240000
62
+ },
63
+ {
64
+ "id": "backend-tests",
65
+ "kind": "test",
66
+ "cwd": "backend",
67
+ "command": [
68
+ "gotestsum",
69
+ "--junitfile",
70
+ "../.reports/backend.xml",
71
+ "--",
72
+ "-count=1",
73
+ "./..."
74
+ ],
75
+ "timeoutMs": 240000,
76
+ "dependsOn": ["backend-vet"],
77
+ "report": {
78
+ "type": "junit",
79
+ "path": ".reports/backend.xml"
80
+ }
81
+ }
82
+ ],
83
+ "protectedPaths": [
84
+ ".github/",
85
+ ".githooks/",
86
+ "contract/",
87
+ "frontend/package.json",
88
+ "frontend/pnpm-lock.yaml",
89
+ "frontend/test/",
90
+ "REQUIREMENTS.md",
91
+ "backend/go.mod",
92
+ "backend/label/label_test.go"
93
+ ]
94
+ }
95
+ ],
96
+ "roles": {
97
+ "developer": {
98
+ "writer": {
99
+ "runtime": "claude"
100
+ }
101
+ }
102
+ },
103
+ "review": {
104
+ "code": {
105
+ "reviewer": {
106
+ "runtime": "codex"
107
+ }
108
+ },
109
+ "plan": {
110
+ "reviewer": {
111
+ "runtime": "codex"
112
+ }
113
+ },
114
+ "contract": {
115
+ "reviewer": {
116
+ "runtime": "codex"
117
+ }
118
+ }
119
+ },
120
+ "isolation": {
121
+ "mode": "none",
122
+ "domains": []
123
+ },
124
+ "maxAttempts": 2,
125
+ "runTimeoutMs": 900000
126
+ }
@@ -0,0 +1,9 @@
1
+ # Requirements
2
+
3
+ ## REQ-FRONTEND-CONTRACT
4
+
5
+ The frontend label(n) follows contract/label.json: prefix "item-", width 3, every example matches.
6
+
7
+ ## REQ-BACKEND-CONTRACT
8
+
9
+ The backend label follows the same contract/label.json examples as the frontend.
@@ -0,0 +1,6 @@
1
+ node_modules/
2
+ .reports/
3
+ .venv/
4
+ __pycache__/
5
+ .pytest_cache/
6
+ .ruff_cache/
@@ -0,0 +1,3 @@
1
+ module example.com/fxbackend
2
+
3
+ go 1.24
@@ -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,30 @@
1
+ package label
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "testing"
7
+ )
8
+
9
+ type contract struct {
10
+ Prefix string `json:"prefix"`
11
+ Width int `json:"width"`
12
+ Examples [][]any `json:"examples"`
13
+ }
14
+
15
+ func TestBackendLabelFollowsContract(t *testing.T) {
16
+ raw, err := os.ReadFile("../../contract/label.json")
17
+ if err != nil {
18
+ t.Fatal(err)
19
+ }
20
+ var c contract
21
+ if err := json.Unmarshal(raw, &c); err != nil {
22
+ t.Fatal(err)
23
+ }
24
+ for _, e := range c.Examples {
25
+ n, want := int(e[0].(float64)), e[1].(string)
26
+ if got := Label(n); got != want {
27
+ t.Errorf("Label(%d) = %q, want %q", n, got, want)
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,17 @@
1
+ // Согласованность общего контракта меток: примеры следуют prefix и width.
2
+ // Обе стороны (frontend и backend) проверяют себя по этим же примерам.
3
+ import { readFileSync } from 'node:fs';
4
+ const contract = JSON.parse(readFileSync(new URL('./label.json', import.meta.url), 'utf8'));
5
+ let bad = 0;
6
+ for (const [n, expected] of contract.examples) {
7
+ const derived = contract.prefix + String(n).padStart(contract.width, '0');
8
+ if (derived !== expected) {
9
+ console.error(`contract: ${n} → ${expected}, правило даёт ${derived}`);
10
+ bad++;
11
+ }
12
+ }
13
+ if (!contract.examples.length) bad++;
14
+ console.log(
15
+ bad ? 'contract: inconsistent' : `contract: ${contract.examples.length} examples consistent`,
16
+ );
17
+ process.exit(bad ? 1 : 0);
@@ -0,0 +1,9 @@
1
+ {
2
+ "prefix": "item-",
3
+ "width": 3,
4
+ "examples": [
5
+ [7, "item-007"],
6
+ [42, "item-042"],
7
+ [1234, "item-1234"]
8
+ ]
9
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "fx-frontend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "packageManager": "pnpm@10.12.3",
7
+ "scripts": {
8
+ "check": "node --check src/label.js && node --check test/label.test.js",
9
+ "contract": "node ../contract/check.mjs",
10
+ "test": "node --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=../.reports/frontend.xml test/*.test.js"
11
+ },
12
+ "devDependencies": {
13
+ "picocolors": "1.1.1"
14
+ }
15
+ }
@@ -0,0 +1,22 @@
1
+ lockfileVersion: '9.0'
2
+
3
+ settings:
4
+ autoInstallPeers: true
5
+ excludeLinksFromLockfile: false
6
+
7
+ importers:
8
+
9
+ .:
10
+ devDependencies:
11
+ picocolors:
12
+ specifier: 1.1.1
13
+ version: 1.1.1
14
+
15
+ packages:
16
+
17
+ picocolors@1.1.1:
18
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
19
+
20
+ snapshots:
21
+
22
+ picocolors@1.1.1: {}
@@ -0,0 +1,4 @@
1
+ // Метка позиции заказа.
2
+ export function label(n) {
3
+ return `item-${n}`;
4
+ }
@@ -0,0 +1,11 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync } from 'node:fs';
4
+ import { label } from '../src/label.js';
5
+
6
+ const contract = JSON.parse(
7
+ readFileSync(new URL('../../contract/label.json', import.meta.url), 'utf8'),
8
+ );
9
+ test('frontend label follows the shared contract', () => {
10
+ for (const [n, expected] of contract.examples) assert.equal(label(n), expected);
11
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "title": "Pad item labels to three digits",
3
+ "goal": "Change label(n) in frontend/src/label.js and backend/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": ["frontend/src", "backend/label"],
10
+ "gates": ["frontend-check", "frontend-tests", "contract", "backend-vet", "backend-tests"],
11
+ "requirements": [
12
+ {
13
+ "id": "REQ-FRONTEND-CONTRACT",
14
+ "gate": "frontend-tests",
15
+ "testId": "test.frontend label follows the shared contract",
16
+ "scenario": "test.frontend label follows the shared contract runs and passes on the accepted SHA"
17
+ },
18
+ {
19
+ "id": "REQ-BACKEND-CONTRACT",
20
+ "gate": "backend-tests",
21
+ "testId": "example.com/fxbackend/label.TestBackendLabelFollowsContract",
22
+ "scenario": "example.com/fxbackend/label.TestBackendLabelFollowsContract runs and passes on the accepted SHA"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,6 @@
1
+ """Метки позиций заказа (backend)."""
2
+
3
+
4
+ def label(n: int) -> str:
5
+ """Неверная реализация для отрицательного прогона: два знака."""
6
+ return f"item-{n:02d}"
@@ -0,0 +1,4 @@
1
+ // Метка позиции заказа: номер дополняется нулями до трёх знаков.
2
+ export function label(n) {
3
+ return `item-${String(n).padStart(3, '0')}`;
4
+ }
@@ -0,0 +1,6 @@
1
+ """Метки позиций заказа (backend)."""
2
+
3
+
4
+ def label(n: int) -> str:
5
+ """Метка позиции: номер дополняется нулями до трёх знаков."""
6
+ return f"item-{n:03d}"
@@ -0,0 +1,4 @@
1
+ // Метка позиции заказа: номер дополняется нулями до трёх знаков.
2
+ export function label(n) {
3
+ return `item-${String(n).padStart(3, '0')}`;
4
+ }