@1aboveio/skills 1.0.8 → 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 (214) 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 +1 -1
  8. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  9. package/skills/app-dev/app-debug/SKILL.md +1 -1
  10. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  11. package/skills/app-dev/better-auth/SKILL.md +1 -1
  12. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  13. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  14. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  15. package/skills/app-dev/data-quality/SKILL.md +1 -1
  16. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  17. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  18. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  19. package/skills/app-dev/monorepo/SKILL.md +1 -1
  20. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  21. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  22. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  23. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  24. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  25. package/skills/app-dev/pyspark/SKILL.md +1 -1
  26. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  27. package/skills/app-dev/python-backend/SKILL.md +1 -1
  28. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  29. package/skills/app-dev/shadcn/SKILL.md +1 -1
  30. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  31. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  32. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  33. package/skills/cicd/cloud-build/SKILL.md +1 -1
  34. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  35. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  36. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  37. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  38. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  39. package/skills/cicd/docker/SKILL.md +1 -1
  40. package/skills/cicd/docker/release-check.mjs +3 -1
  41. package/skills/cicd/github-actions/SKILL.md +1 -1
  42. package/skills/cicd/github-actions/release-check.mjs +3 -1
  43. package/skills/cicd/github-flow/SKILL.md +1 -1
  44. package/skills/cicd/github-flow/release-check.mjs +3 -1
  45. package/skills/cicd/google-cloud/SKILL.md +1 -1
  46. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  47. package/skills/cicd/mergify/SKILL.md +1 -1
  48. package/skills/cicd/mergify/release-check.mjs +3 -1
  49. package/skills/cicd/podman/SKILL.md +1 -1
  50. package/skills/cicd/podman/release-check.mjs +3 -1
  51. package/skills/content/article-review/SKILL.md +1 -1
  52. package/skills/content/article-review/release-check.mjs +3 -1
  53. package/skills/content/article-to-image/SKILL.md +1 -1
  54. package/skills/content/article-to-image/release-check.mjs +3 -1
  55. package/skills/content/flowchart-svg/SKILL.md +1 -1
  56. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  57. package/skills/content/image-gen/SKILL.md +1 -1
  58. package/skills/content/image-gen/release-check.mjs +3 -1
  59. package/skills/content/mermaid/SKILL.md +1 -1
  60. package/skills/content/mermaid/release-check.mjs +3 -1
  61. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  62. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  63. package/skills/content/wechat-publisher/SKILL.md +1 -1
  64. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  65. package/skills/content/wechat-search/SKILL.md +1 -1
  66. package/skills/content/wechat-search/release-check.mjs +3 -1
  67. package/skills/content/xai-search/SKILL.md +88 -13
  68. package/skills/content/xai-search/release-check.mjs +3 -1
  69. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  70. package/skills/core/distill-writing/SKILL.md +1 -1
  71. package/skills/core/distill-writing/release-check.mjs +3 -1
  72. package/skills/core/doc-to-md/SKILL.md +1 -1
  73. package/skills/core/doc-to-md/release-check.mjs +3 -1
  74. package/skills/core/editorial-review/SKILL.md +1 -1
  75. package/skills/core/editorial-review/release-check.mjs +3 -1
  76. package/skills/core/house-style/SKILL.md +1 -1
  77. package/skills/core/house-style/release-check.mjs +3 -1
  78. package/skills/core/plain-language/SKILL.md +1 -1
  79. package/skills/core/plain-language/release-check.mjs +3 -1
  80. package/skills/core/polish-cn/SKILL.md +1 -1
  81. package/skills/core/polish-cn/release-check.mjs +3 -1
  82. package/skills/core/reg-parser/SKILL.md +1 -1
  83. package/skills/core/reg-parser/release-check.mjs +3 -1
  84. package/skills/core/terminology/SKILL.md +1 -1
  85. package/skills/core/terminology/release-check.mjs +3 -1
  86. package/skills/core/write-doc/SKILL.md +1 -1
  87. package/skills/core/write-doc/release-check.mjs +3 -1
  88. package/skills/engineering/better-review/SKILL.md +1 -1
  89. package/skills/engineering/better-review/release-check.mjs +3 -1
  90. package/skills/engineering/contributing/SKILL.md +1 -1
  91. package/skills/engineering/contributing/release-check.mjs +3 -1
  92. package/skills/engineering/e2e-test/SKILL.md +1 -1
  93. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  94. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  95. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  96. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  97. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  98. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  99. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  100. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  101. package/skills/engineering/golden-path/SKILL.md +1 -1
  102. package/skills/engineering/golden-path/release-check.mjs +3 -1
  103. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  104. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  105. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  106. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  107. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  108. package/skills/engineering/resolve-issues/references/task-plan.md +5 -1
  109. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  110. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  111. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  112. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  113. package/skills/engineering/resolve-release/SKILL.md +1 -1
  114. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  115. package/skills/engineering/rush-issues/SKILL.md +1 -1
  116. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  117. package/skills/engineering/rush-release/SKILL.md +1 -1
  118. package/skills/engineering/rush-release/release-check.mjs +3 -1
  119. package/skills/engineering/test-quality/SKILL.md +1 -1
  120. package/skills/engineering/test-quality/release-check.mjs +3 -1
  121. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  122. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  123. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  124. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  125. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  126. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  127. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  128. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  129. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  130. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  131. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  132. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  133. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  134. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  135. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  136. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  137. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -436
  138. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  139. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  140. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  141. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  142. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  143. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  144. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  145. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  146. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  147. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  148. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  149. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  150. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  151. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  152. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  153. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  154. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  155. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  156. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  157. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  158. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  159. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  160. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  161. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  162. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  163. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  164. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  165. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  166. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  167. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  168. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  169. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  170. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  171. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  172. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  173. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  174. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  175. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  176. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  177. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  178. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  179. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  180. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  181. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  182. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  183. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  184. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  185. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  186. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  187. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  188. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  189. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  190. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  191. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  192. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  193. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  194. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  195. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  196. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  197. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  198. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  199. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  200. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  201. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  202. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  203. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  204. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  205. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  206. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  207. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  208. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  209. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  210. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  211. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  212. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  213. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  214. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
@@ -1,264 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4
- import { tmpdir } from 'node:os'
5
- import { join } from 'node:path'
6
-
7
- import {
8
- collectExternal,
9
- fetchPr,
10
- permissionFetcher,
11
- findStructured,
12
- handledIds,
13
- mergeExternal,
14
- parsePr,
15
- run,
16
- severityTag,
17
- STRUCTURED_MARKER,
18
- } from './github-reviews.mjs'
19
-
20
- const HEAD = 'abc1234'
21
- const ROLES = { vera932: 'write', alo335: 'admin', reader: 'read' }
22
- const permissionOf = (login) => ROLES[login] ?? 'none'
23
- const review = (id, login, state, body = '', at = '2026-10-08T10:00:00Z', commit = HEAD) => ({
24
- databaseId: id, author: { login }, state, body, submittedAt: at, url: `https://github.com/o/r/pull/1#pullrequestreview-${id}`, commit: { oid: commit },
25
- })
26
- const thread = (id, login, body, extra = {}) => ({
27
- isResolved: false, isOutdated: false, ...extra,
28
- comments: { nodes: [{ databaseId: id, author: { login }, body, path: 'src/a.ts', line: 12, url: `https://github.com/o/r/pull/1#discussion_r${id}`, originalCommit: { oid: HEAD } }] },
29
- })
30
- const pr = (parts = {}) => ({
31
- author: { login: 'cory-bot' },
32
- reviews: { nodes: parts.reviews ?? [] },
33
- reviewThreads: { nodes: parts.threads ?? [] },
34
- comments: { nodes: parts.comments ?? [] },
35
- })
36
-
37
- test('severity tags are read from the first line in the usual forms only', () => {
38
- for (const [body, tag] of [
39
- ['P1: refund sign flips', 'P1'], ['**P0** token leaks', 'P0'], ['[P1] missing check', 'P1'],
40
- ['![P1 Badge](https://img.shields.io/badge/P1-orange) Wrong total', 'P1'], ['P2: rename', 'P2'],
41
- ['**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Keep the lock**', 'P1'],
42
- ['<!-- codex-pull-request-review-summary -->', null],
43
- ['Pretty sure this is fine', null], ['P10 items', null], ['nit\nP1: on line two', null], ['', null],
44
- ]) assert.equal(severityTag(body), tag, body)
45
- })
46
-
47
- test('a trusted changes request is a P1 finding; approvals, dismissals and later approvals clear it', () => {
48
- const blocked = collectExternal(pr({ reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'Refund rounds the wrong way\nmore')] }), HEAD, { permissionOf })
49
- assert.deepEqual(blocked.findings.map((f) => [f.id, f.severity, f.summary, f.author]), [['gh-review-11', 'P1', 'Refund rounds the wrong way', 'vera932']])
50
- assert.match(blocked.findings[0].detail, /more/)
51
- const cleared = collectExternal(pr({ reviews: [
52
- review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z'),
53
- review(12, 'vera932', 'COMMENTED', 'thanks', '2026-10-08T11:00:00Z'),
54
- review(13, 'vera932', 'APPROVED', '', '2026-10-08T12:00:00Z'),
55
- ] }), HEAD, { permissionOf })
56
- assert.deepEqual(cleared.findings, [])
57
- const dismissed = collectExternal(pr({ reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'x'), review(14, 'vera932', 'DISMISSED', '', '2026-10-08T12:00:00Z')] }), HEAD, { permissionOf })
58
- assert.deepEqual(dismissed.findings, [])
59
- })
60
-
61
- test('only write access counts, and never the PR author', () => {
62
- const external = collectExternal(pr({
63
- reviews: [review(21, 'reader', 'CHANGES_REQUESTED', 'no'), review(22, 'cory-bot', 'CHANGES_REQUESTED', 'self')],
64
- threads: [thread(31, 'codex-bot', 'P1: bot says'), thread(32, 'alo335', 'P1: maintainer says')],
65
- }), HEAD, { permissionOf })
66
- assert.deepEqual(external.findings.map((f) => f.id), ['gh-comment-32'])
67
- assert.deepEqual(external.ignored.map((i) => [i.author, i.reason]).sort(), [
68
- ['codex-bot', 'permission none'], ['cory-bot', 'PR author'], ['reader', 'permission read'],
69
- ])
70
- })
71
-
72
- test('tagged comments block until resolved; P2/P3 and untagged comments never do', () => {
73
- const external = collectExternal(pr({
74
- threads: [
75
- thread(41, 'vera932', '**P0** tenant check missing'),
76
- thread(42, 'vera932', 'P1: resolved already', { isResolved: true }),
77
- thread(43, 'vera932', 'P2: rename'),
78
- thread(44, 'vera932', 'looks off to me'),
79
- ],
80
- reviews: [review(45, 'vera932', 'COMMENTED', '[P1] the export drops currency')],
81
- comments: [{ databaseId: 46, author: { login: 'alo335' }, body: 'P1: migration not reversible', createdAt: '2026-10-08T10:00:00Z', url: 'u46' }],
82
- }), HEAD, { permissionOf })
83
- assert.deepEqual(external.findings.map((f) => [f.id, f.severity]), [['gh-comment-41', 'P0'], ['gh-comment-45', 'P1'], ['gh-comment-46', 'P1']])
84
- assert.equal(external.findings[0].file, 'src/a.ts')
85
- assert.equal(external.findings[0].line, 12)
86
- const badge = collectExternal(pr({ threads: [thread(49, 'vera932', '**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange)</sub></sub> Keep the lock**')] }), HEAD, { permissionOf })
87
- assert.equal(badge.findings[0].summary, 'Keep the lock')
88
- })
89
-
90
- test('a changes request whose body is tagged counts once', () => {
91
- const external = collectExternal(pr({ reviews: [review(47, 'vera932', 'CHANGES_REQUESTED', 'P1: wrong total')] }), HEAD, { permissionOf })
92
- assert.deepEqual(external.findings.map((f) => f.id), ['gh-review-47'])
93
- })
94
-
95
- test('after a fix brief carried them, a standing changes request is awaited and a comment is dropped', () => {
96
- const handled = new Set(['gh-review-11', 'gh-comment-41'])
97
- const external = collectExternal(pr({
98
- reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z', 'old1234')],
99
- threads: [thread(41, 'vera932', 'P1: still unresolved'), thread(48, 'vera932', 'P1: new one')],
100
- }), HEAD, { permissionOf, handled })
101
- assert.deepEqual(external.findings.map((f) => f.id), ['gh-comment-48'])
102
- assert.deepEqual(external.awaiting.map((a) => [a.id, a.author, a.commit]), [['gh-review-11', 'vera932', 'old1234']])
103
- const rerequested = collectExternal(pr({ reviews: [
104
- review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z', 'old1234'),
105
- review(15, 'vera932', 'CHANGES_REQUESTED', 'still wrong', '2026-10-08T13:00:00Z'),
106
- ] }), HEAD, { permissionOf, handled })
107
- assert.deepEqual(rerequested.findings.map((f) => f.id), ['gh-review-15'], 'a new changes request is a new finding')
108
- assert.deepEqual(rerequested.awaiting, [])
109
- })
110
-
111
- test('the handled ledger carries forward and merge replaces what it added before', () => {
112
- const prior = { findings: [{ id: 'gh-review-11', severity: 'P1' }, { id: 'R1', severity: 'P1' }], external: { added: ['gh-review-11'], handled: ['gh-comment-41'] } }
113
- const brief = { fix: [{ id: 'gh-review-11' }, { id: 'R1' }] }
114
- assert.deepEqual([...handledIds(prior, brief)].sort(), ['gh-comment-41', 'gh-review-11'])
115
- assert.deepEqual([...handledIds(prior, null)], ['gh-comment-41'], 'no fix brief: the prior round handed nothing on')
116
- assert.deepEqual([...handledIds(null)], [])
117
- const own = { id: 'R2', severity: 'P1' }
118
- const stale = { id: 'gh-comment-1', severity: 'P1', source: 'github' }
119
- const fresh = { findings: [{ id: 'gh-comment-2', severity: 'P1', source: 'github' }], awaiting: [], ignored: [], head: HEAD, prAuthor: 'cory-bot' }
120
- const merged = mergeExternal({ version: 1, findings: [own, stale], external: { added: ['gh-comment-1'] } }, fresh, new Set(['gh-review-11']))
121
- assert.deepEqual(merged.findings.map((f) => f.id), ['R2', 'gh-comment-2'])
122
- assert.deepEqual(merged.external.added, ['gh-comment-2'])
123
- assert.deepEqual(merged.external.handled, ['gh-review-11'])
124
- })
125
-
126
- const PASSES = { spec: 'done', correctness: 'done', tests: 'done', standards: 'done' }
127
- const structuredBody = (head, extra = {}) => `${STRUCTURED_MARKER}\nReview by Agentier\n\n\`\`\`json\n${JSON.stringify({ version: 1, head, complete: true, passes: PASSES, findings: [], commands: ['npm test'], ...extra })}\n\`\`\`\n`
128
-
129
- test('a structured result stands in only when trusted and of this HEAD, newest first', () => {
130
- const comments = [
131
- { databaseId: 51, author: { login: 'alo335' }, body: structuredBody(HEAD, { commands: ['older'] }), createdAt: '2026-10-08T10:00:00Z', url: 'u51' },
132
- { databaseId: 52, author: { login: 'alo335' }, body: structuredBody(HEAD, { commands: ['newer'] }), createdAt: '2026-10-08T11:00:00Z', url: 'u52' },
133
- { databaseId: 53, author: { login: 'alo335' }, body: structuredBody('other99'), createdAt: '2026-10-08T12:00:00Z', url: 'u53' },
134
- { databaseId: 54, author: { login: 'reader' }, body: structuredBody(HEAD, { commands: ['untrusted'] }), createdAt: '2026-10-08T13:00:00Z', url: 'u54' },
135
- { databaseId: 55, author: { login: 'cory-bot' }, body: structuredBody(HEAD, { commands: ['self'] }), createdAt: '2026-10-08T14:00:00Z', url: 'u55' },
136
- { databaseId: 56, author: { login: 'alo335' }, body: `${STRUCTURED_MARKER}\n\`\`\`json\n{not json\n\`\`\``, createdAt: '2026-10-08T15:00:00Z', url: 'u56' },
137
- ]
138
- const found = findStructured(pr({ comments }), HEAD, { permissionOf })
139
- assert.deepEqual(found.result.commands, ['newer'])
140
- assert.deepEqual(found.source, { kind: 'github', author: 'alo335', url: 'u52' })
141
- assert.equal(findStructured(pr({ comments: comments.slice(2) }), HEAD, { permissionOf }), null)
142
- const tagged = collectExternal(pr({ comments: [{ ...comments[0], body: structuredBody(HEAD, { findings: [{ id: 'R1', severity: 'P1' }] }).replace('Review by', 'P1 Review by') }] }), HEAD, { permissionOf })
143
- assert.deepEqual(tagged.findings, [], 'a structured result is never read as a tagged comment')
144
- })
145
-
146
- test('parsePr takes owner/name#N only', () => {
147
- assert.deepEqual(parsePr('1aboveio/ai#1545'), { owner: '1aboveio', name: 'ai', number: 1545 })
148
- assert.throws(() => parsePr('1545'), /owner\/name#N/)
149
- })
150
-
151
- test('run merges into the round file using the prior beside it, and writes a structured result with the caller\'s counters', (t) => {
152
- const dir = mkdtempSync(join(tmpdir(), 'github-reviews-'))
153
- t.after(() => rmSync(dir, { recursive: true, force: true }))
154
- const deps = (node) => ({ fetch: () => node, permissions: () => permissionOf })
155
- writeFileSync(join(dir, '7-r1.json'), JSON.stringify({ findings: [{ id: 'gh-review-11', severity: 'P1', source: 'github' }], external: { added: ['gh-review-11'] } }))
156
- writeFileSync(join(dir, '7-r1.fix.json'), JSON.stringify({ fix: [{ id: 'gh-review-11' }] }))
157
- const path = join(dir, '7-r2.json')
158
- writeFileSync(path, JSON.stringify({ version: 1, unit: '7', round: 2, head: HEAD, findings: [] }))
159
- const node = pr({ reviews: [review(11, 'vera932', 'CHANGES_REQUESTED', 'x', '2026-10-08T10:00:00Z', 'old1234')], threads: [thread(61, 'vera932', 'P1: new')] })
160
- const merged = run(['merge', '--pr', 'o/r#1', '--head', HEAD, '--review', path], deps(node))
161
- assert.deepEqual(merged.out.awaiting.map((a) => a.id), ['gh-review-11'])
162
- const file = JSON.parse(readFileSync(path, 'utf8'))
163
- assert.deepEqual(file.findings.map((f) => f.id), ['gh-comment-61'])
164
- assert.deepEqual(file.external.awaiting.map((a) => a.id), ['gh-review-11'])
165
- assert.deepEqual(file.external.handled, ['gh-review-11'])
166
-
167
- const out = join(dir, '7-r3.json')
168
- const none = run(['structured', '--pr', 'o/r#1', '--head', HEAD, '--out', out, '--unit', '7', '--round', '3', '--fix-round', '2'], deps(pr()))
169
- assert.equal(none.out.found, false)
170
- const withResult = pr({ comments: [{ databaseId: 71, author: { login: 'alo335' }, body: structuredBody(HEAD, { unit: 'theirs', round: 9, fixRound: 9 }), createdAt: '2026-10-08T10:00:00Z', url: 'u71' }] })
171
- const written = run(['structured', '--pr', 'o/r#1', '--head', HEAD, '--out', out, '--unit', '7', '--round', '3', '--fix-round', '2'], deps(withResult))
172
- assert.equal(written.out.found, true)
173
- const result = JSON.parse(readFileSync(out, 'utf8'))
174
- assert.deepEqual([result.unit, result.round, result.fixRound, result.source.author], ['7', 3, 2, 'alo335'])
175
- assert.throws(() => run(['merge', '--pr', 'o/r#1', '--head', HEAD], deps(node)), /usage/)
176
- })
177
-
178
- test('a GitHub finding from an incomplete or handed-back round is not handled: it still blocks', (t) => {
179
- const dir = mkdtempSync(join(tmpdir(), 'github-reviews-nofix-'))
180
- t.after(() => rmSync(dir, { recursive: true, force: true }))
181
- // round 1 was incomplete (or handed back): the verdict wrote no fix brief
182
- writeFileSync(join(dir, '7-r1.json'), JSON.stringify({ complete: false, findings: [{ id: 'gh-comment-1', severity: 'P1', source: 'github' }], external: { added: ['gh-comment-1'] } }))
183
- const path = join(dir, '7-r2.json')
184
- writeFileSync(path, JSON.stringify({ version: 1, unit: '7', round: 2, head: HEAD, findings: [] }))
185
- run(['merge', '--pr', 'o/r#1', '--head', HEAD, '--review', path], { fetch: () => pr({ threads: [thread(1, 'vera932', 'P1: still broken')] }), permissions: () => permissionOf })
186
- assert.deepEqual(JSON.parse(readFileSync(path, 'utf8')).findings.map((f) => f.id), ['gh-comment-1'])
187
- })
188
-
189
- test('merge never removes a reviewer finding that only claims github provenance', () => {
190
- const forged = { id: 'R1', severity: 'P1', summary: 'real local blocker', source: 'github' }
191
- const merged = mergeExternal({ version: 1, findings: [forged] }, { findings: [], awaiting: [], ignored: [], head: HEAD }, new Set())
192
- assert.deepEqual(merged.findings, [forged])
193
- const faked = mergeExternal({ version: 1, findings: [forged], external: { added: ['R1'] } }, { findings: [], awaiting: [], ignored: [], head: HEAD }, new Set())
194
- assert.deepEqual(faked.findings, [forged], 'listing a reviewer id under external.added does not make it removable')
195
- })
196
-
197
- test('every page is read: a standing changes request behind 100 comments still blocks', () => {
198
- const standing = review(1, 'vera932', 'CHANGES_REQUESTED', 'no', '2026-10-08T09:00:00Z')
199
- const chatter = Array.from({ length: 150 }, (_, i) => review(100 + i, 'alo335', 'COMMENTED', 'ok', '2026-10-08T10:00:00Z'))
200
- const all = { reviews: [standing, ...chatter], reviewThreads: [], comments: [] }
201
- const calls = []
202
- const query = (text, { after }) => {
203
- const field = Object.keys(all).find((f) => text.includes(`${f}(first: 100`))
204
- calls.push([field, after])
205
- const start = after ? Number(after) : 0
206
- const nodes = all[field].slice(start, start + 100)
207
- const next = start + 100 < all[field].length
208
- return { data: { repository: { pullRequest: { author: { login: 'cory-bot' }, [field]: { nodes, pageInfo: { hasNextPage: next, endCursor: next ? String(start + 100) : null } } } } } }
209
- }
210
- const fetched = fetchPr({ owner: 'o', name: 'r', number: 1 }, query)
211
- assert.equal(fetched.reviews.nodes.length, 151)
212
- assert.deepEqual(calls.filter(([f]) => f === 'reviews'), [['reviews', null], ['reviews', '100']])
213
- assert.deepEqual(collectExternal(fetched, HEAD, { permissionOf }).findings.map((f) => f.id), ['gh-review-1'])
214
- assert.throws(() => fetchPr({ owner: 'o', name: 'r', number: 1 }, () => ({ data: { repository: { pullRequest: { reviews: { nodes: [], pageInfo: { hasNextPage: true, endCursor: null } } } } } })), /without a cursor/)
215
- })
216
-
217
- test('an access lookup that fails stops the merge; only "not a user" means no access', () => {
218
- const failing = (message) => permissionFetcher({ owner: 'o', name: 'r' }, () => { throw new Error(message) })
219
- assert.throws(() => failing('gh api failed: connection reset')('vera932'), /cannot tell vera932's access/)
220
- assert.equal(failing('gh: x is not a user (HTTP 404)')('x'), 'none')
221
- const answering = (body) => permissionFetcher({ owner: 'o', name: 'r' }, () => JSON.stringify(body))
222
- assert.equal(answering({ permission: 'none', role_name: '' })('codex[bot]'), 'none', 'a bot answers 200 with no access')
223
- assert.equal(answering({ permission: 'write', role_name: 'maintain' })('alo335'), 'write')
224
- assert.equal(answering({ permission: 'write', role_name: 'Security reviewer' })('sec'), 'write', 'a custom role keeps its base access')
225
- const blocked = () => collectExternal(pr({ reviews: [review(1, 'alo335', 'CHANGES_REQUESTED', 'no')] }), HEAD, { permissionOf: failing('HTTP 502') })
226
- assert.throws(blocked, /cannot tell alo335's access/)
227
- })
228
-
229
- test('a GitHub finding the reviewer carried forward is replaced, not doubled or refused', () => {
230
- const carried = { id: 'gh-comment-1', severity: 'P1', summary: 'carried by the reviewer', trigger: 't', rule: 'r', paths: [{ path: 'a', status: 'open' }] }
231
- const current = collectExternal(pr({ threads: [thread(1, 'vera932', 'P1: still broken')] }), HEAD, { permissionOf })
232
- const merged = mergeExternal({ version: 1, findings: [carried, { id: 'R2', severity: 'P3' }] }, current, new Set())
233
- assert.deepEqual(merged.findings.map((f) => [f.id, f.summary]), [['R2', undefined], ['gh-comment-1', 'still broken']])
234
- const resolved = mergeExternal({ version: 1, findings: [carried] }, collectExternal(pr(), HEAD, { permissionOf }), new Set())
235
- assert.deepEqual(resolved.findings, [], 'GitHub decides whether its finding is still open')
236
- })
237
-
238
- test('GitHub findings carry the blocking shape, so the verdict needs no exemption', () => {
239
- const [finding] = collectExternal(pr({ threads: [thread(1, 'vera932', 'P1: still broken')] }), HEAD, { permissionOf }).findings
240
- assert.match(finding.trigger, /vera932's P1 comment on GitHub/)
241
- assert.ok(finding.rule)
242
- assert.deepEqual(finding.paths, [{ path: 'src/a.ts:12', status: 'open' }])
243
- })
244
-
245
- test('a comment that only mentions the marker is still read as feedback', () => {
246
- for (const body of [
247
- `P1: Parser drops feedback containing ${STRUCTURED_MARKER}`,
248
- `P1: Parser drops feedback containing ${STRUCTURED_MARKER}\n\n\`\`\`json\n{}\n\`\`\``,
249
- `P1: wrong\n${STRUCTURED_MARKER}\n\`\`\`json\n{"head":"${HEAD}"}\n\`\`\``,
250
- ]) {
251
- const external = collectExternal(pr({ threads: [thread(1, 'vera932', body)] }), HEAD, { permissionOf })
252
- assert.deepEqual(external.findings.map((f) => f.id), ['gh-comment-1'], body)
253
- }
254
- })
255
-
256
- test('a posted result must be complete by the reviewer\'s own rules', () => {
257
- const posted = (extra) => pr({ comments: [{ databaseId: 1, author: { login: 'alo335' }, body: structuredBody(HEAD, extra), createdAt: '2026-10-08T10:00:00Z', url: 'u1' }] })
258
- assert.ok(findStructured(posted({}), HEAD, { permissionOf }))
259
- assert.equal(findStructured(posted({ complete: undefined, passes: undefined }), HEAD, { permissionOf }), null)
260
- assert.equal(findStructured(posted({ passes: { ...PASSES, tests: 'partial' } }), HEAD, { permissionOf }), null)
261
- assert.equal(findStructured(posted({ commands: [] }), HEAD, { permissionOf }), null)
262
- assert.equal(findStructured(posted({ findings: undefined }), HEAD, { permissionOf }), null)
263
- assert.equal(findStructured(posted({ complete: undefined }), HEAD, { permissionOf }), null, 'all passes done but no complete: true')
264
- })
@@ -1,79 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { spawnSync } from 'node:child_process'
4
- import fs from 'node:fs'
5
- import os from 'node:os'
6
- import path from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
-
9
- import { run, withHook, hookCommand, REHYDRATE } from './install-hooks.mjs'
10
-
11
- const SCRIPT = path.join(path.dirname(fileURLToPath(import.meta.url)), 'install-hooks.mjs')
12
-
13
- function home(files = {}) {
14
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'install-hooks-'))
15
- for (const [rel, body] of Object.entries(files)) {
16
- fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true })
17
- fs.writeFileSync(path.join(dir, rel), typeof body === 'string' ? body : JSON.stringify(body))
18
- }
19
- return dir
20
- }
21
- const read = (dir, rel) => JSON.parse(fs.readFileSync(path.join(dir, rel), 'utf8'))
22
-
23
- test('installs into both harnesses, keeps every other setting and hook, and a second run changes nothing', () => {
24
- const herdr = { hooks: [{ type: 'command', command: 'bash herdr.sh session' }] }
25
- const dir = home({
26
- '.claude/settings.json': { model: 'opus', hooks: { SessionStart: [herdr], Stop: [{ hooks: [] }] } },
27
- '.codex/hooks.json': { hooks: { SessionStart: [herdr] } },
28
- })
29
- assert.deepEqual(run({ home: dir }).map((r) => `${r.harness}:${r.status}`), ['claude:updated', 'codex:updated'])
30
- const claude = read(dir, '.claude/settings.json')
31
- assert.equal(claude.model, 'opus')
32
- assert.deepEqual(claude.hooks.Stop, [{ hooks: [] }])
33
- assert.deepEqual(claude.hooks.SessionStart, [herdr, { matcher: 'compact', hooks: [{ type: 'command', command: hookCommand(), timeout: 10 }] }])
34
- assert.deepEqual(read(dir, '.codex/hooks.json').hooks.SessionStart[1].matcher, 'compact')
35
- assert.deepEqual(run({ home: dir }).map((r) => r.status), ['installed', 'installed'])
36
- assert.deepEqual(run({ home: dir, check: true }).map((r) => r.status), ['installed', 'installed'])
37
- })
38
-
39
- test('a moved skill updates the old entry in place instead of adding a second', () => {
40
- const old = { hooks: { SessionStart: [{ matcher: 'compact', hooks: [{ type: 'command', command: 'node "/old/resolve-issues/scripts/rehydrate.mjs" --hook' }] }] } }
41
- const { settings, changed } = withHook(old)
42
- assert.equal(changed, true)
43
- assert.equal(settings.hooks.SessionStart.length, 1)
44
- assert.equal(settings.hooks.SessionStart[0].hooks[0].command, `node "${REHYDRATE}" --hook`)
45
- })
46
-
47
- test('check: a missing hook is reported in the output (exit 0; --exit-missing exits 1) and nothing is written; an enabled plugin counts; no harness home is absent', () => {
48
- const dir = home({ '.claude/settings.json': { model: 'opus' } })
49
- const out = spawnSync(process.execPath, [SCRIPT, '--check', '--home', dir], { encoding: 'utf8' })
50
- assert.equal(out.status, 0, out.stderr)
51
- assert.match(out.stdout, /claude {2}missing/)
52
- assert.match(out.stdout, /codex {3}absent/)
53
- assert.match(out.stdout, /^missing: claude/m)
54
- const json = JSON.parse(spawnSync(process.execPath, [SCRIPT, '--check', '--json', '--home', dir], { encoding: 'utf8' }).stdout)
55
- assert.deepEqual(json.missing, ['claude'])
56
- assert.equal(spawnSync(process.execPath, [SCRIPT, '--check', '--exit-missing', '--home', dir], { encoding: 'utf8' }).status, 1)
57
- assert.equal(spawnSync(process.execPath, [SCRIPT, '--bogus'], { encoding: 'utf8' }).status, 2)
58
- assert.deepEqual(read(dir, '.claude/settings.json'), { model: 'opus' })
59
- const plugin = home({ '.claude/settings.json': { enabledPlugins: { 'engineering-workflow@1aboveio-engineering-skills': true } } })
60
- assert.deepEqual(run({ home: plugin, check: true, harness: 'claude' }).map((r) => r.status), ['plugin'])
61
- assert.deepEqual(run({ home: plugin, harness: 'claude' }).map((r) => r.status), ['plugin'], 'the plugin ships the hook: no second copy')
62
- assert.equal(read(plugin, '.claude/settings.json').hooks, undefined)
63
- })
64
-
65
- test('a settings root or SessionStart of the wrong shape is refused, not silently kept', () => {
66
- for (const body of [[], { hooks: { SessionStart: {} } }]) {
67
- const dir = home({ '.claude/settings.json': body })
68
- assert.throws(() => run({ home: dir, harness: 'claude' }), /fix it by hand/)
69
- assert.deepEqual(read(dir, '.claude/settings.json'), body)
70
- }
71
- })
72
-
73
- test('a settings file that is not JSON is never overwritten', () => {
74
- const dir = home({ '.claude/settings.json': '{ "model": "opus", // comment\n}' })
75
- const out = spawnSync(process.execPath, [SCRIPT, '--home', dir, '--harness', 'claude'], { encoding: 'utf8' })
76
- assert.equal(out.status, 2)
77
- assert.match(out.stderr, /is not JSON/)
78
- assert.equal(fs.readFileSync(path.join(dir, '.claude/settings.json'), 'utf8'), '{ "model": "opus", // comment\n}')
79
- })
@@ -1,107 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
-
4
- import { DETAIL_QUERY, PAGE_QUERY, TREE_QUERY, WIDTHS, fetchTree, withRetry } from './issue-tree.mjs'
5
-
6
- // A fake GitHub GraphQL endpoint over issues keyed "o/r#n": { state, labels, blockedBy, children }.
7
- // It honors the query's level widths, so truncation and follow-ups behave as on GitHub.
8
- function fakeGraphql(issues, { fail = {} } = {}) {
9
- const calls = []
10
- const node = (key, depth, widths) => {
11
- const i = issues[key]
12
- const [repo, n] = key.split('#')
13
- const labels = i.labels ?? []
14
- const blockers = i.blockedBy ?? []
15
- const out = {
16
- number: Number(n), title: `t${n}`, state: (i.state ?? 'open').toUpperCase(), url: null, repository: { nameWithOwner: repo },
17
- labels: { totalCount: labels.length, nodes: labels.slice(0, widths.labels).map((name) => ({ name })) },
18
- blockedBy: { totalCount: blockers.length, nodes: blockers.slice(0, widths.blockers).map((b) => ({ number: Number(b.split('#')[1]), state: (issues[b]?.state ?? 'open').toUpperCase(), repository: { nameWithOwner: b.split('#')[0] } })) },
19
- }
20
- const kids = i.children ?? []
21
- if (depth >= WIDTHS.length) out.subIssuesSummary = { total: kids.length }
22
- else {
23
- const shown = kids.slice(0, WIDTHS[depth])
24
- out.subIssues = { totalCount: kids.length, pageInfo: { hasNextPage: kids.length > shown.length, endCursor: String(shown.length) }, nodes: shown.map((k) => node(k, depth + 1, widths)) }
25
- }
26
- return out
27
- }
28
- const graphql = (query, { owner, name, number, after }) => {
29
- const key = `${owner}/${name}#${number}`
30
- const kind = query === TREE_QUERY ? 'tree' : query === DETAIL_QUERY ? 'detail' : query === PAGE_QUERY ? 'page' : 'unknown'
31
- calls.push(`${kind} ${key}`)
32
- if (fail[key]) { const err = fail[key]; if (typeof err === 'number') { fail[key]--; throw new Error('HTTP 502 Bad Gateway') } throw new Error(err) }
33
- if (!issues[key]) return { repository: { issue: null } }
34
- if (kind === 'tree') return { repository: { issue: node(key, 0, { labels: 20, blockers: 10 }) } }
35
- if (kind === 'detail') { const full = node(key, WIDTHS.length, { labels: 100, blockers: 100 }); delete full.subIssuesSummary; return { repository: { issue: full } } }
36
- const kids = issues[key].children.slice(Number(after), Number(after) + 100)
37
- return { repository: { issue: { subIssues: { pageInfo: { hasNextPage: Number(after) + 100 < issues[key].children.length, endCursor: String(Number(after) + 100) }, nodes: kids.map((k) => ({ number: Number(k.split('#')[1]), repository: { nameWithOwner: k.split('#')[0] } })) } } } }
38
- }
39
- return { graphql, calls }
40
- }
41
- const noSleep = () => {}
42
-
43
- test('one query fetches three levels with labels and blockers', () => {
44
- const { graphql, calls } = fakeGraphql({
45
- 'o/r#1': { children: ['o/r#2', 'o/r#3'] },
46
- 'o/r#2': { state: 'closed', labels: ['ready-for-agent'] },
47
- 'o/r#3': { children: ['x/y#4'], blockedBy: ['o/r#2'] },
48
- 'x/y#4': { labels: ['ready-for-agent'] },
49
- })
50
- const tree = fetchTree({ repo: 'o/r', number: 1 }, { graphql, sleep: noSleep })
51
- assert.equal(tree.complete, true)
52
- assert.deepEqual(calls, ['tree o/r#1'])
53
- assert.deepEqual(tree.nodes['o/r#3'].children, ['x/y#4'])
54
- assert.deepEqual(tree.nodes['o/r#3'].blockedBy, [{ key: 'o/r#2', state: 'closed' }])
55
- assert.equal(tree.nodes['x/y#4'].parent, 'o/r#3')
56
- assert.equal(tree.nodes['o/r#2'].state, 'closed')
57
- })
58
-
59
- test('follow-ups run only where GitHub truncated: depth, child pages, wide labels or blockers', () => {
60
- const many = Array.from({ length: 55 }, (_, i) => `o/r#${100 + i}`)
61
- const issues = {
62
- 'o/r#1': { children: ['o/r#2', ...many] },
63
- 'o/r#2': { children: ['o/r#3'], blockedBy: Array.from({ length: 12 }, (_, i) => `o/r#${200 + i}`) },
64
- 'o/r#3': { children: ['o/r#4'] },
65
- 'o/r#4': { children: ['o/r#5'] },
66
- 'o/r#5': {},
67
- }
68
- for (const k of many) issues[k] = {}
69
- const { graphql, calls } = fakeGraphql(issues)
70
- const tree = fetchTree({ repo: 'o/r', number: 1 }, { graphql, sleep: noSleep })
71
- assert.equal(tree.complete, true)
72
- assert.equal(tree.nodes['o/r#1'].children.length, 56, 'every child, past the first level width')
73
- assert.deepEqual(tree.nodes['o/r#4'].children, ['o/r#5'], 'the fourth level came from a re-rooted query')
74
- assert.equal(tree.nodes['o/r#5'].parent, 'o/r#4')
75
- assert.equal(tree.nodes['o/r#2'].blockedBy.length, 12, 'a wide detail query filled the blockers')
76
- const pagedChildren = many.slice(49).map((k) => `tree ${k}`)
77
- assert.deepEqual([...calls].sort(), ['tree o/r#1', 'detail o/r#2', 'tree o/r#4', 'page o/r#1', ...pagedChildren].sort(), 'one follow-up per truncation, nothing else')
78
- })
79
-
80
- test('transient failures retry; a lasting failure marks the tree incomplete', () => {
81
- const issues = { 'o/r#1': { children: ['o/r#2'] }, 'o/r#2': {} }
82
- const flaky = fakeGraphql(issues, { fail: { 'o/r#1': 2 } })
83
- assert.equal(fetchTree({ repo: 'o/r', number: 1 }, { graphql: flaky.graphql, sleep: noSleep }).complete, true)
84
- assert.equal(flaky.calls.length, 3)
85
- const down = fakeGraphql(issues, { fail: { 'o/r#1': 5 } })
86
- const tree = fetchTree({ repo: 'o/r', number: 1 }, { graphql: down.graphql, sleep: noSleep })
87
- assert.equal(tree.complete, false)
88
- assert.match(tree.errors[0].error, /502/)
89
- const denied = fakeGraphql(issues, { fail: { 'o/r#1': 'Could not resolve to a Repository' } })
90
- fetchTree({ repo: 'o/r', number: 1 }, { graphql: denied.graphql, sleep: noSleep })
91
- assert.equal(denied.calls.length, 1, 'a non-transient error is not retried')
92
- })
93
-
94
- test('withRetry gives up after its attempts', () => {
95
- let n = 0
96
- const call = withRetry(() => { n++; throw new Error('read: EOF') }, { attempts: 3, sleep: noSleep })
97
- assert.throws(call, /EOF/)
98
- assert.equal(n, 3)
99
- })
100
-
101
- test('the tree query stays under GitHub’s 500,000-node limit', () => {
102
- // GitHub multiplies `first:` down the nesting; each issue also asks for 20 labels and 10 blockers.
103
- let issues = 1
104
- let total = 0
105
- for (const width of WIDTHS) { issues *= width; total += issues * (1 + 20 + 10) }
106
- assert.ok(total < 500_000, `estimated ${total} nodes`)
107
- })
@@ -1,14 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { dirname, resolve } from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
-
6
- import { manualOnlyPolicyFindings } from '../../engineering-runtime/scripts/invocation-policy.mjs'
7
-
8
- const engineeringRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
9
-
10
- test('issue and release orchestrators require explicit invocation', () => {
11
- for (const name of ['resolve-issues', 'rush-issues', 'rush-release', 'resolve-release']) {
12
- assert.deepEqual(manualOnlyPolicyFindings(resolve(engineeringRoot, name)), [], name)
13
- }
14
- })
@@ -1,149 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { spawnSync } from 'node:child_process'
3
- import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4
- import { tmpdir } from 'node:os'
5
- import { join } from 'node:path'
6
- import test from 'node:test'
7
- import { nextActions, parseCliArgs, NextActionsError } from './next-actions.mjs'
8
-
9
- const script = new URL('./next-actions.mjs', import.meta.url)
10
- const pending = (n) => Array.from({ length: n }, (_, i) => ({ id: `p${i}`, status: 'pending', stage: 'pending' }))
11
- const busy = (n) => Array.from({ length: n }, (_, i) => ({ id: `w${i}`, status: 'in-flight', stage: 'implement', worker: `run${i}`, workerKind: 'implement' }))
12
- const snapshot = (maxConcurrent, runningCount = 0, pendingCount = 0) => ({
13
- revision: 'live-settings', maxConcurrent, runningCount, pendingCount,
14
- occupiedSlots: runningCount + pendingCount,
15
- availableSlots: Math.max(0, maxConcurrent - runningCount - pendingCount),
16
- })
17
- const starts = (result) => result.due.filter(a => ['fill-slot', 'resume', 'start-review', 'start-fix'].includes(a.action)).map(a => a.unit)
18
-
19
- test('the live Harness limit permits six independent units instead of a hidden four', () => {
20
- const result = nextActions({ units: pending(6) }, {}, snapshot(6))
21
- assert.deepEqual(starts(result), ['p0', 'p1', 'p2', 'p3', 'p4', 'p5'])
22
- assert.deepEqual(result.waiting, [])
23
- })
24
-
25
- test('available capacity already subtracts workflow, outside, and reserved workers', () => {
26
- const plan = { units: [...busy(2), ...pending(4)] }
27
- const result = nextActions(plan, {}, snapshot(6, 3, 1))
28
- assert.deepEqual(starts(result), ['p0', 'p1'])
29
- assert.deepEqual(result.waiting.map(a => a.unit), ['p2', 'p3'])
30
- assert.equal(result.busy, 2)
31
- })
32
-
33
- test('a lower cap preserves existing workers, blocks new starts, and permits coordination', () => {
34
- const plan = { units: [...busy(4), ...pending(2),
35
- { id: 'done', status: 'in-flight', stage: 'done', pr: '#1', headSha: 'abc', reviewedHead: 'abc' }] }
36
- const before = structuredClone(plan)
37
- const result = nextActions(plan, {}, snapshot(2, 4))
38
- assert.deepEqual(starts(result), [])
39
- assert.deepEqual(result.waiting.map(a => a.unit), ['p0', 'p1'])
40
- assert.deepEqual(result.due.map(a => a.action), ['deliver'])
41
- assert.deepEqual(plan, before)
42
- })
43
-
44
- test('each scheduling pass uses its new snapshot, not a frozen run-start budget', () => {
45
- const plan = { units: pending(6) }
46
- assert.deepEqual(starts(nextActions(plan, {}, snapshot(1))), ['p0'])
47
- assert.deepEqual(starts(nextActions(plan, {}, snapshot(6))), ['p0', 'p1', 'p2', 'p3', 'p4', 'p5'])
48
- assert.deepEqual(starts(nextActions(plan, {}, snapshot(6, 6))), [])
49
- })
50
-
51
- test('dependency constraints still hold even with spare capacity', () => {
52
- const plan = { units: [...pending(6), { id: 'child', status: 'pending', dependsOn: ['p0'] }] }
53
- assert.deepEqual(starts(nextActions(plan, {}, snapshot(8))), ['p0', 'p1', 'p2', 'p3', 'p4', 'p5'])
54
- })
55
-
56
- test('inconsistent capacity refuses instead of guessing; no capacity at all means unlimited', () => {
57
- for (const capacity of [{}, [], '6', -1, 1.5, Infinity,
58
- { ...snapshot(6), availableSlots: 7 }, { ...snapshot(6), runningCount: -1 },
59
- { ...snapshot(6), occupiedSlots: 1 }, { ...snapshot(6), pendingCount: 0.5 },
60
- { ...snapshot(6), maxConcurrent: 0 }, { maxConcurrent: 6, availableSlots: 6 }]) {
61
- assert.throws(() => nextActions({ units: pending(1) }, {}, capacity), NextActionsError)
62
- }
63
- assert.deepEqual(starts(nextActions({ units: pending(6) }, {}, 6)), ['p0', 'p1', 'p2', 'p3', 'p4', 'p5'])
64
- assert.deepEqual(starts(nextActions({ units: pending(1) }, {}, 0)), [])
65
- for (const none of [undefined, null]) {
66
- const open = nextActions({ units: [...busy(2), ...pending(6)] }, {}, none)
67
- assert.deepEqual(open.capacity, { source: 'none' })
68
- assert.deepEqual(starts(open), ['p0', 'p1', 'p2', 'p3', 'p4', 'p5'], 'every worker action is due without a capacity source')
69
- assert.deepEqual(open.waiting, [])
70
- assert.ok(open.due.every((a) => /\[needs a free worker slot\]$/.test(a.detail)))
71
- }
72
- })
73
-
74
- test('CLI takes at most one capacity source, none at all, and accepts an exhausted budget', () => {
75
- assert.equal(parseCliArgs(['--run', '/run']).slots, undefined)
76
- assert.equal(parseCliArgs(['--run', '/run']).capacity, undefined)
77
- assert.throws(() => parseCliArgs(['--run', '/run', '--slots', '4', '--capacity-json', JSON.stringify(snapshot(6))]), NextActionsError)
78
- assert.throws(() => parseCliArgs(['--run', '/run', '--capacity-json', '{broken']), NextActionsError)
79
- assert.throws(() => parseCliArgs(['--run', '/run', '--capacity-json', JSON.stringify({ maxConcurrent: 6 })]), NextActionsError)
80
- assert.equal(parseCliArgs(['--run', '/run', '--slots', '0']).slots, 0)
81
- })
82
-
83
- test('malformed CLI capacity refuses before sync, network access, or run-state writes', t => {
84
- const run = mkdtempSync(join(tmpdir(), 'next-actions-capacity-refusal-'))
85
- t.after(() => rmSync(run, { recursive: true, force: true }))
86
- const contents = {
87
- 'task-plan.json': JSON.stringify({ skill: 'resolve-issues', spec: 'o/r#1', units: [{ id: '2', status: 'pending', stage: 'pending' }] }),
88
- 'issue-tree.json': JSON.stringify({ root: 'o/r#1', complete: true, nodes: {
89
- 'o/r#1': { key: 'o/r#1', repo: 'o/r', number: 1, state: 'open', children: ['o/r#2'], blockedBy: [] },
90
- 'o/r#2': { key: 'o/r#2', repo: 'o/r', number: 2, title: 'Pending unit', state: 'open', children: [], blockedBy: [] },
91
- } }),
92
- 'next-actions-seen.json': JSON.stringify({ marker: 'preserve-seen-state' }),
93
- }
94
- for (const [file, value] of Object.entries(contents)) writeFileSync(join(run, file), value)
95
- const gh = join(run, 'gh')
96
- writeFileSync(gh, '#!/bin/sh\nprintf touched > "$CAPACITY_GH_SPY"\nexit 1\n')
97
- chmodSync(gh, 0o755)
98
- const spy = join(run, 'network-called')
99
- for (const flag of ['--sync', '--resume']) {
100
- for (const value of ['{broken', JSON.stringify({ maxConcurrent: 6 }), JSON.stringify({ ...snapshot(6), occupiedSlots: 1 })]) {
101
- const result = spawnSync(process.execPath, [script.pathname, '--run', run, '--capacity-json', value, flag, '--json'], {
102
- encoding: 'utf8', env: { ...process.env, PATH: run, CAPACITY_GH_SPY: spy },
103
- })
104
- assert.equal(result.status, 2, result.stderr)
105
- assert.equal(result.stdout, '')
106
- assert.equal(existsSync(spy), false, 'invalid capacity must not reach GitHub sync')
107
- for (const [file, value] of Object.entries(contents)) assert.equal(readFileSync(join(run, file), 'utf8'), value)
108
- }
109
- }
110
- const control = spawnSync(process.execPath, [script.pathname, '--run', run, '--capacity-json', JSON.stringify(snapshot(6)), '--sync', '--json', '--peek'], {
111
- encoding: 'utf8', env: { ...process.env, PATH: run, CAPACITY_GH_SPY: spy },
112
- })
113
- assert.equal(control.status, 0, control.stderr)
114
- assert.equal(existsSync(spy), true, 'positive control proves valid capacity reaches the same GitHub sync boundary')
115
- assert.deepEqual(starts(JSON.parse(control.stdout)), ['2'])
116
- })
117
-
118
- test('CLI applies fresh capacity and reports its source without modifying the task plan', t => {
119
- const run = mkdtempSync(join(tmpdir(), 'next-actions-capacity-'))
120
- t.after(() => rmSync(run, { recursive: true, force: true }))
121
- const path = join(run, 'task-plan.json')
122
- const saved = JSON.stringify({ skill: 'resolve-issues', units: [...busy(2), ...pending(4)] })
123
- writeFileSync(path, saved)
124
- const call = (...args) => spawnSync(process.execPath, [script.pathname, '--run', run, ...args], { encoding: 'utf8' })
125
- const unlimited = call('--json', '--peek')
126
- assert.equal(unlimited.status, 0, unlimited.stderr)
127
- assert.deepEqual(JSON.parse(unlimited.stdout).capacity, { source: 'none' })
128
- assert.deepEqual(starts(JSON.parse(unlimited.stdout)), ['p0', 'p1', 'p2', 'p3'], 'with no capacity flag every worker action is due')
129
- const unlimitedText = call('--peek')
130
- assert.equal(unlimitedText.status, 0, unlimitedText.stderr)
131
- assert.match(unlimitedText.stdout, /fill-slot\s+.*\[needs a free worker slot\]/)
132
- assert.equal(unlimitedText.stdout.match(/no capacity given/g).length, 1, 'the capacity line says so once')
133
- assert.equal(call('--exit-due', '--peek').status, 1)
134
- assert.equal(call('--slots', '2', '--capacity-json', JSON.stringify(snapshot(6))).status, 2, 'two capacity sources are an error')
135
- assert.equal(existsSync(join(run, 'next-actions-seen.json')), false)
136
- const first = call('--capacity-json', JSON.stringify(snapshot(6, 3, 1)), '--json')
137
- assert.equal(first.status, 0, first.stderr)
138
- assert.equal(JSON.parse(first.stdout).exitDue, true)
139
- const result = JSON.parse(first.stdout)
140
- assert.deepEqual(starts(result), ['p0', 'p1'])
141
- assert.deepEqual(result.capacity, { source: 'harness', ...snapshot(6, 3, 1) })
142
- const exhausted = call('--capacity-json', JSON.stringify(snapshot(2, 4)), '--json')
143
- assert.equal(exhausted.status, 0, exhausted.stderr)
144
- assert.equal(JSON.parse(exhausted.stdout).exitDue, false)
145
- assert.deepEqual(starts(JSON.parse(exhausted.stdout)), [])
146
- assert.equal(readFileSync(path, 'utf8'), saved)
147
- const text = call('--capacity-json', JSON.stringify(snapshot(6, 3, 1)))
148
- assert.match(text.stdout, /Harness.*4\/6.*2 available/)
149
- })