@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,217 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { spawnSync } from 'node:child_process'
4
- import { chmodSync, mkdtempSync, 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 { DEFAULT_LABEL, ReadyCheckError, parseArgs, parseIssueRef, readyCheck, treeFetchers } from './ready-check.mjs'
10
-
11
- const script = join(dirname(fileURLToPath(import.meta.url)), 'ready-check.mjs')
12
-
13
- // A small in-memory GitHub: issues keyed "owner/name#n", sub-issues as API-shaped children.
14
- function fakeGithub(issues, subIssues = {}) {
15
- return {
16
- fetchIssue: ({ repo, number }) => {
17
- const issue = issues[`${repo}#${number}`]
18
- if (!issue) throw new Error(`HTTP 404 ${repo}#${number}`)
19
- return issue
20
- },
21
- fetchSubIssues: ({ repo, number }) => (subIssues[`${repo}#${number}`] || []).map((ref) => {
22
- const [childRepo, n] = ref.split('#')
23
- return { number: Number(n), repository_url: `https://api.github.com/repos/${childRepo}` }
24
- }),
25
- }
26
- }
27
- const issue = (labels, state = 'open', title = 't') => ({ state, title, labels: labels.map((name) => ({ name })) })
28
-
29
- test('the default label is ready-for-agent', () => {
30
- assert.equal(DEFAULT_LABEL, 'ready-for-agent')
31
- })
32
-
33
- test('an issue and all its sub-issues labeled ready passes', () => {
34
- const gh = fakeGithub(
35
- { 'o/r#1': issue(['epic', 'ready-for-agent']), 'o/r#2': issue(['ready-for-agent']), 'o/r#3': issue(['Ready-For-Agent']) },
36
- { 'o/r#1': ['o/r#2', 'o/r#3'] },
37
- )
38
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
39
- assert.equal(result.ready, true)
40
- assert.equal(result.checked.length, 3, 'label match ignores case')
41
- })
42
-
43
- test('an epic without the label passes when all its open sub-issues are ready', () => {
44
- const gh = fakeGithub(
45
- { 'o/r#1': issue(['epic']), 'o/r#2': issue(['ready-for-agent']), 'o/r#3': issue(['epic']), 'o/r#4': issue(['ready-for-agent']) },
46
- { 'o/r#1': ['o/r#2', 'o/r#3'], 'o/r#3': ['o/r#4'] },
47
- )
48
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
49
- assert.equal(result.ready, true)
50
- assert.deepEqual(result.checked.filter((c) => c.exempt).map((c) => c.ref), ['o/r#1', 'o/r#3'], 'nested epics are exempt too')
51
- })
52
-
53
- test('an epic without the label still reports a sub-issue that is not ready', () => {
54
- const gh = fakeGithub({ 'o/r#1': issue(['epic']), 'o/r#2': issue(['triage']) }, { 'o/r#1': ['o/r#2'] })
55
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
56
- assert.equal(result.ready, false)
57
- assert.deepEqual(result.missing.map((m) => m.ref), ['o/r#2'])
58
- })
59
-
60
- test('an open issue whose sub-issues are all closed is a unit again: a rollup that needs the label', () => {
61
- // #1343 was held, its prerequisite #1659 was filed under it and closed: the old check
62
- // called #1343 resolved and the run dropped its remaining work.
63
- const gh = fakeGithub(
64
- { 'o/r#1': issue(['epic']), 'o/r#2': issue([], 'closed'), 'o/r#3': issue(['ready-for-agent']), 'o/r#4': issue([], 'closed') },
65
- { 'o/r#1': ['o/r#2'], 'o/r#3': ['o/r#4'] },
66
- )
67
- const result = readyCheck([{ repo: 'o/r', number: 1 }, { repo: 'o/r', number: 3 }], gh)
68
- assert.equal(result.ready, false)
69
- assert.deepEqual(result.missing.map((m) => m.ref), ['o/r#1'])
70
- assert.deepEqual(result.rollups.map((r) => r.ref), ['o/r#1', 'o/r#3'])
71
- })
72
-
73
- test('an epic whose only open sub-issue is a rollup stays a container', () => {
74
- const gh = fakeGithub(
75
- { 'o/r#1': issue(['epic']), 'o/r#2': issue(['ready-for-agent']), 'o/r#3': issue([], 'closed') },
76
- { 'o/r#1': ['o/r#2'], 'o/r#2': ['o/r#3'] },
77
- )
78
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
79
- assert.equal(result.ready, true)
80
- assert.equal(result.checked.find((c) => c.ref === 'o/r#1').exempt, 'has open sub-issues')
81
- assert.deepEqual(result.rollups.map((r) => r.ref), ['o/r#2'])
82
- })
83
-
84
- test('a plain issue with no sub-issues is a unit, not a rollup', () => {
85
- const gh = fakeGithub({ 'o/r#1': issue([]) })
86
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
87
- assert.equal(result.ready, false)
88
- assert.deepEqual(result.rollups, [])
89
- })
90
-
91
- test('an epic with an unreadable sub-issue is neither exempt nor a rollup', () => {
92
- const gh = fakeGithub({ 'o/r#1': issue(['epic']) }, { 'o/r#1': ['o/r#404'] })
93
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
94
- assert.equal(result.ready, false)
95
- assert.deepEqual(result.missing.map((m) => m.ref), ['o/r#1'])
96
- assert.deepEqual(result.unreadable.map((m) => m.ref), ['o/r#404'])
97
- const partly = fakeGithub({ 'o/r#1': issue(['epic']), 'o/r#2': issue([], 'closed') }, { 'o/r#1': ['o/r#2', 'o/r#404'] })
98
- assert.deepEqual(readyCheck([{ repo: 'o/r', number: 1 }], partly).rollups, [], 'one closed plus one unreadable is not a rollup')
99
- })
100
-
101
- test('tree fetchers answer from the GraphQL tree and fall back to REST for the rest', () => {
102
- const tree = { nodes: {
103
- 'o/r#1': { key: 'o/r#1', state: 'open', title: 'epic', labels: ['epic'], children: ['o/r#2', 'x/y#9'] },
104
- 'o/r#2': { key: 'o/r#2', state: 'open', title: 'a', labels: ['ready-for-agent'], children: [] },
105
- 'x/y#9': { key: 'x/y#9', state: 'open', title: 'deep', labels: ['ready-for-agent'], children: null },
106
- } }
107
- const calls = []
108
- const rest = fakeGithub({ 'x/y#10': issue(['ready-for-agent']) }, { 'x/y#9': ['x/y#10'] })
109
- const fallback = { fetchIssue: (r) => { calls.push(`issue ${r.repo}#${r.number}`); return rest.fetchIssue(r) }, fetchSubIssues: (r) => { calls.push(`subs ${r.repo}#${r.number}`); return rest.fetchSubIssues(r) } }
110
- const result = readyCheck([{ repo: 'o/r', number: 1 }], treeFetchers([tree], fallback))
111
- assert.equal(result.ready, true)
112
- assert.deepEqual(calls, ['subs x/y#9', 'issue x/y#10', 'subs x/y#10'], 'only the part the tree did not cover hits REST')
113
- })
114
-
115
- test('a sub-issue shared by two epics makes both containers', () => {
116
- const gh = fakeGithub(
117
- { 'o/r#1': issue(['epic']), 'o/r#2': issue(['epic']), 'o/r#3': issue(['ready-for-agent']) },
118
- { 'o/r#1': ['o/r#3'], 'o/r#2': ['o/r#3'] },
119
- )
120
- assert.equal(readyCheck([{ repo: 'o/r', number: 1 }, { repo: 'o/r', number: 2 }], gh).ready, true)
121
- })
122
-
123
- test('a sub-issue at any depth, in another repository, without the label is reported', () => {
124
- const gh = fakeGithub(
125
- { 'o/r#1': issue(['ready-for-agent']), 'o/r#2': issue(['ready-for-agent']), 'x/y#9': issue(['bug']) },
126
- { 'o/r#1': ['o/r#2'], 'o/r#2': ['x/y#9'] },
127
- )
128
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
129
- assert.equal(result.ready, false)
130
- assert.deepEqual(result.missing, [{ ref: 'x/y#9', title: 't', labels: ['bug'], parent: 'o/r#2' }])
131
- assert.equal(result.checked.find((c) => c.ref === 'x/y#9').depth, 2)
132
- })
133
-
134
- test('closed sub-issues are skipped; a closed named issue is still checked', () => {
135
- const gh = fakeGithub(
136
- { 'o/r#1': issue(['ready-for-agent']), 'o/r#2': issue([], 'closed'), 'o/r#5': issue([], 'closed') },
137
- { 'o/r#1': ['o/r#2'] },
138
- )
139
- assert.equal(readyCheck([{ repo: 'o/r', number: 1 }], gh).ready, true)
140
- assert.equal(readyCheck([{ repo: 'o/r', number: 5 }], gh).ready, false)
141
- })
142
-
143
- test('an unreadable issue or sub-issue list fails closed', () => {
144
- const gh = fakeGithub({ 'o/r#1': issue(['ready-for-agent']) }, { 'o/r#1': ['o/r#404'] })
145
- const result = readyCheck([{ repo: 'o/r', number: 1 }], gh)
146
- assert.equal(result.ready, false)
147
- assert.equal(result.unreadable[0].ref, 'o/r#404')
148
- const brokenList = { ...gh, fetchSubIssues: () => { throw new Error('HTTP 502') } }
149
- assert.equal(readyCheck([{ repo: 'o/r', number: 1 }], brokenList).ready, false)
150
- })
151
-
152
- test('a sub-issue loop is walked once', () => {
153
- const gh = fakeGithub(
154
- { 'o/r#1': issue(['ready-for-agent']), 'o/r#2': issue(['ready-for-agent']) },
155
- { 'o/r#1': ['o/r#2'], 'o/r#2': ['o/r#1'] },
156
- )
157
- assert.equal(readyCheck([{ repo: 'o/r', number: 1 }], gh).checked.length, 2)
158
- })
159
-
160
- test('a custom label is honored', () => {
161
- const gh = fakeGithub({ 'o/r#1': issue(['agent-ok']) })
162
- assert.equal(readyCheck([{ repo: 'o/r', number: 1 }], { ...gh, label: 'agent-ok' }).ready, true)
163
- assert.equal(readyCheck([{ repo: 'o/r', number: 1 }], gh).ready, false)
164
- })
165
-
166
- test('issue references accept owner/name#n, URLs, and bare numbers with a repo', () => {
167
- assert.deepEqual(parseIssueRef('o/r#12'), { repo: 'o/r', number: 12 })
168
- assert.deepEqual(parseIssueRef('https://github.com/o/r/issues/7'), { repo: 'o/r', number: 7 })
169
- assert.deepEqual(parseIssueRef('#3', 'o/r'), { repo: 'o/r', number: 3 })
170
- assert.throws(() => parseIssueRef('3'), ReadyCheckError)
171
- assert.throws(() => parseArgs([]), ReadyCheckError)
172
- assert.throws(() => parseArgs(['--bogus']), ReadyCheckError)
173
- })
174
-
175
- // ---- CLI against a fake `gh` on PATH ----
176
-
177
- function withFakeGh(responses) {
178
- const dir = mkdtempSync(join(tmpdir(), 'ready-check-gh-'))
179
- const cases = Object.entries(responses)
180
- .map(([path, body]) => ` "${path}") cat <<'EOF'\n${JSON.stringify(body)}\nEOF\n ;;`)
181
- .join('\n')
182
- // gh api [--paginate --slurp] <path>: the path is the last argument.
183
- writeFileSync(join(dir, 'gh'), `#!/bin/sh\nfor last; do :; done\ncase "$last" in\n${cases}\n *) echo "HTTP 404" >&2; exit 1 ;;\nesac\n`)
184
- chmodSync(join(dir, 'gh'), 0o755)
185
- return { ...process.env, PATH: `${dir}:${process.env.PATH}` }
186
- }
187
-
188
- test('CLI exits 0 with the outcome in the output, 2 on bad input; --exit-outcome restores the old codes', () => {
189
- const env = withFakeGh({
190
- 'repos/o/r/issues/1': issue(['ready-for-agent']),
191
- 'repos/o/r/issues/1/sub_issues': [[{ number: 2, repository_url: 'https://api.github.com/repos/o/r' }]],
192
- 'repos/o/r/issues/2': issue(['triage'], 'open', 'Add login'),
193
- 'repos/o/r/issues/2/sub_issues': [[]],
194
- 'repos/o/r/issues/3': issue(['ready-for-agent']),
195
- 'repos/o/r/issues/3/sub_issues': [[]],
196
- })
197
- const ok = spawnSync(process.execPath, [script, '--issue', 'o/r#3'], { env, encoding: 'utf8' })
198
- assert.equal(ok.status, 0, ok.stderr)
199
- assert.equal(JSON.parse(ok.stdout).outcome, 'ready')
200
- const unlabeled = spawnSync(process.execPath, [script, '--repo', 'o/r', '--issue', '1'], { env, encoding: 'utf8' })
201
- assert.equal(unlabeled.status, 0, unlabeled.stderr)
202
- assert.equal(JSON.parse(unlabeled.stdout).outcome, 'unlabeled')
203
- assert.deepEqual(JSON.parse(unlabeled.stdout).missing, [{ ref: 'o/r#2', title: 'Add login', labels: ['triage'], parent: 'o/r#1' }])
204
- const named = spawnSync(process.execPath, [script, '--issue', 'o/r#2'], { env, encoding: 'utf8' })
205
- assert.equal(named.status, 0, named.stderr)
206
- assert.equal(JSON.parse(named.stdout).missing[0].parent, null)
207
- const unreadable = spawnSync(process.execPath, [script, '--issue', 'o/r#2', '--issue', 'o/r#404'], { env, encoding: 'utf8' })
208
- assert.equal(unreadable.status, 0, unreadable.stderr)
209
- assert.equal(JSON.parse(unreadable.stdout).outcome, 'unreadable', 'an unreadable issue outranks an unlabeled one')
210
- const legacy = (...args) => spawnSync(process.execPath, [script, '--exit-outcome', ...args], { env, encoding: 'utf8' }).status
211
- assert.equal(legacy('--issue', 'o/r#3'), 0)
212
- assert.equal(legacy('--repo', 'o/r', '--issue', '1'), 3)
213
- assert.equal(legacy('--issue', 'o/r#2', '--issue', 'o/r#404'), 1)
214
- assert.equal(legacy(), 2)
215
- const usage = spawnSync(process.execPath, [script], { env, encoding: 'utf8' })
216
- assert.equal(usage.status, 2)
217
- })
@@ -1,91 +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 { activeRuns, message, ACTIVE_HOURS } from './rehydrate.mjs'
10
- import { RULES, SKILL_PATH } from './next-actions.mjs'
11
-
12
- const SCRIPT = path.join(path.dirname(fileURLToPath(import.meta.url)), 'rehydrate.mjs')
13
-
14
- function repo(runs) {
15
- const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'rehydrate-'))
16
- for (const [name, plan, ageHours = 0] of runs) {
17
- const dir = path.join(cwd, '.resolve-issues', 'rush', name)
18
- fs.mkdirSync(dir, { recursive: true })
19
- const file = path.join(dir, 'task-plan.json')
20
- fs.writeFileSync(file, typeof plan === 'string' ? plan : JSON.stringify(plan))
21
- const at = new Date(Date.now() - ageHours * 3_600_000)
22
- fs.utimesSync(file, at, at)
23
- }
24
- return cwd
25
- }
26
-
27
- test('only a recent run with an open unit counts; finished, stale, and unreadable runs do not', () => {
28
- const cwd = repo([
29
- ['live', { skill: 'rush-issues', planRevision: 267, units: [{ id: '1', status: 'succeeded' }, { id: '2', status: 'in-flight' }, { id: '3', status: 'blocked' }] }, 1],
30
- ['done', { units: [{ id: '1', status: 'succeeded' }, { id: '2', status: 'cancelled' }] }],
31
- ['stale', { units: [{ id: '1', status: 'in-flight' }] }, ACTIVE_HOURS + 1],
32
- ['broken', '{not json'],
33
- ])
34
- const runs = activeRuns(cwd)
35
- assert.deepEqual(runs, [{ dir: path.join(cwd, '.resolve-issues', 'rush', 'live'), skill: 'rush-issues', revision: 267, open: 2 }])
36
- assert.deepEqual(activeRuns(fs.mkdtempSync(path.join(os.tmpdir(), 'rehydrate-none-'))), [], 'no .resolve-issues: nothing')
37
- const unit = path.join(cwd, '.resolve-issues', 'rush', 'live', 'units', '7')
38
- fs.mkdirSync(unit, { recursive: true })
39
- assert.deepEqual(activeRuns(unit), runs, 'a coordinator standing in a unit workspace still finds its run')
40
- })
41
-
42
- test('the message names both skills for a rush run, each run with its next-actions command, and the rules', () => {
43
- const dir = '/repo/.resolve-issues/rush/r1'
44
- const rush = path.join(path.dirname(path.dirname(SKILL_PATH)), 'rush-issues', 'SKILL.md')
45
- const text = message([{ dir, skill: 'rush-issues', revision: 5, open: 3 }], { rushSkillPath: SKILL_PATH })
46
- assert.match(text, /read each of these in full, with no line limit/)
47
- assert.ok(text.includes(`- ${SKILL_PATH}\n`))
48
- assert.ok(text.includes(`next-actions.mjs --run ${dir} --capacity-json '<fresh snapshot JSON>'`))
49
- assert.match(text, /Read subagent_settings now/)
50
- assert.match(text, /no default worker count/)
51
- assert.ok(text.endsWith(RULES))
52
- const plain = message([{ dir, skill: 'resolve-issues', revision: 5, open: 3 }], { rushSkillPath: rush })
53
- assert.ok(!plain.includes('rush-issues/SKILL.md'), 'a resolve-issues run reads only its own skill')
54
- assert.equal(message([]), '')
55
- })
56
-
57
- test('each recovered run refreshes capacity separately instead of reusing one snapshot', () => {
58
- const text = message([
59
- { dir: '/repo/.resolve-issues/rush/first', skill: 'resolve-issues', revision: 1, open: 6 },
60
- { dir: '/repo/.resolve-issues/rush/second', skill: 'resolve-issues', revision: 2, open: 3 },
61
- ])
62
- const commands = text.split('\n').filter(line => line.includes('next-actions.mjs --run'))
63
- assert.equal(commands.length, 2)
64
- for (const command of commands) {
65
- assert.match(command, /refresh subagent_settings immediately before node/)
66
- assert.match(command, /--capacity-json '<fresh snapshot JSON>'/)
67
- }
68
- })
69
-
70
- test('as a hook it reads cwd from stdin, prints SessionStart context, and prints nothing without a run', () => {
71
- const cwd = repo([['live', { skill: 'resolve-issues', units: [{ id: '1', status: 'pending' }] }]])
72
- const run = (input) => spawnSync(process.execPath, [SCRIPT, '--hook'], { input, encoding: 'utf8', cwd: os.tmpdir() })
73
- const out = run(JSON.stringify({ hook_event_name: 'SessionStart', source: 'compact', cwd }))
74
- assert.equal(out.status, 0)
75
- const parsed = JSON.parse(out.stdout)
76
- assert.equal(parsed.hookSpecificOutput.hookEventName, 'SessionStart')
77
- assert.match(parsed.hookSpecificOutput.additionalContext, /Runs in progress here:/)
78
- const empty = run(JSON.stringify({ cwd: fs.mkdtempSync(path.join(os.tmpdir(), 'rehydrate-empty-')) }))
79
- assert.equal(empty.status, 0)
80
- assert.equal(empty.stdout, '')
81
- assert.equal(run('not json').status, 0, 'bad input never blocks a session')
82
- })
83
-
84
- test('rush-issues/rehydrate.mjs prints the same message, for a harness that runs the owner skill\'s script', () => {
85
- const cwd = repo([['live', { skill: 'rush-issues', units: [{ id: '1', status: 'in-flight' }] }]])
86
- const rush = path.join(path.dirname(path.dirname(SKILL_PATH)), 'rush-issues', 'rehydrate.mjs')
87
- const out = spawnSync(process.execPath, [rush, '--cwd', cwd], { encoding: 'utf8' })
88
- assert.equal(out.status, 0)
89
- assert.equal(out.stdout, `${message(activeRuns(cwd))}\n`)
90
- assert.match(out.stdout, /rush-issues\/SKILL\.md/)
91
- })