@1aboveio/skills 1.0.7 → 1.0.9

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 (224) hide show
  1. package/.codex-plugin/plugin.json +13 -0
  2. package/.codex-plugin/resources.json +1827 -0
  3. package/README.md +22 -0
  4. package/package.json +9 -2
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/airflow/release-check.mjs +3 -1
  7. package/skills/app-dev/android-foundation/SKILL.md +92 -34
  8. package/skills/app-dev/android-foundation/assets/android-ci-job.yml +13 -7
  9. package/skills/app-dev/android-foundation/references/ci.md +23 -13
  10. package/skills/app-dev/android-foundation/references/delivery-focus.md +29 -0
  11. package/skills/app-dev/android-foundation/references/diagnosis.md +136 -0
  12. package/skills/app-dev/android-foundation/references/gotchas.md +20 -11
  13. package/skills/app-dev/android-foundation/references/setup.md +15 -6
  14. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  15. package/skills/app-dev/android-foundation/scripts/device-run.sh +148 -42
  16. package/skills/app-dev/app-debug/SKILL.md +1 -1
  17. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  18. package/skills/app-dev/better-auth/SKILL.md +1 -1
  19. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  20. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  21. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  22. package/skills/app-dev/data-quality/SKILL.md +1 -1
  23. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  24. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  25. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  26. package/skills/app-dev/monorepo/SKILL.md +1 -1
  27. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  28. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  29. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  30. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  31. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  32. package/skills/app-dev/pyspark/SKILL.md +1 -1
  33. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  34. package/skills/app-dev/python-backend/SKILL.md +1 -1
  35. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  36. package/skills/app-dev/shadcn/SKILL.md +1 -1
  37. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  38. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  39. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  40. package/skills/cicd/cloud-build/SKILL.md +1 -1
  41. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  42. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  43. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  44. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  45. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  46. package/skills/cicd/docker/SKILL.md +1 -1
  47. package/skills/cicd/docker/release-check.mjs +3 -1
  48. package/skills/cicd/github-actions/SKILL.md +1 -1
  49. package/skills/cicd/github-actions/release-check.mjs +3 -1
  50. package/skills/cicd/github-flow/SKILL.md +1 -1
  51. package/skills/cicd/github-flow/release-check.mjs +3 -1
  52. package/skills/cicd/google-cloud/SKILL.md +1 -1
  53. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  54. package/skills/cicd/mergify/SKILL.md +1 -1
  55. package/skills/cicd/mergify/release-check.mjs +3 -1
  56. package/skills/cicd/podman/SKILL.md +1 -1
  57. package/skills/cicd/podman/release-check.mjs +3 -1
  58. package/skills/content/article-review/SKILL.md +1 -1
  59. package/skills/content/article-review/release-check.mjs +3 -1
  60. package/skills/content/article-to-image/SKILL.md +1 -1
  61. package/skills/content/article-to-image/release-check.mjs +3 -1
  62. package/skills/content/flowchart-svg/SKILL.md +1 -1
  63. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  64. package/skills/content/image-gen/SKILL.md +1 -1
  65. package/skills/content/image-gen/release-check.mjs +3 -1
  66. package/skills/content/mermaid/SKILL.md +1 -1
  67. package/skills/content/mermaid/release-check.mjs +3 -1
  68. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  69. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  70. package/skills/content/wechat-publisher/SKILL.md +1 -1
  71. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  72. package/skills/content/wechat-search/SKILL.md +1 -1
  73. package/skills/content/wechat-search/release-check.mjs +3 -1
  74. package/skills/content/xai-search/SKILL.md +88 -13
  75. package/skills/content/xai-search/release-check.mjs +3 -1
  76. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  77. package/skills/core/distill-writing/SKILL.md +1 -1
  78. package/skills/core/distill-writing/release-check.mjs +3 -1
  79. package/skills/core/doc-to-md/SKILL.md +1 -1
  80. package/skills/core/doc-to-md/release-check.mjs +3 -1
  81. package/skills/core/editorial-review/SKILL.md +1 -1
  82. package/skills/core/editorial-review/release-check.mjs +3 -1
  83. package/skills/core/house-style/SKILL.md +1 -1
  84. package/skills/core/house-style/release-check.mjs +3 -1
  85. package/skills/core/plain-language/SKILL.md +1 -1
  86. package/skills/core/plain-language/release-check.mjs +3 -1
  87. package/skills/core/polish-cn/SKILL.md +1 -1
  88. package/skills/core/polish-cn/release-check.mjs +3 -1
  89. package/skills/core/reg-parser/SKILL.md +1 -1
  90. package/skills/core/reg-parser/release-check.mjs +3 -1
  91. package/skills/core/terminology/SKILL.md +1 -1
  92. package/skills/core/terminology/release-check.mjs +3 -1
  93. package/skills/core/write-doc/SKILL.md +1 -1
  94. package/skills/core/write-doc/release-check.mjs +3 -1
  95. package/skills/engineering/better-review/SKILL.md +1 -1
  96. package/skills/engineering/better-review/release-check.mjs +3 -1
  97. package/skills/engineering/contributing/SKILL.md +1 -1
  98. package/skills/engineering/contributing/release-check.mjs +3 -1
  99. package/skills/engineering/e2e-test/SKILL.md +1 -1
  100. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  101. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  102. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  103. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  104. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  105. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  106. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  107. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  108. package/skills/engineering/golden-path/SKILL.md +1 -1
  109. package/skills/engineering/golden-path/release-check.mjs +3 -1
  110. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  111. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  112. package/skills/engineering/resolve-issues/SKILL.md +11 -20
  113. package/skills/engineering/resolve-issues/references/delivery-progress.md +63 -0
  114. package/skills/engineering/resolve-issues/references/expire.md +24 -11
  115. package/skills/engineering/resolve-issues/references/implementation.md +9 -1
  116. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  117. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  118. package/skills/engineering/resolve-issues/references/task-plan.md +12 -5
  119. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  120. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  121. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  122. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  123. package/skills/engineering/resolve-release/SKILL.md +1 -1
  124. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  125. package/skills/engineering/rush-issues/SKILL.md +5 -2
  126. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  127. package/skills/engineering/rush-release/SKILL.md +1 -1
  128. package/skills/engineering/rush-release/release-check.mjs +3 -1
  129. package/skills/engineering/test-quality/SKILL.md +1 -1
  130. package/skills/engineering/test-quality/release-check.mjs +3 -1
  131. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  132. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  133. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  134. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  135. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  136. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  137. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  138. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  139. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  140. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  141. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  142. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  143. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  144. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  145. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  146. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  147. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -172
  148. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  149. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  150. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  151. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  152. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  153. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  154. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  155. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  156. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  157. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  158. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  159. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  160. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  161. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  162. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  163. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  164. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  165. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  166. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  167. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  168. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  169. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  170. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  171. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  172. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  173. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  174. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  175. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  176. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  177. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  178. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  179. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  180. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  181. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  182. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  183. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  184. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  185. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  186. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  187. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  188. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  189. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  190. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  191. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  192. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  193. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  194. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  195. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  196. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  197. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  198. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  199. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  200. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  201. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  202. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  203. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  204. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  205. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  206. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  207. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  208. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  209. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  210. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  211. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  212. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  213. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  214. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  215. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  216. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  217. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  218. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  219. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  220. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  221. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  222. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  223. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  224. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
@@ -1,1060 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { spawnSync } from 'node:child_process'
4
- import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
5
- import { tmpdir } from 'node:os'
6
- import { dirname, join } from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
-
9
- import { reviewVerdict, fixAction, fixBriefOf, carryRecord, AFTER_PASS, ReviewIncomplete, ReviewVerdictError } from './review-verdict.mjs'
10
- import { rereviewScope } from '../../better-review/scripts/rereview-scope.mjs'
11
-
12
- const script = join(dirname(fileURLToPath(import.meta.url)), 'review-verdict.mjs')
13
-
14
- const HEAD = 'abc1234'
15
- const p1 = {
16
- id: 'R1', severity: 'P1', summary: 'tenant id not checked on update', file: 'src/a.ts', line: 10,
17
- trigger: 'update order 7 of tenant B with a tenant A token returns 200',
18
- rule: 'every write checks the caller tenant against the row tenant',
19
- paths: [{ path: 'src/a.ts:10', status: 'open' }, { path: 'src/b.ts:4', status: 'closed', evidence: 'assertTenant at src/b.ts:4' }],
20
- }
21
- const p2 = { id: 'R2', severity: 'P2', summary: 'rename helper', followUpUrl: 'https://github.com/o/r/issues/9' }
22
- const p3 = { id: 'R3', severity: 'P3', summary: 'tidy comment' }
23
- const review = (findings, extra = {}) => ({ version: 1, unit: '1201', round: 1, head: HEAD, commands: ['npm test'], findings, ...extra })
24
- const done = { spec: 'done', correctness: 'done', tests: 'done', standards: 'done' }
25
- const skipPrefixes = [
26
- 'skipped: not applicable:', 'skipped: not applicable -',
27
- 'skipped: non-applicable:', 'skipped: non-applicable -',
28
- ]
29
- const incompleteReasons = [
30
- 'sampled review', 'sampling only', 'sample-only review', 'only a subset reviewed', 'spot checked files',
31
- 'SAMPLED REVIEW', 'sampled-review',
32
- 'ran out of budget', 'ran out of time', 'budget exhausted', 'exhausted review budget',
33
- 'time limit reached', 'token budget exceeded', 'no time left', 'insufficient review budget',
34
- 'review not finished', 'tests not fully completed', 'did not finish the review',
35
- 'could not complete the pass', "review isn't complete", 'review has not been finished',
36
- 'review still to be completed', 'review not yet done', 'review never completed',
37
- 'review not yet fully completed', "review couldn't be completed", 'review not started',
38
- 'ran-out-of-budget', 'review not finished',
39
- ]
40
-
41
- test('completion metadata cannot authorize unfinished reviews, even with no findings or claimed completion', () => {
42
- for (const complete of [false, null, undefined, 'true', 0]) {
43
- assert.throws(() => reviewVerdict(review([], { complete }), HEAD), ReviewIncomplete)
44
- }
45
- for (const passes of [null, [], 'done', {}, { ...done, standards: undefined }]) {
46
- assert.throws(() => reviewVerdict(review([], { complete: true, passes }), HEAD), ReviewIncomplete)
47
- }
48
- for (const pass of Object.keys(done)) {
49
- for (const status of ['partial', 'pending', 'skipped: unfinished', 'skipped: not reached as a completed pass', 'skipped: timed out', 'skipped: stopped after execution incident', 'skipped: ran out of budget', 'skipped: ', 'skipped: not applicable; unfinished review']) {
50
- assert.throws(() => reviewVerdict(review([], { complete: true, passes: { ...done, [pass]: status } }), HEAD), ReviewIncomplete, `${pass}: ${status}`)
51
- }
52
- }
53
- })
54
-
55
- test('an accepted non-applicable prefix cannot override sampling, exhaustion, or negated completion', () => {
56
- for (const prefix of skipPrefixes) {
57
- for (const reason of incompleteReasons) {
58
- for (const pass of Object.keys(done)) {
59
- for (const completion of [{}, { complete: true }]) {
60
- const record = review([], { ...completion, passes: { ...done, [pass]: `${prefix} ${reason}` } })
61
- const original = structuredClone(record)
62
- assert.throws(() => reviewVerdict(record, HEAD), (error) =>
63
- error instanceof ReviewIncomplete && error.message.includes(`review pass ${pass} is unfinished`),
64
- `${pass}: ${prefix} ${reason}`)
65
- assert.deepEqual(record, original)
66
- }
67
- }
68
- }
69
- }
70
- })
71
-
72
- test('documented complete v1 records and non-applicable skips retain severity-only disposition', () => {
73
- for (const extra of [
74
- {},
75
- { complete: true },
76
- { passes: done },
77
- { complete: true, passes: done },
78
- { passes: { ...done, spec: 'skipped: no spec found' } },
79
- { passes: { ...done, standards: 'skipped: no standards found' } },
80
- { passes: { ...done, standards: 'skipped: not applicable: no repository standards' } },
81
- { passes: { ...done, tests: 'skipped: non-applicable: documentation-only diff' } },
82
- ...skipPrefixes.map((prefix) => ({ complete: true, passes: { ...done, tests: `${prefix} documentation-only diff` } })),
83
- { passes: { ...done, tests: 'skipped: not applicable' } },
84
- { passes: { ...done, tests: 'skipped: non-applicable' } },
85
- ]) {
86
- const result = reviewVerdict(review([p2, p3], extra), HEAD)
87
- assert.equal(result.verdict, 'pass')
88
- assert.equal(result.reviewedHead, HEAD)
89
- assert.deepEqual(result.deferred.map((finding) => finding.id), ['R2'])
90
- }
91
- })
92
-
93
- test('unresolved or malformed execution incidents remain incomplete independently of passes and severities', () => {
94
- for (const executionIncident of [{ status: 'open-impact-unresolved' }, { status: 'open' }, { status: 'contained' }, { status: 'unknown' }, {}, [], 'resolved', false]) {
95
- for (const findings of [[], [p1], [p2, p3]]) {
96
- assert.throws(() => reviewVerdict(review(findings, { passes: done, complete: true, executionIncident }), HEAD), ReviewIncomplete)
97
- }
98
- }
99
- const commands = ['npm test (failed)', 'npm test (corrected)']
100
- const record = review([p2], {
101
- passes: done, complete: true, commands,
102
- commandResults: [{ exitCode: 1 }, { exitCode: 0 }],
103
- executionIncident: { status: 'resolved', disposition: 'Owner assessed impact; corrected checks completed.' },
104
- })
105
- const original = structuredClone(record)
106
- const result = reviewVerdict(record, HEAD)
107
- assert.equal(result.verdict, 'pass')
108
- assert.deepEqual(result.commands, commands)
109
- assert.deepEqual(record, original)
110
- assert.equal(reviewVerdict(review([], { executionIncident: null }), HEAD).verdict, 'pass')
111
- })
112
-
113
- test('only P2/P3 findings pass the review and go to CI; P3 is skipped, not deferred', () => {
114
- const result = reviewVerdict(review([p2, p3]), HEAD)
115
- assert.equal(result.verdict, 'pass')
116
- assert.equal(result.reviewedHead, HEAD)
117
- assert.equal(result.fix, undefined)
118
- assert.deepEqual(result.deferred.map((f) => f.id), ['R2'])
119
- })
120
-
121
- test('a P3 is ignored: no follow-up, dropbox id, id, or summary needed', () => {
122
- for (const finding of [p3, { ...p3, followUpUrl: 'TODO' }, { ...p3, dropboxId: 'later' }, { severity: 'P3' }, { ...p3, summary: '' }]) {
123
- const result = reviewVerdict(review([finding]), HEAD)
124
- assert.equal(result.verdict, 'pass')
125
- assert.deepEqual(result.deferred, [])
126
- }
127
- assert.equal(reviewVerdict(review([p2, { ...p3, id: 'R2' }]), HEAD).verdict, 'pass', 'a P3 never collides with a real id')
128
- })
129
-
130
- test('a review with no findings passes', () => {
131
- assert.equal(reviewVerdict(review([]), HEAD).verdict, 'pass')
132
- })
133
-
134
- test('the reviewer cannot talk a P1 into a pass', () => {
135
- const result = reviewVerdict(review([p1], { verdict: 'pass' }), HEAD)
136
- assert.equal(result.verdict, 'fix')
137
- })
138
-
139
- test('a P0/P1 sends only the blocking findings to the fix worker', () => {
140
- for (const severity of ['P0', 'P1']) {
141
- const result = reviewVerdict(review([{ ...p1, severity }, p2, p3], { complete: true, passes: done }), HEAD)
142
- assert.equal(result.verdict, 'fix')
143
- const brief = fixBriefOf(result)
144
- assert.deepEqual(brief.fix.map((f) => f.id), ['R1'])
145
- assert.doesNotMatch(JSON.stringify(brief), /rename helper|tidy comment/)
146
- }
147
- })
148
-
149
- test('the first blocking round is a plain fix; with no previous file a later round is too', () => {
150
- const first = reviewVerdict(review([p1]), HEAD)
151
- assert.equal(first.verdict, 'fix')
152
- assert.equal(first.action, 'fix')
153
- assert.equal(reviewVerdict(review([p1], { round: 2 }), HEAD, null).action, 'fix')
154
- })
155
-
156
- test('integration attempts preserve fixRound and the first blocker after a pass selects a plain fix', () => {
157
- let prior = review([], { fixRound: 1 })
158
- assert.equal(reviewVerdict(prior, HEAD).fixRound, 1)
159
- for (let round = 2; round <= 8; round += 1) {
160
- const current = review([p2], { round, fixRound: 1, head: `integration-${round}` })
161
- const original = structuredClone(current)
162
- const result = reviewVerdict(current, current.head, prior)
163
- assert.equal(result.verdict, 'pass')
164
- assert.equal(result.round, round)
165
- assert.equal(result.fixRound, 1)
166
- assert.equal(result.reviewedHead, current.head)
167
- assert.deepEqual(current, original)
168
- prior = current
169
- }
170
- const current = review([p1], { round: 9, fixRound: 1 })
171
- const originalPrior = structuredClone(prior)
172
- const result = reviewVerdict(current, HEAD, prior)
173
- assert.equal(result.verdict, 'fix')
174
- assert.equal(result.action, 'fix')
175
- assert.deepEqual(result.recurring, [])
176
- assert.deepEqual(result.introduced, [])
177
- assert.equal(result.fixRound, 1)
178
- assert.equal(fixBriefOf(result).round, 9)
179
- assert.equal(fixBriefOf(result).fixRound, 1)
180
- assert.equal(fixAction(current, [p1], prior).action, 'fix')
181
- assert.deepEqual(prior, originalPrior)
182
- })
183
-
184
- const withOpen = (n, id = 'R1') => ({ ...p1, id, paths: Array.from({ length: n }, (_, i) => ({ path: `src/p${i}.ts:1`, status: 'open' })) })
185
-
186
- // Runs a blocking loop from open-row counts per round; returns each round's verdict.
187
- function runLoop(counts, extra = () => ({})) {
188
- const records = []
189
- return counts.map((n, i) => {
190
- const current = review([withOpen(n)], { round: i + 1, fixRound: i + 1, ...extra(i) })
191
- const result = reviewVerdict(current, HEAD, records.at(-1) ?? null, { history: records.slice(0, -1) })
192
- records.push(current)
193
- return result.verdict === 'fix' ? 'fix' : result.reason
194
- })
195
- }
196
-
197
- test('fix rounds continue past any fixed count while open rows reach new lows', () => {
198
- assert.deepEqual(runLoop([9, 8, 7, 6, 5, 4, 3, 2, 1]), Array(9).fill('fix'))
199
- // One round without a new low still fixes; the second hands back.
200
- assert.deepEqual(runLoop([3, 2, 2, 2]), ['fix', 'fix', 'fix', 'not-converging'])
201
- // A stall, a new low, then a stall is never two in a row.
202
- assert.deepEqual(runLoop([4, 4, 3, 3, 2]), Array(5).fill('fix'))
203
- })
204
-
205
- test('alternating or rising counts hand back: progress is a new low, not a drop from last round', () => {
206
- assert.deepEqual(runLoop([3, 2, 3, 2]), ['fix', 'fix', 'fix', 'not-converging'])
207
- assert.deepEqual(runLoop([5, 4, 5, 4, 5]), ['fix', 'fix', 'fix', 'not-converging', 'not-converging'])
208
- assert.deepEqual(runLoop([2, 5, 3]), ['fix', 'fix', 'not-converging'])
209
- // Swapping one finding for another of the same size is no progress.
210
- const r1 = review([withOpen(1)], { round: 1, fixRound: 1 })
211
- const r2 = review([withOpen(1, 'other')], { round: 2, fixRound: 2 })
212
- const r3 = review([withOpen(1, 'third')], { round: 3, fixRound: 3 })
213
- assert.equal(reviewVerdict(r3, HEAD, r2, { history: [r1] }).reason, 'not-converging')
214
- })
215
-
216
- test('incomplete reviews are passed over, never counted as progress; a pass starts a new loop', () => {
217
- const incomplete = (i) => (i === 2 ? { complete: false } : {})
218
- assert.throws(() => runLoop([2, 2, 2, 2], incomplete), ReviewIncomplete)
219
- const r = (n, round, extra = {}) => review([withOpen(n)], { round, fixRound: round, ...extra })
220
- const history = [r(2, 1), r(2, 2), { ...r(2, 3), complete: false, fixRound: 3 }]
221
- const result = reviewVerdict(r(2, 4, { fixRound: 3, tier: 'full', complete: true, passes: done }), HEAD, history.at(-1), { history: history.slice(0, -1) })
222
- assert.equal(result.reason, 'not-converging')
223
- const passed = review([p2], { round: 3, fixRound: 3 })
224
- assert.equal(reviewVerdict(r(2, 4, { fixRound: 3 }), HEAD, passed, { history: [r(2, 1), r(2, 2)] }).verdict, 'fix')
225
- })
226
-
227
- test('legacy findings without paths are not measured, so moving to new records is no false stall', () => {
228
- const legacy = { ...p1, paths: undefined }
229
- const old1 = review([legacy], { round: 1 })
230
- const old2 = review([legacy], { round: 2 })
231
- const current = review([withOpen(5)], { round: 3 })
232
- assert.equal(reviewVerdict(current, HEAD, old2, { history: [old1] }).verdict, 'fix')
233
- })
234
-
235
- test('a valid pass at or beyond the fix cap still passes, but completion and HEAD remain required', () => {
236
- for (const fixRound of [4, 5]) {
237
- const prior = review([p1], { round: 10, fixRound: fixRound - 1 })
238
- const current = review([p2, p3], { round: 11, fixRound, complete: true, passes: done })
239
- const result = reviewVerdict(current, HEAD, prior)
240
- assert.equal(result.verdict, 'pass')
241
- assert.equal(result.fixRound, fixRound)
242
- assert.equal(result.reviewedHead, HEAD)
243
- assert.throws(() => reviewVerdict({ ...current, complete: false }, HEAD, prior), /review complete must be true/)
244
- assert.throws(() => reviewVerdict({ ...current, passes: { ...done, tests: 'pending' } }, HEAD, prior), /review pass tests is unfinished/)
245
- assert.throws(() => reviewVerdict({ ...current, head: 'stale' }, HEAD, prior), /HEAD "stale"/)
246
- const integrated = review([], { round: 12, fixRound })
247
- assert.equal(reviewVerdict(integrated, HEAD, current).fixRound, fixRound)
248
- }
249
- })
250
-
251
- test('legacy attempt accounting remains conservative, including migration from a legacy prior', () => {
252
- const legacy = review([p1], { round: 3 })
253
- const result = reviewVerdict(legacy, HEAD, review([p1], { round: 2 }))
254
- assert.equal(result.verdict, 'fix')
255
- assert.equal(result.fixRound, 3)
256
- assert.equal(fixBriefOf(result).fixRound, 3)
257
- const { fixRound, ...legacyResult } = result
258
- assert.equal(fixBriefOf(legacyResult).fixRound, 3)
259
- assert.equal(reviewVerdict(review([p1], { round: 4, fixRound: 4 }), HEAD, legacy, { history: [review([p1], { round: 2 })] }).reason, 'not-converging')
260
- const legacyPass = review([p2], { round: 7 })
261
- const current = review([p1], { round: 8, fixRound: 7 })
262
- const migrated = reviewVerdict(current, HEAD, legacyPass)
263
- assert.equal(migrated.fixRound, 7)
264
- assert.equal(migrated.verdict, 'fix')
265
- assert.throws(() => reviewVerdict({ ...current, fixRound: 1 }, HEAD, legacyPass), /expected 7 from prior review/)
266
- assert.throws(() => reviewVerdict(review([p1], { round: 4, fixRound: 3 }), HEAD, legacy), /expected 4 from prior review/)
267
- })
268
-
269
- test('malformed current and prior counters are rejected by verdict, action, and brief APIs', () => {
270
- for (const value of [undefined, null, false, '1', 0, -1, 1.5, NaN, Infinity, Number.MAX_SAFE_INTEGER + 1]) {
271
- for (const field of ['round', 'fixRound']) {
272
- const current = review([p1], { fixRound: 1, [field]: value })
273
- assert.throws(() => reviewVerdict(current, HEAD), (error) => error instanceof ReviewIncomplete && /round|fixRound/.test(error.message))
274
- assert.throws(() => fixAction(current, [p1]), (error) => error instanceof ReviewIncomplete && /round|fixRound/.test(error.message))
275
- assert.throws(() => fixBriefOf(current), /round and fixRound must be positive safe integers/)
276
- const prior = review([p1], { fixRound: 1, [field]: value })
277
- assert.throws(() => reviewVerdict(review([p1], { round: 2, fixRound: 2 }), HEAD, prior), (error) =>
278
- error instanceof ReviewIncomplete && /round|fixRound/.test(error.message))
279
- }
280
- }
281
- assert.throws(() => reviewVerdict(review([], { fixRound: 2 }), HEAD), /no greater than round/)
282
- assert.throws(() => fixAction(review([p1], { fixRound: 2 }), [p1]), /no greater than round/)
283
- assert.throws(() => reviewVerdict(review([], { round: 3, fixRound: 2 }), HEAD, review([], { round: 2, fixRound: 3 })), /prior review.*fixRound|fixRound must/s)
284
- })
285
-
286
- test('supplied fix counters must match the proven transition and cannot decrease or skip cycles', () => {
287
- for (const [findings, expected] of [[[p1], 3], [[], 2], [[p2, p3], 2]]) {
288
- const prior = review(findings, { round: 8, fixRound: 2 })
289
- for (const fixRound of [1, 2, 3, 4].filter((value) => value !== expected)) {
290
- const current = review([], { round: 9, fixRound })
291
- assert.throws(() => reviewVerdict(current, HEAD, prior), new RegExp(`expected ${expected} from prior review`))
292
- assert.throws(() => fixAction(current, [p1], prior), new RegExp(`expected ${expected} from prior review`))
293
- }
294
- assert.equal(reviewVerdict(review([], { round: 9, fixRound: expected }), HEAD, prior).fixRound, expected)
295
- assert.throws(() => reviewVerdict(review([], { round: 9 }), HEAD, prior), new RegExp(`expected ${expected} from prior review`))
296
- }
297
- for (const round of [2, 9]) {
298
- assert.throws(() => reviewVerdict(review([], { round, fixRound: 1 }), HEAD), /cannot be lower than round without a prior/)
299
- assert.equal(reviewVerdict(review([p1], { round, fixRound: round }), HEAD).fixRound, round)
300
- }
301
- })
302
-
303
- test('new counters require a same-unit completed immediate prior with valid review identity', () => {
304
- const current = review([p1], { round: 9, fixRound: 2 })
305
- const prior = review([p1], { round: 8, fixRound: 1, head: 'prior-head' })
306
- const cases = [
307
- [{ unit: 'other' }, /prior review unit does not match/],
308
- [{ version: 2 }, /review version is 2/],
309
- [{ head: undefined }, /prior review has no HEAD/],
310
- [{ head: '' }, /prior review has no HEAD/],
311
- [{ round: 7 }, /must immediately precede/],
312
- [{ round: 9 }, /must immediately precede/],
313
- [{ complete: false }, /review complete must be true/],
314
- [{ passes: { ...done, tests: 'pending' } }, /review pass tests is unfinished/],
315
- [{ executionIncident: { status: 'open' } }, /executionIncident must be resolved/],
316
- [{ commands: [] }, /ran no checks/],
317
- [{ findings: undefined }, /no findings array/],
318
- [{ findings: [{ ...p1, id: '' }] }, /has no id/],
319
- [{ findings: [{ ...p2, followUpUrl: undefined }] }, /without a followUpUrl/],
320
- ]
321
- for (const [extra, message] of cases) {
322
- const invalid = { ...prior, ...extra }
323
- assert.throws(() => reviewVerdict(current, HEAD, invalid), message)
324
- assert.throws(() => fixAction(current, [p1], invalid), message)
325
- }
326
- for (const invalid of [{}, [], false, 'prior']) {
327
- assert.throws(() => reviewVerdict(current, HEAD, invalid), /invalid prior review for fixRound/)
328
- }
329
- assert.equal(reviewVerdict(current, HEAD, prior).action, 'test-first')
330
- })
331
-
332
- test('only a completed full retry can recover from an incomplete immediate prior', () => {
333
- const prior = review([p1], { fixRound: 1, complete: false, passes: { ...done, tests: 'partial' }, commands: [] })
334
- const current = review([], { round: 2, fixRound: 1, scope: 'full', complete: true, passes: done })
335
- assert.equal(reviewVerdict(current, HEAD, prior).verdict, 'pass')
336
- assert.equal(reviewVerdict(current, HEAD, prior).fixRound, 1)
337
- assert.equal(reviewVerdict({ ...current, findings: [{ ...p1, pass: 'spec' }] }, HEAD, prior).action, 'fix')
338
- assert.throws(() => reviewVerdict({ ...current, scope: 'delta' }, HEAD, prior), /invalid prior review/)
339
- const { passes, ...withoutCoverage } = current
340
- assert.throws(() => reviewVerdict(withoutCoverage, HEAD, prior), /full retry must complete every pass/)
341
- assert.throws(() => reviewVerdict({ ...current, fixRound: 2 }, HEAD, prior), /expected 1 from prior/)
342
- assert.throws(() => reviewVerdict(current, HEAD, { ...prior, unit: 'other' }), /unit does not match/)
343
- assert.throws(() => reviewVerdict(current, HEAD, { ...prior, fixRound: 0 }), /fixRound must/)
344
- })
345
-
346
- test('full retry requires pass coverage when prior checks or follow-ups are incomplete', () => {
347
- for (const extra of [{ commands: [] }, { findings: [p1, { ...p2, followUpUrl: undefined }] }]) {
348
- const prior = review([p1], { round: 3, fixRound: 3, passes: done, ...extra })
349
- assert.throws(() => reviewVerdict(prior, HEAD), ReviewIncomplete)
350
- const current = review([p1], { round: 4, fixRound: 3, scope: 'full', passes: done })
351
- assert.equal(reviewVerdict(current, HEAD, prior).verdict, 'fix')
352
- assert.equal(reviewVerdict(current, HEAD, prior).fixRound, 3)
353
- const { passes, ...withoutCoverage } = current
354
- assert.throws(() => reviewVerdict(withoutCoverage, HEAD, prior), /full retry must complete every pass/)
355
- assert.throws(() => reviewVerdict({ ...current, scope: 'delta' }, HEAD, prior), /invalid prior review/)
356
- }
357
- })
358
-
359
- test('a P0/P1 that survives a fix is fixed test-first', () => {
360
- const prior = review([p1], { round: 1 })
361
- const result = reviewVerdict(review([p1], { round: 2 }), HEAD, prior)
362
- assert.equal(result.verdict, 'fix')
363
- assert.equal(result.action, 'test-first')
364
- assert.deepEqual(result.recurring, ['R1'])
365
- const brief = fixBriefOf(result)
366
- assert.match(brief.instruction, /failing test|see it fail/)
367
- assert.match(brief.instruction, /every code path/)
368
- assert.deepEqual(brief.recurring, ['R1'])
369
- })
370
-
371
- test('only new P0/P1 after a fix are fixed one by one', () => {
372
- const prior = review([p1], { round: 2 })
373
- const fresh = { ...p1, id: 'R4', summary: 'fix dropped the currency on refunds' }
374
- const result = reviewVerdict(review([fresh, p2], { round: 3 }), HEAD, prior)
375
- assert.equal(result.action, 'one-by-one')
376
- assert.deepEqual(result.introduced, ['R4'])
377
- assert.deepEqual(fixBriefOf(result).fix.map((f) => f.id), ['R4'])
378
- })
379
-
380
- test('a recurring finding wins over new ones: test-first', () => {
381
- const prior = review([p1], { round: 1 })
382
- const fresh = { ...p1, id: 'R4' }
383
- const result = reviewVerdict(review([p1, fresh], { round: 2 }), HEAD, prior)
384
- assert.equal(result.action, 'test-first')
385
- assert.deepEqual(result.recurring, ['R1'])
386
- assert.deepEqual(result.introduced, ['R4'])
387
- })
388
-
389
- test('a recurring spec finding with the same trigger and open paths is a decision, not a handback', () => {
390
- const spec = { ...p1, pass: 'spec', summary: 'refund must also void the receipt' }
391
- const result = reviewVerdict(review([spec], { round: 2 }), HEAD, review([spec]))
392
- assert.equal(result.verdict, 'decide')
393
- assert.equal(result.reason, 'spec-question')
394
- assert.deepEqual(result.question.map((q) => [q.id, q.rule, q.trigger]), [['R1', spec.rule, spec.trigger]])
395
- // Whitespace in the trigger is not a new trigger.
396
- const reflowed = { ...spec, trigger: ` ${spec.trigger.replaceAll(' ', '\n ')}` }
397
- assert.equal(reviewVerdict(review([reflowed], { round: 2 }), HEAD, review([spec])).verdict, 'decide')
398
- // A new spec finding is still an ordinary fix.
399
- assert.equal(reviewVerdict(review([spec], { round: 2 }), HEAD, review([])).verdict, 'fix')
400
- })
401
-
402
- test('a recurring spec finding with a new trigger on the same line is an incomplete fix (agentier #2521)', () => {
403
- // Round 1: duplicate scalar selectors bypassed the refusal at router.ts:313. The fix closed
404
- // that; round 2 found bracket-plus-scalar selectors bypassing the same line. Same id, same
405
- // open path, new trigger: the rule is clear and the fix missed a path, so fix it test-first.
406
- const rows = (status) => [{ path: 'apps/control-api/src/skills/router.ts:313', status: 'open' }, { path: 'apps/dashboard/impl.ts:56', ...status }]
407
- const r1 = { ...p1, pass: 'spec', rule: 'a conflicting supplied org selector refuses deletion', trigger: 'DELETE ?orgId=org-a&orgId=org-b returns 204', paths: rows({ status: 'open' }) }
408
- const r2 = { ...r1, trigger: 'DELETE ?orgId[x]=org-a&orgId=org-b returns 204', paths: rows({ status: 'closed', evidence: 'getAll at :56' }) }
409
- const result = reviewVerdict(review([r2], { round: 2 }), HEAD, review([r1]))
410
- assert.equal(result.verdict, 'fix')
411
- assert.equal(result.action, 'test-first')
412
- assert.deepEqual(result.recurring, ['R1'])
413
- })
414
-
415
- test('--decision turns a decide round into a test-first fix that carries the decision, past the stall guard', () => {
416
- const spec = { ...p1, pass: 'spec' }
417
- const history = [review([spec])]
418
- const r2 = review([spec], { round: 2 })
419
- const r3 = review([spec], { round: 3 })
420
- // Without a decision, round 3 has gone two rounds without a new low.
421
- assert.equal(reviewVerdict(r3, HEAD, r2, { history }).reason, 'not-converging')
422
- const decided = reviewVerdict(r3, HEAD, r2, { history, decision: 'd-7' })
423
- assert.equal(decided.verdict, 'fix')
424
- assert.equal(decided.action, 'test-first')
425
- const brief = fixBriefOf(decided)
426
- assert.equal(brief.decision, 'd-7')
427
- assert.match(brief.instruction, /Decision d-7 in the plan's `decisions` settles what the recurring spec finding requires/)
428
- // A decision with nothing to decide changes nothing.
429
- const ordinary = reviewVerdict(review([p1], { round: 2 }), HEAD, review([{ ...p1, id: 'R9' }]), { decision: 'd-7' })
430
- assert.equal(ordinary.action, 'one-by-one')
431
- assert.equal(fixBriefOf(ordinary).decision, undefined)
432
- })
433
-
434
- test('CLI exits 6 on decide with a decide file and no fix brief; --decision writes the brief', (t) => {
435
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-decide-'))
436
- t.after(() => rmSync(dir, { recursive: true, force: true }))
437
- const write = (name, value) => { const p = join(dir, name); writeFileSync(p, JSON.stringify(value)); return p }
438
- const spec = { ...p1, pass: 'spec', requestedAction: 'refuse before deleting' }
439
- write('1201-r1.json', review([spec]))
440
- const path = write('1201-r2.json', review([spec], { round: 2 }))
441
- const run = (extra = []) => spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', HEAD, ...extra], { encoding: 'utf8' })
442
- const ask = run()
443
- assert.equal(ask.status, 6, ask.stderr)
444
- const out = JSON.parse(ask.stdout)
445
- assert.equal(out.decide, join(dir, '1201-r2.decide.json'))
446
- const file = JSON.parse(readFileSync(out.decide, 'utf8'))
447
- assert.deepEqual([file.unit, file.round, file.head, file.reason, file.question[0].requestedAction], ['1201', 2, HEAD, 'spec-question', 'refuse before deleting'])
448
- assert.equal(existsSync(join(dir, '1201-r2.fix.json')), false)
449
- const fix = run(['--decision', 'd-1'])
450
- assert.equal(fix.status, 1, fix.stderr)
451
- assert.equal(JSON.parse(readFileSync(join(dir, '1201-r2.fix.json'), 'utf8')).decision, 'd-1')
452
- })
453
-
454
- test('a recurring spec finding with new open paths is an incomplete fix, not a question', () => {
455
- const before = { ...p1, pass: 'spec', paths: [{ path: 'src/refund.ts:12', status: 'open' }] }
456
- const after = { ...before, paths: [{ path: 'src/refund.ts:12', status: 'closed', evidence: 'voidReceipt at :12' }, { path: 'src/batch-refund.ts:30', status: 'open' }] }
457
- const result = reviewVerdict(review([after], { round: 2 }), HEAD, review([before]))
458
- assert.equal(result.verdict, 'fix')
459
- assert.equal(result.action, 'test-first')
460
- })
461
-
462
- test('a P0/P1 without trigger, rule, or an open paths row is an incomplete review', () => {
463
- const { rule, ...noRule } = p1
464
- const { paths, ...noPaths } = p1
465
- const { trigger, ...noTrigger } = p1
466
- const allClosed = { ...p1, paths: [{ path: 'src/a.ts:10', status: 'closed', evidence: 'fixed at :10' }] }
467
- const closedNoEvidence = { ...p1, paths: [{ path: 'src/a.ts:10', status: 'open' }, { path: 'src/b.ts:4', status: 'closed' }] }
468
- for (const [name, finding, message] of [
469
- ['no rule', noRule, /has no rule/], ['no paths', noPaths, /has no paths rows/], ['no trigger', noTrigger, /has no trigger/],
470
- ['every row closed', allClosed, /no open paths row/], ['closed row without evidence', closedNoEvidence, /closed without evidence/],
471
- ]) {
472
- assert.throws(() => reviewVerdict(review([finding]), HEAD), (error) => error instanceof ReviewIncomplete && message.test(error.message), name)
473
- }
474
- // P2/P3 need none of them, and an earlier record without them still reads as a prior.
475
- assert.equal(reviewVerdict(review([p2, p3]), HEAD).verdict, 'pass')
476
- assert.equal(reviewVerdict(review([p1], { round: 2 }), HEAD, review([noPaths])).action, 'test-first')
477
- })
478
-
479
- test('two stalled rounds hand back at any round; P2/P3 alone still pass', () => {
480
- assert.equal(reviewVerdict(review([p1], { round: 4 }), HEAD, review([p1], { round: 3 })).verdict, 'fix')
481
- const result = reviewVerdict(review([p1], { round: 4 }), HEAD, review([p1], { round: 3 }), { history: [review([p1], { round: 2 })] })
482
- assert.equal(result.verdict, 'handback')
483
- assert.equal(result.reason, 'not-converging')
484
- assert.deepEqual(result.fix.map((f) => f.id), ['R1'])
485
- assert.equal(reviewVerdict(review([p2], { round: 5 }), HEAD).verdict, 'pass')
486
- })
487
-
488
- test('CLI reads the previous round beside the review, exits 1 test-first, and 4 on hand back without a fix brief', (t) => {
489
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-rounds-'))
490
- t.after(() => rmSync(dir, { recursive: true, force: true }))
491
- const write = (name, value) => { const p = join(dir, name); writeFileSync(p, JSON.stringify(value)); return p }
492
- const run = (path, extra = []) => spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', HEAD, ...extra], { encoding: 'utf8' })
493
- write('1201-r1.json', review([p1]))
494
- const again = run(write('1201-r2.json', review([p1], { round: 2 })))
495
- assert.equal(again.status, 1, again.stderr)
496
- const out = JSON.parse(again.stdout)
497
- assert.equal(out.action, 'test-first')
498
- assert.equal(JSON.parse(readFileSync(out.fixBrief, 'utf8')).action, 'test-first')
499
- const explicit = run(write('later.json', review([p1], { round: 2 })), ['--prior', join(dir, '1201-r1.json')])
500
- assert.equal(JSON.parse(explicit.stdout).action, 'test-first')
501
- const stop = run(write('1201-r3.json', review([p1], { round: 3 })))
502
- assert.equal(stop.status, 4, stop.stderr)
503
- assert.equal(JSON.parse(stop.stdout).reason, 'not-converging')
504
- assert.equal(existsSync(join(dir, '1201-r3.fix.json')), false)
505
- const named = run(write('next.json', review([p1], { round: 3 })), ['--prior', join(dir, '1201-r2.json')])
506
- assert.equal(named.status, 4, named.stderr)
507
- // Zero-padded names number earlier rounds with the same width.
508
- for (const n of [1, 2]) write(`1201-r0${n}.json`, review([p1], { round: n }))
509
- assert.equal(run(write('1201-r03.json', review([p1], { round: 3 }))).status, 4)
510
- // The guard fails closed: a missing earlier round or an unnumbered name is an incomplete review.
511
- rmSync(join(dir, '1201-r1.json'))
512
- assert.equal(run(join(dir, '1201-r3.json')).status, 3)
513
- const unnamed = run(write('cur.json', review([p1], { round: 3 })), ['--prior', write('prev.json', review([p1], { round: 2 }))])
514
- assert.equal(unnamed.status, 3)
515
- assert.match(unnamed.stdout, /name review files/)
516
- })
517
-
518
- test('CLI explicit and inferred priors prove low fix rounds at high attempt numbers and hand back two stalled rounds', (t) => {
519
- for (const explicit of [false, true]) {
520
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-fix-cycles-'))
521
- t.after(() => rmSync(dir, { recursive: true, force: true }))
522
- const write = (round, record) => {
523
- const path = join(dir, `1201-r${round}.json`)
524
- writeFileSync(path, JSON.stringify(record))
525
- return path
526
- }
527
- const run = (round, record) => spawnSync(process.execPath, [script, '--exit-verdict',
528
- '--review', write(round, record), '--head', HEAD,
529
- ...(explicit ? ['--prior', join(dir, `1201-r${round - 1}.json`)] : []),
530
- '--max-rounds', '100',
531
- ], { encoding: 'utf8' })
532
- for (let round = 1; round <= 8; round++) write(round, review([p2], { round, fixRound: 1 }))
533
- const first = run(9, review([p1, p2], { round: 9, fixRound: 1 }))
534
- assert.equal(first.status, 1, first.stderr)
535
- const firstOut = JSON.parse(first.stdout)
536
- assert.equal(firstOut.action, 'fix')
537
- assert.equal(firstOut.round, 9)
538
- assert.equal(firstOut.fixRound, 1)
539
- const brief = JSON.parse(readFileSync(firstOut.fixBrief, 'utf8'))
540
- assert.equal(brief.fixRound, 1)
541
- assert.deepEqual(brief.fix.map((finding) => finding.id), ['R1'])
542
- const second = run(10, review([p1], { round: 10, fixRound: 2 }))
543
- assert.equal(second.status, 1, second.stderr)
544
- assert.equal(JSON.parse(second.stdout).action, 'test-first')
545
- const third = run(11, review([p1], { round: 11, fixRound: 3 }))
546
- assert.equal(third.status, 4, third.stderr)
547
- assert.equal(JSON.parse(third.stdout).reason, 'not-converging')
548
- assert.equal(existsSync(join(dir, '1201-r11.fix.json')), false)
549
- const stopped = run(12, review([p1], { round: 12, fixRound: 4 }))
550
- assert.equal(stopped.status, 4, stopped.stderr)
551
- assert.equal(JSON.parse(stopped.stdout).reason, 'not-converging')
552
- const passed = run(12, review([p2], { round: 12, fixRound: 4 }))
553
- assert.equal(passed.status, 0, passed.stderr)
554
- assert.equal(JSON.parse(passed.stdout).reviewedHead, HEAD)
555
- assert.equal(JSON.parse(passed.stdout).fixRound, 4)
556
- const integration = run(13, review([], { round: 13, fixRound: 4 }))
557
- assert.equal(integration.status, 0, integration.stderr)
558
- assert.equal(JSON.parse(integration.stdout).fixRound, 4)
559
- }
560
- })
561
-
562
- test('CLI counter and prior refusals preserve reviews and existing briefs with no verdict output', (t) => {
563
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-counter-refusals-'))
564
- t.after(() => rmSync(dir, { recursive: true, force: true }))
565
- const path = join(dir, '1201-r9.json')
566
- const priorPath = join(dir, '1201-r8.json')
567
- const briefPath = join(dir, '1201-r9.fix.json')
568
- const current = review([p1], { round: 9, fixRound: 2 })
569
- const prior = review([p1], { round: 8, fixRound: 1 })
570
- const cases = [
571
- [current, '{not json', /cannot use prior review for fixRound/],
572
- [current, null, /prior review must be an object/],
573
- [current, [], /prior review must be an object/],
574
- [current, { ...prior, unit: 'other' }, /prior review unit does not match/],
575
- [current, { ...prior, version: 2 }, /review version is 2/],
576
- [current, { ...prior, round: 7 }, /must immediately precede/],
577
- [current, { ...prior, complete: false }, /review complete must be true/],
578
- [current, { ...prior, fixRound: 0 }, /fixRound must be a positive/],
579
- [current, { ...prior, fixRound: 1.5 }, /fixRound must be a positive/],
580
- [current, { ...prior, fixRound: '1' }, /fixRound must be a positive/],
581
- [{ ...current, fixRound: 0 }, prior, /fixRound must be a positive/],
582
- [{ ...current, fixRound: -1 }, prior, /fixRound must be a positive/],
583
- [{ ...current, fixRound: 1.5 }, prior, /fixRound must be a positive/],
584
- [{ ...current, fixRound: null }, prior, /fixRound must be a positive/],
585
- [{ ...current, fixRound: 1 }, prior, /expected 2 from prior review/],
586
- [{ ...current, fixRound: 3 }, prior, /expected 2 from prior review/],
587
- [current, { ...prior, findings: [] }, /expected 1 from prior review/],
588
- ]
589
- writeFileSync(briefPath, 'existing brief\n')
590
- for (const explicit of [false, true]) {
591
- for (const [record, previous, message] of cases) {
592
- const bytes = JSON.stringify(record)
593
- const priorBytes = typeof previous === 'string' ? previous : JSON.stringify(previous)
594
- writeFileSync(path, bytes)
595
- writeFileSync(priorPath, priorBytes)
596
- const result = spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', HEAD,
597
- ...(explicit ? ['--prior', priorPath] : []),
598
- ], { encoding: 'utf8' })
599
- assert.equal(result.status, 3, result.stderr)
600
- assert.equal(JSON.parse(result.stdout).verdict, 'review-again')
601
- assert.match(result.stdout, message)
602
- assert.equal(readFileSync(path, 'utf8'), bytes)
603
- assert.equal(readFileSync(priorPath, 'utf8'), priorBytes)
604
- assert.equal(readFileSync(briefPath, 'utf8'), 'existing brief\n')
605
- }
606
- }
607
- rmSync(priorPath)
608
- for (const explicit of [false, true]) {
609
- const bytes = JSON.stringify(current)
610
- writeFileSync(path, bytes)
611
- const result = spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', HEAD,
612
- ...(explicit ? ['--prior', priorPath] : []),
613
- ], { encoding: 'utf8' })
614
- assert.equal(result.status, 3, result.stderr)
615
- assert.equal(JSON.parse(result.stdout).verdict, 'review-again')
616
- assert.match(result.stdout, explicit ? /cannot use prior review/ : /cannot be lower than round without a prior/)
617
- assert.equal(readFileSync(path, 'utf8'), bytes)
618
- assert.equal(readFileSync(briefPath, 'utf8'), 'existing brief\n')
619
- }
620
- })
621
-
622
- test('a P2 in the dropbox counts as disposed and passes with its dropbox id', () => {
623
- const result = reviewVerdict(review([{ ...p2, followUpUrl: undefined, dropboxId: 'D4' }]), HEAD)
624
- assert.equal(result.verdict, 'pass')
625
- assert.deepEqual(result.deferred.map((f) => [f.id, f.dropboxId]), [['R2', 'D4']])
626
- assert.throws(() => reviewVerdict(review([{ ...p2, followUpUrl: undefined, dropboxId: 'later' }]), HEAD), /R2 is P2 without a followUpUrl or dropboxId/)
627
- })
628
-
629
- test('an incomplete review is sent back to the reviewer', () => {
630
- const incomplete = (value, message) => assert.throws(() => reviewVerdict(value, HEAD), (e) => e instanceof ReviewIncomplete && message.test(e.message))
631
- incomplete(review([], { commands: [] }), /ran no checks/)
632
- incomplete(review([], { commands: [' '] }), /ran no checks/)
633
- incomplete(review([{ ...p2, followUpUrl: undefined }]), /R2 is P2 without a followUpUrl/)
634
- incomplete(review([{ ...p1, severity: 'high' }]), /severity "high"/)
635
- incomplete(review([p1, { ...p2, id: 'R1' }]), /duplicate finding id R1/)
636
- incomplete(review([], { head: 'old0000' }), /HEAD "old0000", not abc1234/)
637
- incomplete(review([], { version: 2 }), /version is 2/)
638
- incomplete(review(undefined), /no findings array/)
639
- })
640
-
641
- test('a missing --head is a command error', () => {
642
- assert.throws(() => reviewVerdict(review([]), ''), (e) => e instanceof ReviewVerdictError && !(e instanceof ReviewIncomplete))
643
- })
644
-
645
- test('CLI exits 0 on pass, 1 on fix with a P0/P1-only brief, 2 on a bad command, 3 on an incomplete review', (t) => {
646
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-'))
647
- t.after(() => rmSync(dir, { recursive: true, force: true }))
648
- const write = (name, value) => { const p = join(dir, name); writeFileSync(p, typeof value === 'string' ? value : JSON.stringify(value)); return p }
649
- const run = (path, head = HEAD) => spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', head], { encoding: 'utf8' })
650
-
651
- const pass = run(write('1201-r1.json', review([p2])))
652
- assert.equal(pass.status, 0, pass.stderr)
653
- assert.equal(JSON.parse(pass.stdout).verdict, 'pass')
654
- assert.equal(existsSync(join(dir, '1201-r1.fix.json')), false)
655
-
656
- const complete = run(write('1201-complete.json', review([p2, p3], {
657
- complete: true,
658
- passes: { ...done, spec: 'skipped: no spec found' },
659
- executionIncident: { status: 'resolved' },
660
- commands: ['npm test (failed)', 'npm test (corrected)'],
661
- commandResults: [{ exitCode: 1 }, { exitCode: 0 }],
662
- })))
663
- assert.equal(complete.status, 0, complete.stderr)
664
- const completed = JSON.parse(complete.stdout)
665
- assert.equal(completed.reviewedHead, HEAD)
666
- assert.deepEqual(completed.deferred.map((finding) => finding.id), ['R2'])
667
- assert.deepEqual(completed.commands, ['npm test (failed)', 'npm test (corrected)'])
668
- assert.equal(existsSync(join(dir, '1201-complete.fix.json')), false)
669
-
670
- for (const passes of [
671
- done,
672
- { ...done, spec: 'skipped: no spec found', standards: 'skipped: no standards found' },
673
- ...skipPrefixes.map((prefix) => ({ ...done, tests: `${prefix} documentation-only diff` })),
674
- { ...done, tests: 'skipped: not applicable' },
675
- { ...done, tests: 'skipped: non-applicable' },
676
- ]) {
677
- const compatible = run(write('compatible.json', review([], { passes })))
678
- assert.equal(compatible.status, 0, compatible.stderr)
679
- assert.equal(JSON.parse(compatible.stdout).reviewedHead, HEAD)
680
- assert.equal(existsSync(join(dir, 'compatible.fix.json')), false)
681
- }
682
-
683
- const fix = run(write('1201-r2.json', review([p1, p2], { round: 2 })))
684
- assert.equal(fix.status, 1, fix.stderr)
685
- const out = JSON.parse(fix.stdout)
686
- assert.equal(out.fixBrief, join(dir, '1201-r2.fix.json'))
687
- assert.deepEqual(JSON.parse(readFileSync(out.fixBrief, 'utf8')).fix.map((f) => f.id), ['R1'])
688
-
689
- assert.equal(run(join(dir, 'missing.json')).status, 2)
690
- assert.equal(spawnSync(process.execPath, [script, '--exit-verdict', '--review', join(dir, '1201-r1.json')], { encoding: 'utf8' }).status, 2)
691
- assert.equal(run(write('bad.json', '{not json')).status, 3)
692
- const stale = run(join(dir, '1201-r1.json'), 'new9999')
693
- assert.equal(stale.status, 3)
694
- assert.match(stale.stdout, /spawn the reviewer again/)
695
- })
696
-
697
- test('CLI refuses interrupted review records without emitting delivery authority, changing input, or writing a fix brief', (t) => {
698
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-incomplete-'))
699
- t.after(() => rmSync(dir, { recursive: true, force: true }))
700
- const incidentHead = 'f2a24bac863e3e7760afebe50e0e6f2c35e2fea8'
701
- const interrupted = review([], {
702
- head: incidentHead,
703
- complete: false,
704
- commands: ['node vitest.mjs run --config android/vitest.config.ts', 'git diff --check'],
705
- passes: Object.fromEntries(Object.keys(done).map((pass) => [pass, 'skipped: unfinished; stopped after reviewer execution-scope incident'])),
706
- executionIncident: { status: 'open-impact-unresolved', commandIndex: 0 },
707
- })
708
- const cases = [
709
- interrupted,
710
- review([], { complete: false }),
711
- review([], { passes: { ...done, spec: 'skipped: unfinished' } }),
712
- review([], { executionIncident: { status: 'open-impact-unresolved' } }),
713
- review([p1], { complete: false }),
714
- ]
715
- for (const [index, record] of cases.entries()) {
716
- const path = join(dir, `incomplete-${index}.json`)
717
- const bytes = `${JSON.stringify(record, null, 2)}\n`
718
- writeFileSync(path, bytes)
719
- const result = spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', record.head], { encoding: 'utf8' })
720
- assert.equal(result.status, 3, result.stderr)
721
- assert.equal(JSON.parse(result.stdout).verdict, 'review-again')
722
- assert.match(result.stdout, /incomplete review/)
723
- assert.equal(existsSync(join(dir, `incomplete-${index}.fix.json`)), false)
724
- assert.equal(readFileSync(path, 'utf8'), bytes)
725
- }
726
- })
727
-
728
- test('CLI refuses contradictory accepted-prefix skips before severity disposition and preserves artifacts', (t) => {
729
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-contradictory-'))
730
- t.after(() => rmSync(dir, { recursive: true, force: true }))
731
- const path = join(dir, 'review.json')
732
- const briefPath = join(dir, 'review.fix.json')
733
- const retainedBrief = 'retained prior fix brief\n'
734
- let index = 0
735
- for (const prefix of skipPrefixes) {
736
- for (const reason of incompleteReasons) {
737
- const pass = Object.keys(done)[index % 4]
738
- const findings = [[], [p1], [p2, p3]][index % 3]
739
- const record = review(findings, { complete: true, passes: { ...done, [pass]: `${prefix} ${reason}` } })
740
- const bytes = `${JSON.stringify(record, null, 2)}\n`
741
- writeFileSync(path, bytes)
742
- if (index === 1) writeFileSync(briefPath, retainedBrief)
743
- const result = spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', HEAD], { encoding: 'utf8' })
744
- assert.equal(result.status, 3, `${prefix} ${reason}: ${result.stderr}`)
745
- assert.equal(JSON.parse(result.stdout).verdict, 'review-again')
746
- assert.match(result.stdout, new RegExp(`review pass ${pass} is unfinished`))
747
- assert.equal(readFileSync(path, 'utf8'), bytes)
748
- if (index === 0) assert.equal(existsSync(briefPath), false)
749
- else assert.equal(readFileSync(briefPath, 'utf8'), retainedBrief)
750
- index += 1
751
- }
752
- }
753
- })
754
-
755
- test('a skip record passes only when the scope rerun still picks skip', () => {
756
- const prior = review([p2], { head: 'old1234', fixRound: 1, complete: true, passes: done })
757
- const skip = review([], { round: 2, fixRound: 1, tier: 'skip', carriedFrom: 'old1234', complete: true, passes: done, commands: [] })
758
- const confirm = (tier, reasons = []) => () => ({ tier, reasons })
759
- const passed = reviewVerdict(skip, HEAD, prior, { confirmSkip: confirm('skip') })
760
- assert.equal(passed.verdict, 'pass')
761
- assert.equal(passed.reviewedHead, HEAD)
762
- assert.equal(passed.tier, 'skip')
763
- assert.equal(passed.carriedFrom, 'old1234')
764
- assert.deepEqual(passed.deferred, [], 'carried P2/P3 already have their follow-ups')
765
- assert.throws(() => reviewVerdict(skip, HEAD, prior, { confirmSkip: confirm('delta', ['b.txt moved under the PR']) }), /picks "delta"[\s\S]*b\.txt moved/)
766
- assert.throws(() => reviewVerdict(skip, HEAD, prior), (e) => e instanceof ReviewVerdictError && !(e instanceof ReviewIncomplete) && /--base is required/.test(e.message))
767
- assert.throws(() => reviewVerdict(skip, HEAD, null, { confirmSkip: confirm('skip') }), /the review it carries is missing/)
768
- assert.throws(() => reviewVerdict({ ...skip, carriedFrom: 'other' }, HEAD, prior, { confirmSkip: confirm('skip') }), /carries "other"/)
769
- assert.throws(() => reviewVerdict({ ...skip, findings: [p1] }, HEAD, prior, { confirmSkip: confirm('skip') }), /cannot hold findings/)
770
- assert.throws(() => reviewVerdict(review([], { tier: 'delta', commands: [] }), HEAD), /ran no checks/, 'only a skip is exempt from running checks')
771
- })
772
-
773
- test('tier and the legacy scope field are read the same way', () => {
774
- const prior = review([p1], { fixRound: 1, complete: false, passes: { ...done, tests: 'partial' }, commands: [] })
775
- const current = review([], { round: 2, fixRound: 1, tier: 'full', complete: true, passes: done })
776
- assert.equal(reviewVerdict(current, HEAD, prior).verdict, 'pass')
777
- assert.throws(() => reviewVerdict({ ...current, tier: 'delta' }, HEAD, prior), /invalid prior review/)
778
- })
779
-
780
- test('every link of a skip chain must hold back to a real review', () => {
781
- const confirmSkip = () => ({ tier: 'skip', reasons: [] })
782
- const root = review([], { head: 'h1', fixRound: 1, complete: true, passes: done })
783
- const link = (round, carriedFrom, extra = {}) => review([], { round, fixRound: 1, head: `h${round}`, tier: 'skip', carriedFrom, complete: true, passes: done, commands: [], ...extra })
784
- const r2 = link(2, 'h1')
785
- const r3 = { ...link(3, 'h2'), head: HEAD }
786
- assert.equal(reviewVerdict(r3, HEAD, r2, { confirmSkip, earlier: [root] }).verdict, 'pass', 'an honest skip -> skip chain passes')
787
- assert.throws(() => reviewVerdict(r3, HEAD, r2, { confirmSkip }), /round 2: the review it carries is missing/, 'a chain that cannot be walked fails closed')
788
-
789
- // Codex F1: a forged skip prior that carries an approval that never existed.
790
- const forged = link(2, 'no-such-approval')
791
- assert.throws(() => reviewVerdict(r3, HEAD, forged, { confirmSkip, earlier: [root] }), /round 2: it carries "no-such-approval"/)
792
- assert.throws(() => reviewVerdict(r3, HEAD, r2, { confirmSkip, earlier: [{ ...root, commands: [] }] }), /ran no checks/, 'the chain must end at a review that ran checks')
793
- assert.throws(() => reviewVerdict(r3, HEAD, r2, { confirmSkip, earlier: [{ ...root, findings: [p1] }] }), /open P0\/P1/)
794
-
795
- // Codex F2: dropping fixRound must not skip the unit and round checks.
796
- const { fixRound: _a, ...legacyRoot } = root
797
- const { fixRound: _b, ...stripped } = { ...link(2, 'h1'), head: HEAD }
798
- assert.throws(() => reviewVerdict({ ...stripped, unit: 'other', round: 99 }, HEAD, legacyRoot, { confirmSkip }), /unit differs[\s\S]*round does not follow/)
799
- assert.throws(() => reviewVerdict(stripped, HEAD, legacyRoot, { confirmSkip }), /fixRound is undefined/)
800
- const { findings: _c, ...noFindings } = root
801
- assert.throws(() => reviewVerdict({ ...link(2, 'h1'), head: HEAD }, HEAD, noFindings, { confirmSkip }), /no findings array/)
802
- })
803
-
804
- test('the verdict CLI walks a skip chain by filename and fails closed when it cannot', () => {
805
- const dir = mkdtempSync(join(tmpdir(), 'verdict-chain-'))
806
- try {
807
- const base = { version: 1, unit: '1201', complete: true, passes: done, findings: [] }
808
- writeFileSync(join(dir, '1201-r1.json'), JSON.stringify({ ...base, round: 1, fixRound: 1, head: 'h1', commands: ['npm test'] }))
809
- writeFileSync(join(dir, '1201-r2.json'), JSON.stringify({ ...base, round: 2, fixRound: 1, head: 'h2', tier: 'skip', carriedFrom: 'h1', commands: [] }))
810
- writeFileSync(join(dir, '1201-r3.json'), JSON.stringify({ ...base, round: 3, fixRound: 1, head: 'h3', tier: 'skip', carriedFrom: 'h2', commands: [] }))
811
- const run = () => spawnSync('node', [script, '--exit-verdict', '--review', join(dir, '1201-r3.json'), '--head', 'h3', '--base', 'main'], { cwd: dir, encoding: 'utf8' })
812
- // The chain holds, so the check reaches the git rerun, which has no repository here: exit 2.
813
- assert.equal(run().status, 2, run().stderr)
814
- writeFileSync(join(dir, '1201-r1.json'), JSON.stringify({ ...base, round: 1, fixRound: 1, head: 'h1', commands: [] }))
815
- const broken = run()
816
- assert.equal(broken.status, 3)
817
- assert.match(broken.stdout, /round 2: the review it carries: the reviewer ran no checks/)
818
- } finally {
819
- rmSync(dir, { recursive: true, force: true })
820
- }
821
- })
822
-
823
- const ghP1 = { id: 'gh-review-11', severity: 'P1', pass: 'external', source: 'github', author: 'vera932', summary: 'refund rounds the wrong way', trigger: "vera932's changes request on GitHub: u", rule: 'blocking review feedback on the PR is resolved before merge', paths: [{ path: 'u', status: 'open' }] }
824
-
825
- test('a GitHub finding blocks like the reviewer\'s and reaches the fix brief', () => {
826
- const result = reviewVerdict(review([ghP1, p2]), HEAD)
827
- assert.equal(result.verdict, 'fix')
828
- assert.deepEqual(result.fix.map((f) => f.id), ['gh-review-11'])
829
- assert.equal(fixBriefOf(result).fix[0].source, 'github')
830
- })
831
-
832
- test('a re-review still awaited waits instead of passing; a blocker still fixes first', () => {
833
- const awaiting = [{ id: 'gh-review-11', author: 'vera932', url: 'https://github.com/o/r/pull/1#pullrequestreview-11' }]
834
- const waiting = reviewVerdict(review([p2], { external: { awaiting } }), HEAD)
835
- assert.equal(waiting.verdict, 'wait')
836
- assert.equal(waiting.reviewedHead, undefined, 'a wait records no reviewed HEAD')
837
- assert.deepEqual(waiting.awaiting, awaiting)
838
- const fixing = reviewVerdict(review([p1], { external: { awaiting } }), HEAD)
839
- assert.equal(fixing.verdict, 'fix')
840
- assert.deepEqual(fixing.awaiting, awaiting)
841
- assert.equal(reviewVerdict(review([], { external: { awaiting: [] } }), HEAD).verdict, 'pass')
842
- })
843
-
844
- test('a skip record may carry GitHub findings, never the reviewer\'s', () => {
845
- const prior = review([], { complete: true, passes: done, head: 'old1234' })
846
- const skip = review([ghP1], { round: 2, fixRound: 1, tier: 'skip', carriedFrom: 'old1234', complete: true, passes: done, commands: [] })
847
- const confirmSkip = () => ({ tier: 'skip', reasons: [] })
848
- const result = reviewVerdict(skip, HEAD, prior, { confirmSkip })
849
- assert.equal(result.verdict, 'fix')
850
- assert.deepEqual(result.fix.map((f) => f.id), ['gh-review-11'])
851
- assert.throws(() => reviewVerdict({ ...skip, findings: [ghP1, p1] }, HEAD, prior, { confirmSkip }), /cannot hold findings/)
852
- })
853
-
854
- test('CLI exits 5 while a re-review is awaited', (t) => {
855
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-wait-'))
856
- t.after(() => rmSync(dir, { recursive: true, force: true }))
857
- const path = join(dir, '1201-r1.json')
858
- writeFileSync(path, JSON.stringify(review([], { external: { awaiting: [{ id: 'gh-review-11', author: 'vera932' }] } })))
859
- const run = spawnSync(process.execPath, [script, '--exit-verdict', '--review', path, '--head', HEAD], { encoding: 'utf8' })
860
- assert.equal(run.status, 5, run.stderr)
861
- assert.equal(JSON.parse(run.stdout).verdict, 'wait')
862
- })
863
-
864
- test('no finding is exempt from the blocking shape, whatever it claims', () => {
865
- const forged = { id: 'R1', severity: 'P1', summary: 'real local blocker', source: 'github' }
866
- assert.throws(() => reviewVerdict(review([forged], { external: { added: ['R1'] } }), HEAD), /R1 \(P0\/P1\) has no trigger/)
867
- assert.throws(() => reviewVerdict(review([{ ...ghP1, trigger: undefined }]), HEAD), /gh-review-11 \(P0\/P1\) has no trigger/)
868
- })
869
-
870
- test('a HEAD pushed after a passing review carries it with no reviewer and no scope rerun', () => {
871
- const passed = review([p2], { head: 'old1234', fixRound: 2, round: 4, complete: true, passes: done })
872
- const carry = carryRecord(passed, HEAD)
873
- assert.equal(carry.tier, 'skip')
874
- assert.equal(carry.carry, AFTER_PASS)
875
- assert.equal(carry.carriedFrom, 'old1234')
876
- assert.equal(carry.round, 5)
877
- assert.equal(carry.fixRound, 2, 'a carry is not a fix cycle')
878
- assert.deepEqual(carry.findings, [], 'carried P2/P3 already have their follow-ups')
879
- const result = reviewVerdict(carry, HEAD, passed)
880
- assert.equal(result.verdict, 'pass', 'no --base: the chain alone proves the approval')
881
- assert.equal(result.reviewedHead, HEAD)
882
- assert.equal(result.carriedFrom, 'old1234')
883
-
884
- const next = carryRecord(carry, 'new5678')
885
- assert.equal(reviewVerdict(next, 'new5678', carry, { earlier: [passed] }).verdict, 'pass', 'carries chain over later CI fixes')
886
- assert.throws(() => reviewVerdict(next, 'new5678', carry), /round 5: the review it carries is missing/, 'a chain that cannot be walked fails closed')
887
- })
888
-
889
- test('only a passing round can be carried; a P0/P1 fix is always reviewed', () => {
890
- const passed = review([], { head: 'old1234', fixRound: 1, complete: true, passes: done })
891
- assert.throws(() => carryRecord({ ...passed, findings: [p1] }, HEAD), (e) => e instanceof ReviewIncomplete && /open P0\/P1/.test(e.message))
892
- assert.throws(() => carryRecord({ ...passed, findings: [ghP1] }, HEAD), /open P0\/P1/, 'a GitHub changes request blocks the carry')
893
- assert.throws(() => carryRecord({ ...passed, external: { awaiting: [{ id: 'gh-review-11' }] } }, HEAD), /awaits a GitHub re-review/)
894
- assert.throws(() => carryRecord({ ...passed, passes: { ...done, tests: 'partial' } }, HEAD), /review pass tests is unfinished/)
895
- assert.throws(() => carryRecord({ ...passed, complete: false }, HEAD), /complete must be true/)
896
- assert.throws(() => carryRecord({ ...passed, commands: [] }, HEAD), /ran no checks/)
897
- assert.throws(() => carryRecord(passed, 'old1234'), /already of old1234/)
898
- assert.throws(() => carryRecord(null, HEAD), /no previous round/)
899
- const { head: _h, ...headless } = passed
900
- assert.throws(() => carryRecord(headless, HEAD), /it has no head/)
901
- const { complete: _c, ...legacy } = passed
902
- assert.throws(() => carryRecord(legacy, HEAD), /not marked complete/, 'as next-actions, a carry needs complete: true')
903
- // A record that claims to be a carry still needs a passing round before it.
904
- const forged = { ...carryRecord(passed, HEAD) }
905
- assert.throws(() => reviewVerdict(forged, HEAD, { ...passed, findings: [p1] }), /open P0\/P1/)
906
- assert.throws(() => reviewVerdict({ ...forged, findings: [p1] }, HEAD, passed), /cannot hold findings/)
907
- const ghResult = reviewVerdict({ ...forged, findings: [ghP1] }, HEAD, passed)
908
- assert.equal(ghResult.verdict, 'fix', 'GitHub feedback merged onto a carry still blocks')
909
- })
910
-
911
-
912
- // A repository with main and a PR branch of `count` commits; returns their shas, oldest first.
913
- function prRepo(dir, count) {
914
- const git = (...args) => spawnSync('git', ['-c', 'user.name=t', '-c', 'user.email=t@t', ...args], { cwd: dir, encoding: 'utf8' }).stdout.trim()
915
- git('init', '-q', '-b', 'main')
916
- writeFileSync(join(dir, 'a.txt'), 'base\n'); git('add', '.'); git('commit', '-qm', 'base')
917
- git('checkout', '-qb', 'pr')
918
- const heads = []
919
- const body = Array.from({ length: 40 }, (_, j) => `line ${j}`)
920
- for (let i = 1; i <= count; i++) {
921
- body[i] = `line ${i} changed`
922
- writeFileSync(join(dir, 'a.txt'), `base\n${body.join('\n')}\n`); git('commit', '-qam', `c${i}`)
923
- heads.push(git('rev-parse', 'HEAD'))
924
- }
925
- return heads
926
- }
927
-
928
- test('the verdict CLI writes a carry record in the review clone, then passes it without --base', () => {
929
- const dir = mkdtempSync(join(tmpdir(), 'verdict-carry-'))
930
- try {
931
- const h = prRepo(dir, 4)
932
- const reviews = join(dir, 'reviews')
933
- spawnSync('mkdir', [reviews])
934
- const base = { version: 1, unit: '1201', complete: true, passes: done, findings: [] }
935
- writeFileSync(join(reviews, '1201-r1.json'), JSON.stringify({ ...base, round: 1, fixRound: 1, head: h[0], commands: ['npm test'], findings: [p1] }))
936
- writeFileSync(join(reviews, '1201-r2.json'), JSON.stringify({ ...base, round: 2, fixRound: 2, head: h[1], tier: 'delta', fixLinesSinceFull: 2, commands: ['npm test'] }))
937
- const node = (...args) => spawnSync('node', [script, '--exit-verdict', ...args], { cwd: dir, encoding: 'utf8' })
938
- const carry = (round, head, ...extra) => node('--carry', '--review', join(reviews, `1201-r${round}.json`), '--head', head, '--base', 'main', ...extra)
939
- const verdict = (round, head, ...extra) => node('--review', join(reviews, `1201-r${round}.json`), '--head', head, ...extra)
940
-
941
- const wrote = carry(3, h[2])
942
- assert.equal(wrote.status, 0, wrote.stderr)
943
- const record = JSON.parse(readFileSync(join(reviews, '1201-r3.json'), 'utf8'))
944
- assert.equal(record.carriedFrom, h[1])
945
- assert.match(record.mergeBase, /^[0-9a-f]{40}$/, 'records the merge-base so a later tier can anchor on it')
946
- assert.match(record.patchFingerprint, /^[0-9a-f]{64}$/)
947
- assert.equal(verdict(3, h[2]).status, 0, verdict(3, h[2]).stderr)
948
- assert.equal(carry(3, h[2]).status, 2, 'never overwrites a round')
949
-
950
- // A carry as the prior of the next tier anchors a delta, not a forced full review.
951
- const cwd = process.cwd()
952
- process.chdir(dir)
953
- try {
954
- const scope = rereviewScope({ prior: record, base: 'main', head: h[3] })
955
- assert.equal(scope.tier, 'delta', scope.reasons.join('; '))
956
- } finally { process.chdir(cwd) }
957
-
958
- assert.equal(carry(4, h[3]).status, 0)
959
- const chained = verdict(4, h[3])
960
- assert.equal(chained.status, 0, chained.stderr)
961
- assert.equal(JSON.parse(chained.stdout).fixRound, 2)
962
-
963
- assert.equal(node('--carry', '--review', join(reviews, '1201-r5.json'), '--head', h[3]).status, 2, '--base is required')
964
- assert.match(carry(5, h[3], '--prior', join(reviews, '1201-r1.json')).stderr, /drop --prior/, 'a carry always carries the round just before it')
965
- assert.match(carry(3, h[3]).stderr, /exists/)
966
- assert.equal(spawnSync('node', [script, '--exit-verdict', '--carry', '--review', join(reviews, '1201-r9.json'), '--head', h[3], '--base', 'nope'], { cwd: dir, encoding: 'utf8' }).status, 3, 'no round 8 to carry')
967
- } finally {
968
- rmSync(dir, { recursive: true, force: true })
969
- }
970
- })
971
-
972
- test('a carry cannot pass over a newer round that holds a P0/P1', () => {
973
- const dir = mkdtempSync(join(tmpdir(), 'verdict-carry-stale-'))
974
- try {
975
- const h = prRepo(dir, 3)
976
- const base = { version: 1, unit: 'u', complete: true, passes: done, commands: ['npm test'] }
977
- writeFileSync(join(dir, 'u-r1.json'), JSON.stringify({ ...base, round: 1, fixRound: 1, head: h[0], findings: [] }))
978
- writeFileSync(join(dir, 'u-r2.json'), JSON.stringify({ ...base, round: 2, fixRound: 1, head: h[1], tier: 'delta', fixLinesSinceFull: 1, findings: [p1] }))
979
- const node = (...args) => spawnSync('node', [script, '--exit-verdict', ...args], { cwd: dir, encoding: 'utf8' })
980
-
981
- const blocked = node('--carry', '--review', join(dir, 'u-r3.json'), '--head', h[2], '--base', 'main')
982
- assert.equal(blocked.status, 3, 'round 2 holds a P1: review the fix')
983
- assert.equal(node('--carry', '--review', join(dir, 'u-r2.json'), '--head', h[2], '--base', 'main').status, 2, 'r2 exists')
984
- rmSync(join(dir, 'u-r2.json'))
985
- writeFileSync(join(dir, 'u-r4.json'), '{}')
986
- assert.match(node('--carry', '--review', join(dir, 'u-r2.json'), '--head', h[2], '--base', 'main').stderr, /carry only the newest round/)
987
-
988
- // A hand-made carry numbered for an older slot, or a stale --prior, fails closed.
989
- const forged = carryRecord({ ...base, round: 1, fixRound: 1, head: h[0], findings: [] }, h[2])
990
- writeFileSync(join(dir, 'u-r3.json'), JSON.stringify(forged))
991
- const mismatch = node('--review', join(dir, 'u-r3.json'), '--head', h[2], '--prior', join(dir, 'u-r1.json'))
992
- assert.equal(mismatch.status, 3)
993
- assert.match(mismatch.stdout, /records round 2, not its file's 3/)
994
- writeFileSync(join(dir, 'u-r3.json'), JSON.stringify({ ...forged, round: 3 }))
995
- assert.match(node('--review', join(dir, 'u-r3.json'), '--head', h[2], '--prior', join(dir, 'u-r1.json')).stdout, /round does not follow/)
996
- } finally {
997
- rmSync(dir, { recursive: true, force: true })
998
- }
999
- })
1000
-
1001
- test('by default every verdict exits 0 with the verdict in the output; only a script failure exits 2', (t) => {
1002
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-exit0-'))
1003
- t.after(() => rmSync(dir, { recursive: true, force: true }))
1004
- const write = (name, value) => { const p = join(dir, name); writeFileSync(p, typeof value === 'string' ? value : JSON.stringify(value)); return p }
1005
- const call = (path, ...extra) => spawnSync(process.execPath, [script, '--review', path, '--head', HEAD, ...extra], { encoding: 'utf8' })
1006
- const verdictOf = (r) => { assert.equal(r.status, 0, r.stderr); return JSON.parse(r.stdout).verdict }
1007
-
1008
- assert.equal(verdictOf(call(write('1201-r1.json', review([p2])))), 'pass')
1009
- assert.equal(verdictOf(call(write('1202-r1.json', review([p1])))), 'fix')
1010
- assert.equal(verdictOf(call(write('1203-r1.json', review([], { external: { awaiting: [{ id: 'gh-review-11', author: 'vera932' }] } })))), 'wait')
1011
- // incomplete: an answer ("review again") with the reason in stdout, nothing on stderr
1012
- const again = call(write('1204-r1.json', '{not json'))
1013
- assert.equal(again.status, 0, again.stderr)
1014
- assert.equal(again.stderr, '')
1015
- assert.deepEqual(Object.keys(JSON.parse(again.stdout)), ['verdict', 'reason'])
1016
- assert.equal(JSON.parse(again.stdout).verdict, 'review-again')
1017
- assert.match(JSON.parse(again.stdout).reason, /spawn the reviewer again/)
1018
- // a script failure is still 2
1019
- assert.equal(call(join(dir, 'missing.json')).status, 2)
1020
- assert.equal(spawnSync(process.execPath, [script, '--review', join(dir, '1201-r1.json')], { encoding: 'utf8' }).status, 2)
1021
- // --json is accepted (the output is always JSON)
1022
- assert.equal(verdictOf(call(join(dir, '1201-r1.json'), '--json')), 'pass')
1023
- })
1024
-
1025
- test('handback and decide exit 0 by default and keep their legacy codes under --exit-verdict', (t) => {
1026
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-exit0b-'))
1027
- t.after(() => rmSync(dir, { recursive: true, force: true }))
1028
- const write = (name, value) => { const p = join(dir, name); writeFileSync(p, JSON.stringify(value)); return p }
1029
- const spec = { ...p1, pass: 'spec', requestedAction: 'refuse before deleting' }
1030
- write('1201-r1.json', review([spec]))
1031
- const decidePath = write('1201-r2.json', review([spec], { round: 2 }))
1032
- const call = (path, ...extra) => spawnSync(process.execPath, [script, '--review', path, '--head', HEAD, ...extra], { encoding: 'utf8' })
1033
- const plain = call(decidePath)
1034
- assert.equal(plain.status, 0, plain.stderr)
1035
- assert.equal(JSON.parse(plain.stdout).verdict, 'decide')
1036
- assert.equal(call(decidePath, '--exit-verdict').status, 6)
1037
-
1038
- const hbDir = mkdtempSync(join(tmpdir(), 'review-verdict-exit0c-'))
1039
- t.after(() => rmSync(hbDir, { recursive: true, force: true }))
1040
- const hb = (name, value) => { const p = join(hbDir, name); writeFileSync(p, JSON.stringify(value)); return p }
1041
- hb('1201-r1.json', review([p1]))
1042
- hb('1201-r2.json', review([p1], { round: 2 }))
1043
- const third = hb('1201-r3.json', review([p1], { round: 3 }))
1044
- const handback = call(third)
1045
- assert.equal(handback.status, 0, handback.stderr)
1046
- assert.equal(JSON.parse(handback.stdout).verdict, 'handback')
1047
- assert.equal(call(third, '--exit-verdict').status, 4)
1048
- })
1049
-
1050
- test('--exit-verdict restores 3 for review-again and 1 for fix', (t) => {
1051
- const dir = mkdtempSync(join(tmpdir(), 'review-verdict-legacy-'))
1052
- t.after(() => rmSync(dir, { recursive: true, force: true }))
1053
- const path = join(dir, '1201-r1.json')
1054
- writeFileSync(path, '{not json')
1055
- const again = spawnSync(process.execPath, [script, '--review', path, '--head', HEAD, '--exit-verdict'], { encoding: 'utf8' })
1056
- assert.equal(again.status, 3)
1057
- assert.equal(JSON.parse(again.stdout).verdict, 'review-again')
1058
- writeFileSync(path, JSON.stringify(review([p1])))
1059
- assert.equal(spawnSync(process.execPath, [script, '--review', path, '--head', HEAD, '--exit-verdict'], { encoding: 'utf8' }).status, 1)
1060
- })