@1aboveio/skills 1.0.8 → 1.0.10

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,366 +0,0 @@
1
- // Tests for model-catalog — pure core + CLI round-trip (exit codes), no network.
2
- import { test } from 'node:test'
3
- import assert from 'node:assert/strict'
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
- import { spawnSync } from 'node:child_process'
9
-
10
- import {
11
- PRUNE_AFTER_DAYS, syncCatalog, bankEntry, readCatalog, writeCatalog, pathForHarness,
12
- EFFORTS, DEFAULT_EFFORT, TIERS, SLOT_ELIGIBLE_TIERS, normalizeTier, operatingPoints, tierAt, toTiers,
13
- } from './model-catalog.mjs'
14
-
15
- const SCRIPT = fileURLToPath(new URL('./bin/model-catalog.mjs', import.meta.url))
16
- const tmpFile = () => path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'model-catalog-test-')), 'catalog.json')
17
-
18
- const entry = (id, lastSeenAt, confirmedAt = '2026-01-01') => ({ id, family: 'x', tier: 'frontier', confirmedAt, ...(lastSeenAt ? { lastSeenAt } : {}) })
19
-
20
- // ---- syncCatalog: the rule-4 predicate in one place ----
21
-
22
- // @covers package:harness-runtime
23
- // @level unit
24
- test('sync bumps lastSeenAt for seen ids and keeps fresh unseen entries', () => {
25
- const { catalog, bumped, pruned } = syncCatalog(
26
- { models: [entry('a', '2026-05-01'), entry('b', '2026-06-01')] },
27
- ['a'],
28
- '2026-07-13',
29
- )
30
- assert.deepEqual(bumped, ['a'])
31
- assert.deepEqual(pruned, [])
32
- assert.equal(catalog.models.find((m) => m.id === 'a').lastSeenAt, '2026-07-13')
33
- assert.equal(catalog.models.find((m) => m.id === 'b').lastSeenAt, '2026-06-01') // unseen: untouched
34
- })
35
-
36
- test('sync prunes strictly after PRUNE_AFTER_DAYS — day 60 kept, day 61 pruned', () => {
37
- // 2026-05-14 is exactly 60 days before 2026-07-13; 2026-05-13 is 61.
38
- const { catalog, pruned } = syncCatalog(
39
- { models: [entry('kept', '2026-05-14'), entry('gone', '2026-05-13')] },
40
- [],
41
- '2026-07-13',
42
- )
43
- assert.equal(PRUNE_AFTER_DAYS, 60)
44
- assert.deepEqual(pruned, ['gone'])
45
- assert.deepEqual(catalog.models.map((m) => m.id), ['kept'])
46
- })
47
-
48
- test('sync falls back to confirmedAt when lastSeenAt is absent (pre-field entries)', () => {
49
- const { pruned } = syncCatalog(
50
- { models: [entry('old', null, '2026-01-01'), entry('new', null, '2026-07-01')] },
51
- [],
52
- '2026-07-13',
53
- )
54
- assert.deepEqual(pruned, ['old'])
55
- })
56
-
57
- test('sync bumps before pruning — a stale entry rediscovered this run survives', () => {
58
- const { catalog, pruned } = syncCatalog(
59
- { models: [entry('back', '2026-01-01')] },
60
- ['back'],
61
- '2026-07-13',
62
- )
63
- assert.deepEqual(pruned, [])
64
- assert.equal(catalog.models[0].lastSeenAt, '2026-07-13')
65
- })
66
-
67
- // ---- bankEntry: rule-2 classification writes ----
68
-
69
- test('bank appends a new entry stamped with confirmedAt + lastSeenAt', () => {
70
- const c = bankEntry({ models: [] }, { id: 'm', family: 'openai', tier: 'balanced-coder' }, '2026-07-13')
71
- assert.deepEqual(c.models, [{ id: 'm', family: 'openai', tiers: { high: 'balanced-coder' }, confirmedAt: '2026-07-13', lastSeenAt: '2026-07-13' }])
72
- })
73
-
74
- test('bank overwrites an existing id (human correction) and refreshes confirmedAt', () => {
75
- const c = bankEntry(
76
- { models: [entry('m', '2026-06-01')] },
77
- { id: 'm', family: 'openai', tier: 'balanced-coder' },
78
- '2026-07-13',
79
- )
80
- assert.equal(c.models.length, 1)
81
- assert.deepEqual(c.models[0].tiers, { high: 'balanced-coder' })
82
- assert.equal(c.models[0].confirmedAt, '2026-07-13')
83
- })
84
-
85
- test('bank rejects entries missing id/family/tier or with an unknown tier', () => {
86
- assert.throws(() => bankEntry({ models: [] }, { family: 'x', tier: 'frontier' }, '2026-07-13'))
87
- assert.throws(() => bankEntry({ models: [] }, { id: 'm', tier: 'frontier' }, '2026-07-13'))
88
- assert.throws(() => bankEntry({ models: [] }, { id: 'm', family: 'x' }, '2026-07-13')) // neither tier nor tiers
89
- assert.throws(() => bankEntry({ models: [] }, { id: 'm', family: 'x', tier: 'huge' }, '2026-07-13'))
90
- })
91
-
92
- // ---- operating points: tier = f(model, effort), banked once (2026-07-19) ----
93
-
94
- test('bank accepts a tiers map — one model, two operating points', () => {
95
- const c = bankEntry(
96
- { models: [] },
97
- { id: 'gpt-5.6-terra', family: 'openai', tiers: { high: 'balanced-coder', max: 'deep-reasoner' } },
98
- '2026-07-19',
99
- )
100
- assert.deepEqual(c.models[0].tiers, { high: 'balanced-coder', max: 'deep-reasoner' })
101
- assert.equal(c.models[0].tier, undefined) // the legacy single-point field never persists
102
- })
103
-
104
- test('a single tier UPSERTS one point; a full tiers map REPLACES all of them', () => {
105
- let c = bankEntry({ models: [] }, { id: 'terra', family: 'openai', tier: 'balanced-coder' }, '2026-07-19')
106
- assert.deepEqual(c.models[0].tiers, { high: 'balanced-coder' }) // no effort -> the high point
107
-
108
- // measured later, confirmed on its own: the existing point survives
109
- c = bankEntry(c, { id: 'terra', family: 'openai', tier: 'deep-reasoner', effort: 'max' }, '2026-07-19')
110
- assert.deepEqual(c.models[0].tiers, { high: 'balanced-coder', max: 'deep-reasoner' })
111
-
112
- // a full map is the human restating the whole picture — it wins outright
113
- c = bankEntry(c, { id: 'terra', family: 'openai', tiers: { max: 'deep-reasoner' } }, '2026-07-19')
114
- assert.deepEqual(c.models[0].tiers, { max: 'deep-reasoner' })
115
- })
116
-
117
- test('an upsert onto a LEGACY-shape entry leaves no stale tier/effort behind', () => {
118
- // Regression: the strip ran on the incoming entry only, so `existing.tier` was
119
- // spread back in with nothing to shadow it — the stored entry then claimed a tier
120
- // that could flatly contradict its own map. Reads went through toTiers and looked
121
- // fine; the FILE was wrong, and the file is what a human and a cross-harness
122
- // lookup (catalog rule 5) actually read.
123
- const legacy = { models: [{ id: 'm', family: 'f', tier: 'mid-tier', effort: 'high', confirmedAt: '2026-06-01' }] }
124
-
125
- const upserted = bankEntry(legacy, { id: 'm', family: 'f', tier: 'deep-reasoner', effort: 'max' }, '2026-07-19')
126
- assert.equal(upserted.models[0].tier, undefined)
127
- assert.equal(upserted.models[0].effort, undefined)
128
- assert.deepEqual(upserted.models[0].tiers, { high: 'balanced-coder', max: 'deep-reasoner' }) // legacy point preserved
129
-
130
- const replaced = bankEntry({ models: [{ id: 'm', family: 'f', tier: 'frontier' }] }, { id: 'm', family: 'f', tiers: { high: 'balanced-coder' } }, '2026-07-19')
131
- assert.equal(replaced.models[0].tier, undefined) // never "frontier" beside a balanced-coder map
132
- assert.deepEqual(replaced.models[0].tiers, { high: 'balanced-coder' })
133
- })
134
-
135
- test('bank refuses an entry carrying no operating points', () => {
136
- // `tiers: {}` passes a truthiness guard but classifies nothing: tierAt returns
137
- // null at every effort and no later sync ever flags it. Check the POINTS.
138
- for (const bad of [{}, [], null]) {
139
- assert.throws(
140
- () => bankEntry({ models: [] }, { id: 'a', family: 'f', tiers: bad }, '2026-07-19'),
141
- /tiers|operating points/,
142
- `tiers: ${JSON.stringify(bad)} must be rejected`,
143
- )
144
- }
145
- assert.throws(() => bankEntry({ models: [] }, { id: 'a', family: 'f', tiers: 'frontier' }, '2026-07-19'), /tiers/)
146
- })
147
-
148
- test('sync does not emit an undefined tier for an entry with no classification at all', () => {
149
- const { migrated } = syncCatalog({ models: [{ id: 'nocap', family: 'f', lastSeenAt: '2026-07-18' }] }, [], '2026-07-19')
150
- assert.ok(!migrated.some((m) => m.includes('undefined')), `migration line leaked undefined: ${migrated}`)
151
- })
152
-
153
- test('bank rejects a point below high — cheaper means a cheaper model, not a throttled one', () => {
154
- for (const bad of ['low', 'medium', 'none']) {
155
- assert.throws(
156
- () => bankEntry({ models: [] }, { id: 'm', family: 'x', tiers: { [bad]: 'balanced-coder' } }, '2026-07-19'),
157
- /effort/,
158
- `effort "${bad}" must be rejected`,
159
- )
160
- }
161
- assert.throws(() => bankEntry({ models: [] }, { id: 'm', family: 'x', tiers: { high: 'huge' } }, '2026-07-19'), /tier/)
162
- assert.deepEqual(EFFORTS, ['high', 'xhigh', 'max'])
163
- assert.equal(DEFAULT_EFFORT, 'high')
164
- })
165
-
166
- test('operatingPoints lists every banked point, strongest tier first', () => {
167
- const catalog = {
168
- models: [
169
- { id: 'terra', family: 'openai', tiers: { high: 'balanced-coder', max: 'deep-reasoner' } },
170
- { id: 'sonnet', family: 'anthropic', tiers: { high: 'balanced-coder' } },
171
- ],
172
- }
173
- // slotEligible rides along on every point (wired to SLOT_ELIGIBLE_TIERS in 2026-07-20's
174
- // audit fix); both bands here are eligible, the scoped-coder case lives in the seed tests.
175
- assert.deepEqual(operatingPoints(catalog, 'terra'), [
176
- { effort: 'max', tier: 'deep-reasoner', slotEligible: true },
177
- { effort: 'high', tier: 'balanced-coder', slotEligible: true },
178
- ])
179
- assert.deepEqual(operatingPoints(catalog, 'sonnet'), [{ effort: 'high', tier: 'balanced-coder', slotEligible: true }])
180
- assert.deepEqual(operatingPoints(catalog, 'unknown'), [])
181
- assert.deepEqual(operatingPoints(null, 'terra'), []) // absent catalog is normal
182
- })
183
-
184
- test('tierAt answers what a model reaches at a given effort, null where nothing is banked', () => {
185
- const catalog = { models: [{ id: 'terra', family: 'openai', tiers: { high: 'balanced-coder', max: 'deep-reasoner' } }] }
186
- assert.equal(tierAt(catalog, 'terra', 'max'), 'deep-reasoner')
187
- assert.equal(tierAt(catalog, 'terra', 'high'), 'balanced-coder')
188
- assert.equal(tierAt(catalog, 'terra', 'xhigh'), null) // not measured -> not claimed
189
- assert.equal(tierAt(catalog, 'nope', 'high'), null)
190
- })
191
-
192
- test('bank accepts deep-reasoner (the band below the current top end)', () => {
193
- const c = bankEntry({ models: [] }, { id: 'gpt-5.5', family: 'openai', tier: 'deep-reasoner' }, '2026-07-13')
194
- assert.deepEqual(c.models[0].tiers, { high: 'deep-reasoner' })
195
- })
196
-
197
- // ---- tier renames (2026-07-19): prior-frontier -> deep-reasoner, mid-tier -> balanced-coder ----
198
-
199
- test('bank accepts either legacy tier name and stores the current one', () => {
200
- const a = bankEntry({ models: [] }, { id: 'opus', family: 'anthropic', tier: 'prior-frontier' }, '2026-07-19')
201
- assert.deepEqual(a.models[0].tiers, { high: 'deep-reasoner' })
202
- const b = bankEntry({ models: [] }, { id: 'sonnet', family: 'anthropic', tier: 'mid-tier', effort: 'max' }, '2026-07-19')
203
- assert.deepEqual(b.models[0].tiers, { max: 'balanced-coder' })
204
- assert.deepEqual(toTiers({ tiers: { high: 'prior-frontier' } }), { high: 'deep-reasoner' }) // legacy names inside a map too
205
- assert.deepEqual(TIERS, ['frontier', 'deep-reasoner', 'balanced-coder', 'scoped-coder'])
206
- for (const t of TIERS) assert.equal(normalizeTier(t), t) // current names pass through
207
- })
208
-
209
- test('sync migrates legacy tier names AND the single-point shape, reporting both', () => {
210
- const { catalog, migrated } = syncCatalog(
211
- {
212
- models: [
213
- { id: 'opus', family: 'anthropic', tier: 'prior-frontier', lastSeenAt: '2026-07-18' },
214
- { id: 'terra', family: 'openai', tier: 'mid-tier', effort: 'max', lastSeenAt: '2026-07-18' },
215
- { id: 'fable', family: 'anthropic', tiers: { high: 'frontier' }, lastSeenAt: '2026-07-18' },
216
- ],
217
- },
218
- ['opus'],
219
- '2026-07-19',
220
- )
221
- // opus: renamed value + reshaped; terra: renamed value at its banked effort;
222
- // fable: already current -> absent from the report entirely.
223
- assert.deepEqual(migrated, [
224
- 'opus: {high:prior-frontier} -> {high:deep-reasoner}',
225
- 'terra: {max:mid-tier} -> {max:balanced-coder}',
226
- ])
227
- assert.deepEqual(catalog.models.find((m) => m.id === 'opus').tiers, { high: 'deep-reasoner' })
228
- assert.deepEqual(catalog.models.find((m) => m.id === 'terra').tiers, { max: 'balanced-coder' })
229
- assert.deepEqual(catalog.models.find((m) => m.id === 'fable').tiers, { high: 'frontier' })
230
- for (const m of catalog.models) {
231
- assert.equal(m.tier, undefined) // the legacy fields are gone, not merely shadowed
232
- assert.equal(m.effort, undefined)
233
- }
234
- })
235
-
236
- test('sync reshapes a current-name single-point entry without claiming a rename', () => {
237
- const { migrated } = syncCatalog(
238
- { models: [{ id: 'sonnet', family: 'anthropic', tier: 'balanced-coder', lastSeenAt: '2026-07-18' }] },
239
- [],
240
- '2026-07-19',
241
- )
242
- assert.deepEqual(migrated, ['sonnet: tier/effort -> tiers map {high:balanced-coder}'])
243
- })
244
-
245
- test('sync migrates a legacy entry even when it never saw it', () => {
246
- const { migrated } = syncCatalog(
247
- { models: [{ id: 'unseen', family: 'x', tier: 'prior-frontier', lastSeenAt: '2026-07-01' }] },
248
- [], // migration is not gated on discovery — it is a rename, not an observation
249
- '2026-07-19',
250
- )
251
- assert.deepEqual(migrated, ['unseen: {high:prior-frontier} -> {high:deep-reasoner}'])
252
- })
253
-
254
- // ---- CLI round-trip ----
255
-
256
- function run(args, input) {
257
- return spawnSync('node', [SCRIPT, ...args], { encoding: 'utf8', input })
258
- }
259
-
260
- test('CLI: read/sync exit 3 on an absent catalog (absent is normal)', () => {
261
- const file = tmpFile()
262
- assert.equal(run(['read', '--file', file]).status, 3)
263
- assert.equal(run(['sync', '--file', file, '--seen', 'a']).status, 3)
264
- })
265
-
266
- // @covers package:harness-runtime
267
- // @level integration
268
- test('CLI: bank creates the file; sync bumps + prunes and reports both', () => {
269
- const file = tmpFile()
270
- assert.equal(run(['bank', JSON.stringify({ id: 'a', family: 'x', tier: 'frontier' }), '--file', file, '--today', '2026-01-01']).status, 0)
271
- assert.equal(run(['bank', JSON.stringify({ id: 'b', family: 'y', tier: 'balanced-coder' }), '--file', file, '--today', '2026-07-01']).status, 0)
272
-
273
- const sync = run(['sync', '--file', file, '--seen', 'b', '--today', '2026-07-13'])
274
- assert.equal(sync.status, 0)
275
- assert.deepEqual(JSON.parse(sync.stdout), { bumped: ['b'], pruned: ['a'], migrated: [] }) // a: unseen since Jan — pruned
276
-
277
- const kept = readCatalog(file)
278
- assert.deepEqual(kept.models.map((m) => m.id), ['b'])
279
- assert.equal(kept.models[0].lastSeenAt, '2026-07-13')
280
- })
281
-
282
- test('CLI: list aliases read, help exits 0', () => {
283
- const file = tmpFile()
284
- assert.equal(run(['bank', JSON.stringify({ id: 'a', family: 'x', tier: 'frontier' }), '--file', file, '--today', '2026-07-13']).status, 0)
285
- const list = run(['list', '--file', file])
286
- assert.equal(list.status, 0)
287
- assert.equal(JSON.parse(list.stdout).models[0].id, 'a')
288
- assert.equal(run(['help']).status, 0)
289
- assert.equal(run(['bogus']).status, 2)
290
- })
291
-
292
- test('CLI: bank via --stdin round-trips through read', () => {
293
- const file = tmpFile()
294
- assert.equal(run(['bank', '--stdin', '--file', file, '--today', '2026-07-13'], JSON.stringify({ id: 's', family: 'x', tier: 'frontier' })).status, 0)
295
- const read = run(['read', '--file', file])
296
- assert.equal(read.status, 0)
297
- assert.equal(JSON.parse(read.stdout).models[0].id, 's')
298
- })
299
-
300
- // ---- per-harness catalogs (one file per harness, never shared) ----
301
-
302
- // @covers package:harness-runtime
303
- // @level unit
304
- test('pathForHarness maps the known harnesses and returns null for unknown ones', () => {
305
- assert.equal(pathForHarness('claude-code', { HOME: '/h' }), '/h/.claude/model-catalog.json')
306
- assert.equal(pathForHarness('codex', { HOME: '/h' }), '/h/.codex/model-catalog.json')
307
- assert.equal(pathForHarness('pi', { HOME: '/h' }), '/h/.pi/agent/model-catalog.json')
308
- assert.equal(pathForHarness('bogus', { HOME: '/h' }), null)
309
- })
310
-
311
- test('CLI: bank --harness stamps the file; a write from another harness is refused', () => {
312
- const file = tmpFile()
313
- assert.equal(run(['bank', JSON.stringify({ id: 'a', family: 'x', tier: 'frontier' }), '--file', file, '--harness', 'codex', '--today', '2026-07-13']).status, 0)
314
- assert.equal(readCatalog(file).harness, 'codex')
315
-
316
- // same file, different harness → refused (exit 2), catalog untouched
317
- assert.equal(run(['sync', '--file', file, '--harness', 'pi', '--seen', 'a', '--today', '2026-07-13']).status, 2)
318
- assert.equal(run(['bank', JSON.stringify({ id: 'b', family: 'y', tier: 'balanced-coder' }), '--file', file, '--harness', 'pi', '--today', '2026-07-13']).status, 2)
319
- assert.deepEqual(readCatalog(file).models.map((m) => m.id), ['a'])
320
-
321
- // matching harness → fine
322
- assert.equal(run(['sync', '--file', file, '--harness', 'codex', '--seen', 'a', '--today', '2026-07-13']).status, 0)
323
- })
324
-
325
- test('CLI: bank --harness stamps a legacy (pre-field) file at its next confirmed write', () => {
326
- const file = tmpFile()
327
- writeCatalog(file, { models: [entry('a', '2026-07-01')] }) // no harness stamp
328
- assert.equal(run(['bank', JSON.stringify({ id: 'b', family: 'y', tier: 'balanced-coder' }), '--file', file, '--harness', 'pi', '--today', '2026-07-13']).status, 0)
329
- assert.equal(readCatalog(file).harness, 'pi')
330
- })
331
-
332
- test('CLI: --harness with no location mapping errors, path --harness resolves', () => {
333
- assert.equal(run(['read', '--harness', 'bogus']).status, 2)
334
- const p = run(['path', '--harness', 'pi'])
335
- assert.equal(p.status, 0)
336
- assert.ok(p.stdout.trim().endsWith(path.join('.pi', 'agent', 'model-catalog.json')))
337
- })
338
-
339
- test('writeCatalog is atomic-ish: file parses after write, tmp file removed', () => {
340
- const file = tmpFile()
341
- writeCatalog(file, { models: [] })
342
- assert.deepEqual(JSON.parse(fs.readFileSync(file, 'utf8')), { models: [] })
343
- assert.ok(!fs.existsSync(`${file}.tmp`))
344
- })
345
-
346
- test('scoped-coder is a bankable classification but never a slot candidate', () => {
347
- // The band exists so a model that keeps turning up in discovery, but sits below the
348
- // implementer floor, can be answered ONCE instead of being tagged
349
- // "unconfirmed — please classify" every run. It must bank like any other tier...
350
- const c = bankEntry({ models: [] }, { id: 'gemini-3.5-flash', family: 'google', tier: 'scoped-coder' }, '2026-07-20')
351
- assert.deepEqual(c.models[0].tiers, { high: 'scoped-coder' })
352
-
353
- // ...and must never reach a slot.
354
- assert.ok(TIERS.includes('scoped-coder'), 'bankable')
355
- assert.ok(!SLOT_ELIGIBLE_TIERS.includes('scoped-coder'), 'not proposable for implementer/reviewer')
356
- assert.deepEqual(SLOT_ELIGIBLE_TIERS, ['frontier', 'deep-reasoner', 'balanced-coder'])
357
- })
358
-
359
- test('scoped-coder sorts below every slot-eligible band', () => {
360
- // operatingPoints sorts by TIERS.indexOf, so a mis-ordered enum would float a
361
- // scoped-coder above a balanced-coder in the proposal block and invite a bad pick.
362
- assert.equal(TIERS.indexOf('scoped-coder'), TIERS.length - 1)
363
- for (const t of SLOT_ELIGIBLE_TIERS) {
364
- assert.ok(TIERS.indexOf(t) < TIERS.indexOf('scoped-coder'), `${t} must outrank scoped-coder`)
365
- }
366
- })
@@ -1,280 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { execFileSync, spawnSync } from 'node:child_process'
4
- import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, utimesSync, 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 { EVIDENCE_MAX_BYTES, planSweep } from './cleanup-runs.mjs'
10
-
11
- const script = join(dirname(fileURLToPath(import.meta.url)), 'cleanup-runs.mjs')
12
- const DAY_MS = 24 * 60 * 60 * 1000
13
-
14
- const git = (dir, ...args) => execFileSync('git', ['-C', dir, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
15
-
16
- function write(path, content = 'x') {
17
- mkdirSync(dirname(path), { recursive: true })
18
- writeFileSync(path, content)
19
- }
20
-
21
- // A clone of the remote at `dir`, with ignored installs, caches, and a test log.
22
- function clone(remote, dir) {
23
- git(dirname(dir), 'clone', '--quiet', remote, dir)
24
- git(dir, 'config', 'user.email', 't@example.com')
25
- git(dir, 'config', 'user.name', 'T')
26
- write(join(dir, 'node_modules', 'pkg', 'package.json'), '{}')
27
- write(join(dir, '.cache', 'sdk', 'system.img'), 'big')
28
- write(join(dir, 'test-results', 'journey.log'), 'emulator boot failed')
29
- write(join(dir, 'test-results', 'avd', 'Charge.avd', 'userdata.img'), 'big')
30
- return dir
31
- }
32
-
33
- function fixture() {
34
- const base = mkdtempSync(join(tmpdir(), 'cleanup-runs-'))
35
- const remote = join(base, 'remote.git')
36
- const seed = join(base, 'seed')
37
- execFileSync('git', ['init', '--quiet', '--bare', remote])
38
- execFileSync('git', ['init', '--quiet', seed])
39
- git(seed, 'config', 'user.email', 't@example.com')
40
- git(seed, 'config', 'user.name', 'T')
41
- write(join(seed, '.gitignore'), 'node_modules/\n.cache/\ntest-results/\n')
42
- write(join(seed, 'app.js'), 'v1')
43
- write(join(seed, 'docs', 'README.md'), '# tracked doc')
44
- git(seed, 'add', '.')
45
- git(seed, 'commit', '--quiet', '-m', 'seed')
46
- git(seed, 'push', '--quiet', remote, 'HEAD:main')
47
- git(remote, 'symbolic-ref', 'HEAD', 'refs/heads/main')
48
- const repo = join(base, 'repo')
49
- mkdirSync(repo)
50
- return { base, remote, repo, rush: join(repo, '.resolve-issues', 'rush') }
51
- }
52
-
53
- function makeRun(rush, name, idleDays) {
54
- const dir = join(rush, name)
55
- write(join(dir, 'task-plan.json'), '{}')
56
- const when = new Date(Date.now() - idleDays * DAY_MS)
57
- utimesSync(join(dir, 'task-plan.json'), when, when)
58
- return dir
59
- }
60
-
61
- const cli = (...args) => spawnSync(process.execPath, [script, ...args], { encoding: 'utf8' })
62
- const has = (dir, rel) => existsSync(join(dir, rel))
63
-
64
- test('an idle run keeps records and logs, and loses downloads, caches, and clean source', () => {
65
- const { remote, repo, rush } = fixture()
66
- const run = makeRun(rush, 'old', 10)
67
- write(join(run, 'exploration', 'findings.json'), '{}')
68
- write(join(run, 'reviews', '1131-r1', 'review.md'), 'ok')
69
- write(join(run, 'reviews', '1131-r1', 'sandbox', 'sdk', 'system.img'), 'big')
70
- write(join(run, 'reviews', '1131-r1', 'sandbox', 'worker.log'), 'review log')
71
- write(join(run, 'diagnoses', '1136', 'deep', 'nested', 'adb.txt'), 'adb not ready')
72
- write(join(run, '.tmp', 'resume-result.json'), '{}')
73
- write(join(run, 'jdk17.tar.gz'), 'big')
74
- mkdirSync(join(run, 'units'), { recursive: true })
75
- const ws = clone(remote, join(run, 'units', '1132'))
76
-
77
- const result = cli('--repo', repo, '--apply')
78
- assert.equal(result.status, 0, result.stderr)
79
-
80
- for (const kept of ['task-plan.json', 'exploration/findings.json', 'reviews/1131-r1/review.md', 'reviews/1131-r1/sandbox/worker.log', 'diagnoses/1136/deep/nested/adb.txt', '.tmp/resume-result.json']) {
81
- assert.ok(has(run, kept), `${kept} should be kept`)
82
- }
83
- for (const gone of ['reviews/1131-r1/sandbox/sdk', 'jdk17.tar.gz']) assert.ok(!has(run, gone), `${gone} should be removed`)
84
-
85
- // The clean clone keeps only its logs: no source, .git, installs, caches, or emulator disks.
86
- assert.ok(has(ws, 'test-results/journey.log'))
87
- for (const gone of ['app.js', 'docs', '.git', '.gitignore', 'node_modules', '.cache', 'test-results/avd']) assert.ok(!has(ws, gone), `clone ${gone} should be removed`)
88
- assert.deepEqual(readdirSync(ws), ['test-results'])
89
- assert.match(result.stdout, /sweep\s+units\/1132 \(clean and pushed; keeps 1 log file\(s\)/)
90
- assert.match(result.stdout.trim().split('\n').at(-1), /^cleanup-runs: 1 idle run\(s\), 0 active; .*1 clean clone\(s\) emptied.*log file\(s\) kept; freed/)
91
- })
92
-
93
- test('a clone with unpushed commits, edits, or a stash keeps its work and logs, and loses ignored downloads', () => {
94
- const { remote, repo, rush } = fixture()
95
- const run = makeRun(rush, 'old', 10)
96
- mkdirSync(join(run, 'units'), { recursive: true })
97
-
98
- const committed = clone(remote, join(run, 'units', 'committed'))
99
- write(join(committed, 'app.js'), 'v2')
100
- git(committed, 'commit', '--quiet', '-am', 'local only')
101
-
102
- const edited = clone(remote, join(run, 'units', 'edited'))
103
- write(join(edited, 'new.js'), 'untracked work')
104
-
105
- const stashed = clone(remote, join(run, 'units', 'stashed'))
106
- write(join(stashed, 'app.js'), 'stashed work')
107
- git(stashed, 'stash', '--quiet')
108
-
109
- const result = cli('--repo', repo, '--apply')
110
- assert.equal(result.status, 0, result.stderr)
111
-
112
- assert.equal(git(committed, 'rev-list', '--count', 'HEAD', '--not', '--remotes').trim(), '1')
113
- assert.ok(has(edited, 'new.js'))
114
- assert.equal(git(stashed, 'stash', 'list').split('\n').filter(Boolean).length, 1)
115
- for (const dir of [committed, edited, stashed]) {
116
- assert.ok(has(dir, 'app.js') && has(dir, 'docs/README.md'), `${dir} keeps tracked files`)
117
- assert.ok(has(dir, 'test-results/journey.log'), `${dir} keeps its log`)
118
- for (const gone of ['node_modules', '.cache', 'test-results/avd']) assert.ok(!has(dir, gone), `${dir} ${gone} should be stripped`)
119
- }
120
- assert.match(result.stdout, /strip\s+units\/committed \(keeps 1 unpushed commit\(s\) and 1 log file\(s\)/)
121
- })
122
-
123
- test('past the log window only records remain; clones with work still keep their work', () => {
124
- const { remote, repo, rush } = fixture()
125
- const run = makeRun(rush, 'ancient', 40)
126
- write(join(run, 'reviews', '1131-r1.json'), '{}')
127
- write(join(run, 'reviews', '1131-r1', 'sandbox', 'worker.log'), 'old log')
128
- mkdirSync(join(run, 'units'), { recursive: true })
129
- const clean = clone(remote, join(run, 'units', 'clean'))
130
- const busy = clone(remote, join(run, 'units', 'busy'))
131
- write(join(busy, 'app.js'), 'v2')
132
- git(busy, 'commit', '--quiet', '-am', 'local only')
133
-
134
- const result = cli('--repo', repo, '--log-days', '30', '--apply')
135
- assert.equal(result.status, 0, result.stderr)
136
- assert.match(result.stdout, /records only, past 30 days/)
137
- assert.ok(has(run, 'reviews/1131-r1.json'))
138
- assert.ok(!has(run, 'reviews/1131-r1'), 'the old log and its folder go')
139
- assert.ok(!existsSync(clean), 'a clean clone with no logs left is deleted')
140
- assert.ok(has(busy, 'app.js'))
141
- assert.ok(!has(busy, 'test-results'), 'its ignored log goes too')
142
- })
143
-
144
- test('text inside a tool cache is not evidence, and an oversized log is dropped', () => {
145
- const { repo, rush } = fixture()
146
- const run = makeRun(rush, 'old', 10)
147
- write(join(run, 'home', '.npm', '_cacache', 'index.json'), '{}')
148
- write(join(run, 'xdg-cache', 'pnpm', 'metadata.jsonl'), '{}')
149
- write(join(run, 'builds', 'Pixel.avd', 'config.txt'), 'x')
150
- write(join(run, 'logs', 'huge.log'), Buffer.alloc(EVIDENCE_MAX_BYTES + 1))
151
- write(join(run, 'logs', 'small.log'), 'ok')
152
-
153
- assert.equal(cli('--repo', repo, '--apply').status, 0)
154
- for (const gone of ['home', 'xdg-cache', 'builds', 'logs/huge.log']) assert.ok(!has(run, gone), `${gone} should be removed`)
155
- assert.ok(has(run, 'logs/small.log'))
156
- })
157
-
158
- test('a clone hidden inside a dot directory is still protected', () => {
159
- const { remote, repo, rush } = fixture()
160
- const run = makeRun(rush, 'old', 10)
161
- mkdirSync(join(run, '.tmp'), { recursive: true })
162
- const hidden = clone(remote, join(run, '.tmp', 'wip'))
163
- write(join(hidden, 'app.js'), 'v2')
164
- git(hidden, 'commit', '--quiet', '-am', 'local only')
165
-
166
- assert.equal(cli('--repo', repo, '--apply').status, 0)
167
- assert.ok(has(hidden, 'app.js'))
168
- assert.ok(!has(hidden, 'node_modules'))
169
- })
170
-
171
- test('a run used recently is left alone, and a dry run removes nothing', () => {
172
- const { remote, repo, rush } = fixture()
173
- const active = makeRun(rush, 'active', 1)
174
- write(join(active, 'units', 'scratch', 'big.img'), 'big')
175
- const idle = makeRun(rush, 'idle', 10)
176
- mkdirSync(join(idle, 'units'), { recursive: true })
177
- clone(remote, join(idle, 'units', 'a'))
178
-
179
- const dry = cli('--repo', repo)
180
- assert.equal(dry.status, 0, dry.stderr)
181
- assert.ok(has(idle, 'units/a/node_modules'))
182
- assert.match(dry.stdout.trim().split('\n').at(-1), /dry run, nothing removed.*Rerun with --apply/)
183
-
184
- assert.equal(cli('--repo', repo, '--older-than-days', '3', '--apply').status, 0)
185
- assert.ok(has(active, 'units/scratch/big.img'))
186
- assert.ok(!has(idle, 'units/a/node_modules'))
187
- })
188
-
189
- test('runs are staged by task-plan.json age', () => {
190
- const { repo, rush } = fixture()
191
- makeRun(rush, 'a-two', 2)
192
- makeRun(rush, 'b-ten', 10)
193
- makeRun(rush, 'c-forty', 40)
194
- const stages = planSweep({ repo, olderThanDays: 3, logDays: 30 }).runs.map((r) => [r.run, r.stage])
195
- assert.deepEqual(stages, [['a-two', 'active'], ['b-ten', 'keep-logs'], ['c-forty', 'records-only']])
196
- assert.deepEqual(planSweep({ repo: join(repo, 'missing') }).runs, [])
197
- })
198
-
199
- test('--merged empties a merged clean workspace to its logs, even with commits that look unpushed', () => {
200
- const { remote, rush } = fixture()
201
- const run = makeRun(rush, 'now', 0)
202
- mkdirSync(join(run, 'units'), { recursive: true })
203
- const ws = clone(remote, join(run, 'units', 'merged'))
204
- write(join(ws, 'app.js'), 'v2')
205
- git(ws, 'commit', '--quiet', '-am', 'squash-merged upstream')
206
-
207
- const result = cli('--merged', ws, '--apply')
208
- assert.equal(result.status, 0, result.stderr)
209
- assert.deepEqual(readdirSync(ws), ['test-results'])
210
- assert.ok(has(ws, 'test-results/journey.log'))
211
- })
212
-
213
- test('--merged keeps uncommitted edits and refuses paths outside .resolve-issues', () => {
214
- const { base, remote, rush } = fixture()
215
- const run = makeRun(rush, 'now', 0)
216
- mkdirSync(join(run, 'units'), { recursive: true })
217
- const ws = clone(remote, join(run, 'units', 'dirty'))
218
- write(join(ws, 'app.js'), 'uncommitted')
219
-
220
- assert.equal(cli('--merged', ws, '--apply').status, 0)
221
- assert.ok(has(ws, 'app.js') && has(ws, 'test-results/journey.log'))
222
- assert.ok(!has(ws, 'node_modules'))
223
-
224
- const outside = clone(remote, join(base, 'elsewhere'))
225
- const refused = cli('--merged', outside, '--apply')
226
- assert.equal(refused.status, 2)
227
- assert.match(refused.stderr, /only removes workspaces under a \.resolve-issues directory/)
228
- assert.ok(existsSync(outside))
229
- })
230
-
231
- test('a bad command exits 2 with usage', () => {
232
- for (const args of [['--older-than-days', 'soon'], ['--bogus'], ['--repo'], ['--log-days', '-1']]) {
233
- const result = cli(...args)
234
- assert.equal(result.status, 2)
235
- assert.match(result.stderr, /^usage: cleanup-runs\.mjs/)
236
- }
237
- const inverted = cli('--older-than-days', '10', '--log-days', '5')
238
- assert.equal(inverted.status, 2)
239
- assert.match(inverted.stderr, /--log-days must be at least --older-than-days/)
240
- })
241
-
242
- test('in a non-Git copy of a repository, only logs and text under evidence folders are kept', () => {
243
- const { repo, rush } = fixture()
244
- const run = makeRun(rush, 'old', 10)
245
- const copy = join(run, 'units', '1132', 'test-results', 'packet', 'build-x', 'workspace')
246
- write(join(copy, 'pnpm-lock.yaml'), 'lock')
247
- write(join(copy, 'package.json'), '{}')
248
- write(join(copy, 'docs', 'design.md'), '# copied source doc')
249
- write(join(copy, 'build.log'), 'gradle failed')
250
- write(join(copy, 'test-results', 'summary.json'), '{}')
251
- write(join(run, 'units', '1132', 'test-results', 'packet', 'gradle', 'caches', 'meta.json'), '{}')
252
- write(join(run, 'units', '1132', 'test-results', 'packet', 'result.json'), '{}')
253
-
254
- assert.equal(cli('--repo', repo, '--apply').status, 0)
255
- for (const kept of ['build.log', 'test-results/summary.json']) assert.ok(has(copy, kept), `${kept} should be kept`)
256
- for (const gone of ['package.json', 'pnpm-lock.yaml', 'docs']) assert.ok(!has(copy, gone), `${gone} should be removed`)
257
- assert.ok(!has(run, 'units/1132/test-results/packet/gradle'))
258
- assert.ok(has(run, 'units/1132/test-results/packet/result.json'))
259
- })
260
-
261
- test('read-only package stores are removed, not left to abort the run', () => {
262
- const { repo, rush } = fixture()
263
- const run = makeRun(rush, 'old', 10)
264
- const store = join(run, 'artifacts', 'pnpm-v11', 'v11')
265
- write(join(store, 'files', '61', 'abc'), 'blob')
266
- write(join(store, 'index.db'), 'db')
267
- for (const p of [join(store, 'files', '61', 'abc'), join(store, 'index.db')]) chmodSync(p, 0o444)
268
- write(join(run, 'artifacts', 'pnpm-tuple', 'store', 'blob'), 'x')
269
- write(join(run, 'artifacts', 'pnpm-tuple', 'install.log'), 'kept log')
270
- for (const d of [join(store, 'files', '61'), join(store, 'files'), store, join(run, 'artifacts', 'pnpm-tuple', 'store'), join(run, 'artifacts', 'pnpm-tuple')]) chmodSync(d, 0o555)
271
- write(join(run, 'zz', 'worker.log'), 'kept')
272
-
273
- const result = cli('--repo', repo, '--apply')
274
- assert.equal(result.status, 0, result.stderr)
275
- assert.ok(!has(run, 'artifacts/pnpm-v11'), 'the read-only store is gone')
276
- assert.ok(!has(run, 'artifacts/pnpm-tuple/store'), 'a store inside a read-only folder is gone')
277
- assert.ok(has(run, 'artifacts/pnpm-tuple/install.log'), 'its sibling log stays')
278
- assert.ok(has(run, 'zz/worker.log'), 'later paths are still processed')
279
- assert.doesNotMatch(result.stdout, /could not be removed/)
280
- })