@1aboveio/skills 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/.codex-plugin/plugin.json +13 -0
  2. package/.codex-plugin/resources.json +1827 -0
  3. package/README.md +22 -0
  4. package/package.json +9 -2
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/airflow/release-check.mjs +3 -1
  7. package/skills/app-dev/android-foundation/SKILL.md +92 -34
  8. package/skills/app-dev/android-foundation/assets/android-ci-job.yml +13 -7
  9. package/skills/app-dev/android-foundation/references/ci.md +23 -13
  10. package/skills/app-dev/android-foundation/references/delivery-focus.md +29 -0
  11. package/skills/app-dev/android-foundation/references/diagnosis.md +136 -0
  12. package/skills/app-dev/android-foundation/references/gotchas.md +20 -11
  13. package/skills/app-dev/android-foundation/references/setup.md +15 -6
  14. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  15. package/skills/app-dev/android-foundation/scripts/device-run.sh +148 -42
  16. package/skills/app-dev/app-debug/SKILL.md +1 -1
  17. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  18. package/skills/app-dev/better-auth/SKILL.md +1 -1
  19. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  20. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  21. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  22. package/skills/app-dev/data-quality/SKILL.md +1 -1
  23. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  24. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  25. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  26. package/skills/app-dev/monorepo/SKILL.md +1 -1
  27. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  28. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  29. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  30. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  31. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  32. package/skills/app-dev/pyspark/SKILL.md +1 -1
  33. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  34. package/skills/app-dev/python-backend/SKILL.md +1 -1
  35. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  36. package/skills/app-dev/shadcn/SKILL.md +1 -1
  37. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  38. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  39. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  40. package/skills/cicd/cloud-build/SKILL.md +1 -1
  41. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  42. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  43. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  44. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  45. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  46. package/skills/cicd/docker/SKILL.md +1 -1
  47. package/skills/cicd/docker/release-check.mjs +3 -1
  48. package/skills/cicd/github-actions/SKILL.md +1 -1
  49. package/skills/cicd/github-actions/release-check.mjs +3 -1
  50. package/skills/cicd/github-flow/SKILL.md +1 -1
  51. package/skills/cicd/github-flow/release-check.mjs +3 -1
  52. package/skills/cicd/google-cloud/SKILL.md +1 -1
  53. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  54. package/skills/cicd/mergify/SKILL.md +1 -1
  55. package/skills/cicd/mergify/release-check.mjs +3 -1
  56. package/skills/cicd/podman/SKILL.md +1 -1
  57. package/skills/cicd/podman/release-check.mjs +3 -1
  58. package/skills/content/article-review/SKILL.md +1 -1
  59. package/skills/content/article-review/release-check.mjs +3 -1
  60. package/skills/content/article-to-image/SKILL.md +1 -1
  61. package/skills/content/article-to-image/release-check.mjs +3 -1
  62. package/skills/content/flowchart-svg/SKILL.md +1 -1
  63. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  64. package/skills/content/image-gen/SKILL.md +1 -1
  65. package/skills/content/image-gen/release-check.mjs +3 -1
  66. package/skills/content/mermaid/SKILL.md +1 -1
  67. package/skills/content/mermaid/release-check.mjs +3 -1
  68. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  69. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  70. package/skills/content/wechat-publisher/SKILL.md +1 -1
  71. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  72. package/skills/content/wechat-search/SKILL.md +1 -1
  73. package/skills/content/wechat-search/release-check.mjs +3 -1
  74. package/skills/content/xai-search/SKILL.md +88 -13
  75. package/skills/content/xai-search/release-check.mjs +3 -1
  76. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  77. package/skills/core/distill-writing/SKILL.md +1 -1
  78. package/skills/core/distill-writing/release-check.mjs +3 -1
  79. package/skills/core/doc-to-md/SKILL.md +1 -1
  80. package/skills/core/doc-to-md/release-check.mjs +3 -1
  81. package/skills/core/editorial-review/SKILL.md +1 -1
  82. package/skills/core/editorial-review/release-check.mjs +3 -1
  83. package/skills/core/house-style/SKILL.md +1 -1
  84. package/skills/core/house-style/release-check.mjs +3 -1
  85. package/skills/core/plain-language/SKILL.md +1 -1
  86. package/skills/core/plain-language/release-check.mjs +3 -1
  87. package/skills/core/polish-cn/SKILL.md +1 -1
  88. package/skills/core/polish-cn/release-check.mjs +3 -1
  89. package/skills/core/reg-parser/SKILL.md +1 -1
  90. package/skills/core/reg-parser/release-check.mjs +3 -1
  91. package/skills/core/terminology/SKILL.md +1 -1
  92. package/skills/core/terminology/release-check.mjs +3 -1
  93. package/skills/core/write-doc/SKILL.md +1 -1
  94. package/skills/core/write-doc/release-check.mjs +3 -1
  95. package/skills/engineering/better-review/SKILL.md +1 -1
  96. package/skills/engineering/better-review/release-check.mjs +3 -1
  97. package/skills/engineering/contributing/SKILL.md +1 -1
  98. package/skills/engineering/contributing/release-check.mjs +3 -1
  99. package/skills/engineering/e2e-test/SKILL.md +1 -1
  100. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  101. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  102. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  103. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  104. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  105. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  106. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  107. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  108. package/skills/engineering/golden-path/SKILL.md +1 -1
  109. package/skills/engineering/golden-path/release-check.mjs +3 -1
  110. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  111. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  112. package/skills/engineering/resolve-issues/SKILL.md +11 -20
  113. package/skills/engineering/resolve-issues/references/delivery-progress.md +63 -0
  114. package/skills/engineering/resolve-issues/references/expire.md +24 -11
  115. package/skills/engineering/resolve-issues/references/implementation.md +9 -1
  116. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  117. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  118. package/skills/engineering/resolve-issues/references/task-plan.md +12 -5
  119. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  120. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  121. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  122. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  123. package/skills/engineering/resolve-release/SKILL.md +1 -1
  124. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  125. package/skills/engineering/rush-issues/SKILL.md +5 -2
  126. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  127. package/skills/engineering/rush-release/SKILL.md +1 -1
  128. package/skills/engineering/rush-release/release-check.mjs +3 -1
  129. package/skills/engineering/test-quality/SKILL.md +1 -1
  130. package/skills/engineering/test-quality/release-check.mjs +3 -1
  131. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  132. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  133. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  134. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  135. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  136. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  137. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  138. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  139. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  140. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  141. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  142. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  143. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  144. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  145. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  146. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  147. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -172
  148. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  149. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  150. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  151. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  152. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  153. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  154. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  155. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  156. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  157. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  158. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  159. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  160. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  161. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  162. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  163. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  164. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  165. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  166. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  167. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  168. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  169. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  170. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  171. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  172. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  173. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  174. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  175. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  176. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  177. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  178. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  179. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  180. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  181. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  182. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  183. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  184. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  185. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  186. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  187. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  188. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  189. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  190. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  191. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  192. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  193. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  194. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  195. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  196. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  197. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  198. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  199. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  200. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  201. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  202. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  203. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  204. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  205. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  206. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  207. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  208. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  209. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  210. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  211. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  212. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  213. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  214. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  215. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  216. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  217. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  218. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  219. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  220. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  221. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  222. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  223. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  224. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
@@ -1,106 +0,0 @@
1
- // Unit tests for the pure overflow predicate (issue #195). Run: `node --test`.
2
- // The Playwright wrapper (assertNoHorizontalOverflow) needs a real browser and is
3
- // exercised downstream; this proves the detection logic without one.
4
-
5
- import { test } from 'node:test'
6
- import assert from 'node:assert/strict'
7
- import {
8
- findHorizontalOverflow, PRESENTATION_VIEWPORT_FLOOR,
9
- rectsIntersect, findOverlaps, applyOverlapAllowlist,
10
- } from './presentation-checks.mjs'
11
-
12
- // helper: build an element with a rect + interactive flag
13
- const el = (selector, [left, top, right, bottom], interactive = false) =>
14
- ({ selector, rect: { left, top, right, bottom, width: right - left, height: bottom - top }, interactive })
15
-
16
- const VW = 375
17
-
18
- test('no overflow when everything fits', () => {
19
- const els = [
20
- { selector: 'header', left: 0, right: 375, width: 375, height: 60 },
21
- { selector: '.card', left: 16, right: 359, width: 343, height: 120 },
22
- ]
23
- assert.deepEqual(findHorizontalOverflow(els, VW), [])
24
- })
25
-
26
- test('flags an element wider than the viewport (right overflow)', () => {
27
- const els = [{ selector: '.wide-table', left: 0, right: 520, width: 520, height: 200 }]
28
- const out = findHorizontalOverflow(els, VW)
29
- assert.equal(out.length, 1)
30
- assert.equal(out[0].selector, '.wide-table')
31
- assert.equal(out[0].overflowPx, 145) // 520 - 375
32
- })
33
-
34
- test('flags a negatively-positioned element (left overflow)', () => {
35
- const els = [{ selector: '#drawer', left: -120, right: 200, width: 320, height: 400 }]
36
- const out = findHorizontalOverflow(els, VW)
37
- assert.equal(out.length, 1)
38
- assert.equal(out[0].overflowPx, 120)
39
- })
40
-
41
- test('respects the tolerance band (sub-pixel rounding does not flag)', () => {
42
- const els = [{ selector: '.x', left: 0, right: 375.5, width: 375.5, height: 10 }]
43
- assert.deepEqual(findHorizontalOverflow(els, VW, { tolerancePx: 1 }), [])
44
- })
45
-
46
- test('ignore list suppresses intentional overflow by selector substring', () => {
47
- const els = [{ selector: '.carousel-track', left: 0, right: 900, width: 900, height: 150 }]
48
- assert.deepEqual(findHorizontalOverflow(els, VW, { ignore: ['carousel'] }), [])
49
- })
50
-
51
- test('zero-size (unrendered) elements are skipped', () => {
52
- const els = [{ selector: '.hidden', left: 0, right: 999, width: 0, height: 0 }]
53
- assert.deepEqual(findHorizontalOverflow(els, VW), [])
54
- })
55
-
56
- test('viewport floor is the documented 375 + 1280 pair', () => {
57
- assert.deepEqual(PRESENTATION_VIEWPORT_FLOOR.map((v) => v.width), [375, 1280])
58
- })
59
-
60
- // ---------- overlap detection (presentation:spatial) ----------
61
-
62
- test('rectsIntersect: overlapping yes, separated no, touching (within tol) no', () => {
63
- const a = { left: 0, top: 0, right: 100, bottom: 50 }
64
- assert.equal(rectsIntersect(a, { left: 50, top: 25, right: 150, bottom: 75 }), true)
65
- assert.equal(rectsIntersect(a, { left: 200, top: 0, right: 300, bottom: 50 }), false)
66
- assert.equal(rectsIntersect(a, { left: 100, top: 0, right: 200, bottom: 50 }), false) // edge-touch
67
- })
68
-
69
- test('findOverlaps: flags a collision but skips pure nesting', () => {
70
- const parent = el('.card', [0, 0, 200, 200])
71
- const child = el('.card .title', [10, 10, 190, 40]) // nested inside parent
72
- const stray = el('.badge', [180, 180, 260, 260]) // overlaps parent corner
73
- const pairs = findOverlaps([parent, child, stray])
74
- // parent⨯child is nesting (skipped); parent⨯stray is a real overlap
75
- assert.equal(pairs.length, 1)
76
- assert.equal(pairs[0].b, '.badge')
77
- })
78
-
79
- test('applyOverlapAllowlist: gated suppression yields a census entry', () => {
80
- const pairs = [{ a: '.carousel-track', b: '.carousel-slide', aInteractive: false, bInteractive: false }]
81
- const r = applyOverlapAllowlist(pairs, [{ match: 'carousel', category: 'decorative', reason: 'z-stacked slides' }])
82
- assert.equal(r.reported.length, 0)
83
- assert.equal(r.suppressed.length, 1)
84
- assert.equal(r.suppressed[0].reason, 'z-stacked slides')
85
- })
86
-
87
- test('applyOverlapAllowlist: entry without category/reason is rejected (still fails)', () => {
88
- const pairs = [{ a: '.x', b: '.y', aInteractive: false, bInteractive: false }]
89
- const r = applyOverlapAllowlist(pairs, [{ match: '.x' }])
90
- assert.equal(r.suppressed.length, 0)
91
- assert.equal(r.rejected.length, 1)
92
- })
93
-
94
- test('applyOverlapAllowlist: cannot allowlist an overlap that occludes an interactive element', () => {
95
- const pairs = [{ a: '.overlay', b: 'button#save', aInteractive: false, bInteractive: true }]
96
- const r = applyOverlapAllowlist(pairs, [{ match: '.overlay', category: 'decorative', reason: 'hero flourish' }])
97
- assert.equal(r.suppressed.length, 0)
98
- assert.equal(r.rejected.length, 1)
99
- assert.match(r.rejected[0].why, /interactive/)
100
- })
101
-
102
- test('applyOverlapAllowlist: unmatched overlap is reported', () => {
103
- const pairs = [{ a: '.a', b: '.b', aInteractive: false, bInteractive: false }]
104
- const r = applyOverlapAllowlist(pairs, [{ match: '.zzz', category: 'decorative', reason: 'n/a' }])
105
- assert.equal(r.reported.length, 1)
106
- })
@@ -1,117 +0,0 @@
1
- // Unit tests for the pure Perceivability cores (issue #196). Run: `node --test`.
2
- // The Playwright wrappers (assertContrast / assertHiddenFromWrongRole /
3
- // assertRemovedAfterMutation) need a real browser + @axe-core/playwright and are
4
- // exercised downstream; the contrast math, axe classifier, and predicates are
5
- // proven here without one.
6
-
7
- import { test } from 'node:test'
8
- import assert from 'node:assert/strict'
9
- import {
10
- relativeLuminance, contrastRatio, meetsContrast, classifyAxeContrast,
11
- isEffectivelyHidden, isExemptFromContrast, assessElementContrast, AbstainError,
12
- } from './presentation-perceivability.mjs'
13
-
14
- const WHITE = [255, 255, 255]
15
- const BLACK = [0, 0, 0]
16
-
17
- // ---------- WCAG math ----------
18
-
19
- test('relative luminance: white ~1, black 0', () => {
20
- assert.ok(Math.abs(relativeLuminance(WHITE) - 1) < 1e-9)
21
- assert.equal(relativeLuminance(BLACK), 0)
22
- })
23
-
24
- test('contrast ratio: black on white is 21:1, white on white is 1:1', () => {
25
- assert.ok(Math.abs(contrastRatio(BLACK, WHITE) - 21) < 1e-6)
26
- assert.ok(Math.abs(contrastRatio(WHITE, WHITE) - 1) < 1e-9)
27
- assert.equal(contrastRatio(BLACK, WHITE), contrastRatio(WHITE, BLACK)) // symmetric
28
- })
29
-
30
- test('meetsContrast: black text on white passes AA', () => {
31
- const r = meetsContrast(BLACK, WHITE)
32
- assert.equal(r.threshold, 4.5)
33
- assert.equal(r.pass, true)
34
- })
35
-
36
- test('meetsContrast: a faint gray fails both text and non-text', () => {
37
- const faint = [170, 170, 170] // ~2.3:1 on white
38
- assert.equal(meetsContrast(faint, WHITE).pass, false)
39
- assert.equal(meetsContrast(faint, WHITE, { nonText: true }).pass, false)
40
- })
41
-
42
- test('meetsContrast: threshold flips between text (4.5) and non-text (3.0)', () => {
43
- const mid = [130, 130, 130] // ~3.8:1 on white — between 3 and 4.5
44
- const asText = meetsContrast(mid, WHITE)
45
- const asUi = meetsContrast(mid, WHITE, { nonText: true })
46
- assert.equal(asText.threshold, 4.5)
47
- assert.equal(asText.pass, false, 'fails as normal text')
48
- assert.equal(asUi.threshold, 3)
49
- assert.equal(asUi.pass, true, 'passes as a UI component / large text')
50
- })
51
-
52
- // ---------- axe classifier ----------
53
-
54
- test('classifyAxeContrast: no findings → pass', () => {
55
- assert.equal(classifyAxeContrast({ violations: [], incomplete: [] }).status, 'pass')
56
- assert.equal(classifyAxeContrast({}).status, 'pass')
57
- })
58
-
59
- test('classifyAxeContrast: a color-contrast violation → fail', () => {
60
- const res = { violations: [{ id: 'color-contrast', nodes: [{ target: ['.btn'] }] }], incomplete: [] }
61
- const c = classifyAxeContrast(res)
62
- assert.equal(c.status, 'fail')
63
- assert.equal(c.count, 1)
64
- })
65
-
66
- test('classifyAxeContrast: incomplete (gradient/image) → abstain, not pass', () => {
67
- const res = { violations: [], incomplete: [{ id: 'color-contrast', nodes: [{ target: ['.hero'] }] }] }
68
- assert.equal(classifyAxeContrast(res).status, 'abstain')
69
- })
70
-
71
- test('classifyAxeContrast: violation outranks incomplete; other rule ids ignored', () => {
72
- const res = {
73
- violations: [{ id: 'color-contrast', nodes: [{}] }, { id: 'label', nodes: [{}] }],
74
- incomplete: [{ id: 'color-contrast', nodes: [{}] }],
75
- }
76
- const c = classifyAxeContrast(res)
77
- assert.equal(c.status, 'fail')
78
- assert.equal(c.count, 1, 'only color-contrast nodes counted')
79
- })
80
-
81
- // ---------- visibility / exemption predicates ----------
82
-
83
- test('isEffectivelyHidden: catches display/visibility/opacity/aria/zero-size', () => {
84
- assert.equal(isEffectivelyHidden({ style: { display: 'none' } }), true)
85
- assert.equal(isEffectivelyHidden({ style: { visibility: 'hidden' } }), true)
86
- assert.equal(isEffectivelyHidden({ style: { opacity: '0' } }), true)
87
- assert.equal(isEffectivelyHidden({ ariaHidden: true }), true)
88
- assert.equal(isEffectivelyHidden({ rect: { width: 0, height: 10 } }), true)
89
- })
90
-
91
- test('isEffectivelyHidden: a normal visible element is not hidden', () => {
92
- assert.equal(isEffectivelyHidden({ style: { display: 'block', visibility: 'visible', opacity: '1' }, rect: { width: 100, height: 40 } }), false)
93
- })
94
-
95
- test('isExemptFromContrast: disabled controls are exempt (WCAG), normal are not', () => {
96
- assert.equal(isExemptFromContrast({ disabled: true }), true)
97
- assert.equal(isExemptFromContrast({ ariaDisabled: true }), true)
98
- assert.equal(isExemptFromContrast({}), false)
99
- })
100
-
101
- test('assessElementContrast (manual/escalation path) composes exemption + threshold', () => {
102
- // disabled → exempt, regardless of (bad) colors
103
- assert.equal(assessElementContrast(WHITE, WHITE, { disabled: true }).status, 'exempt')
104
- // enabled, good contrast → pass
105
- assert.equal(assessElementContrast(BLACK, WHITE).status, 'pass')
106
- // enabled, faint gray → fail, with ratio reported
107
- const fail = assessElementContrast([170, 170, 170], WHITE)
108
- assert.equal(fail.status, 'fail')
109
- assert.ok(fail.ratio < fail.threshold)
110
- })
111
-
112
- test('AbstainError is distinguishable from a hard failure', () => {
113
- const e = new AbstainError('x', { count: 2 })
114
- assert.equal(e.abstain, true)
115
- assert.equal(e.name, 'AbstainError')
116
- assert.ok(e instanceof Error)
117
- })
@@ -1,69 +0,0 @@
1
- // Unit tests for the pure Reachability & Affordance cores (issue #197).
2
- // Run: `node --test`. The Playwright wrappers (crawlReachableRoutes,
3
- // assertNoNavOrphans, assertTargetSizes, assertFocusVisible, assertModalTrapsFocus,
4
- // assertRouteNotReachable) need a real browser and are exercised downstream.
5
-
6
- import { test } from 'node:test'
7
- import assert from 'node:assert/strict'
8
- import { findNavOrphans, meetsTargetSize, hasVisibleFocusIndicator } from './presentation-reachability.mjs'
9
-
10
- // ---------- nav-orphan set diff ----------
11
-
12
- test('findNavOrphans: routes in manifest but not reachable are orphans', () => {
13
- const manifest = ['/', '/dashboard', '/settings', '/secret-report']
14
- const reachable = ['/', '/dashboard', '/settings']
15
- assert.deepEqual(findNavOrphans(manifest, reachable), ['/secret-report'])
16
- })
17
-
18
- test('findNavOrphans: none when everything is reachable', () => {
19
- assert.deepEqual(findNavOrphans(['/', '/a'], ['/', '/a', '/extra']), [])
20
- })
21
-
22
- test('findNavOrphans: ignore by exact id and by prefix glob', () => {
23
- const manifest = ['/', '/auth/callback', '/admin/secret', '/deep/x', '/deep/y']
24
- const reachable = ['/']
25
- // exact ignore for the callback, prefix ignore for everything under /deep
26
- const orphans = findNavOrphans(manifest, reachable, { ignore: ['/auth/callback', '/deep/*'] })
27
- assert.deepEqual(orphans, ['/admin/secret'])
28
- })
29
-
30
- // ---------- target size ----------
31
-
32
- test('meetsTargetSize: 44x44 passes, smaller fails with the short dimension', () => {
33
- assert.equal(meetsTargetSize({ width: 44, height: 44 }).pass, true)
34
- const r = meetsTargetSize({ width: 30, height: 44 })
35
- assert.equal(r.pass, false)
36
- assert.deepEqual(r.short, ['width'])
37
- assert.deepEqual(meetsTargetSize({ width: 10, height: 10 }).short, ['width', 'height'])
38
- })
39
-
40
- test('meetsTargetSize: custom minimum', () => {
41
- assert.equal(meetsTargetSize({ width: 24, height: 24 }, 24).pass, true)
42
- assert.equal(meetsTargetSize({ width: 24, height: 24 }, 44).pass, false)
43
- })
44
-
45
- // ---------- focus indicator ----------
46
-
47
- const NO_OUTLINE = { outlineStyle: 'none', outlineWidth: '0px', outlineColor: 'rgb(0,0,0)', boxShadow: 'none' }
48
-
49
- test('hasVisibleFocusIndicator: outline appearing on focus → visible', () => {
50
- const focused = { outlineStyle: 'solid', outlineWidth: '2px', outlineColor: 'rgb(0,90,255)', boxShadow: 'none' }
51
- assert.equal(hasVisibleFocusIndicator(NO_OUTLINE, focused), true)
52
- })
53
-
54
- test('hasVisibleFocusIndicator: box-shadow ring appearing on focus → visible', () => {
55
- const focused = { ...NO_OUTLINE, boxShadow: '0 0 0 3px rgba(0,90,255,.5)' }
56
- assert.equal(hasVisibleFocusIndicator(NO_OUTLINE, focused), true)
57
- })
58
-
59
- test('hasVisibleFocusIndicator: no change between states → NOT visible (the bug)', () => {
60
- // outline:none in both states and identical shadow — the missing-focus-ring case
61
- assert.equal(hasVisibleFocusIndicator(NO_OUTLINE, { ...NO_OUTLINE }), false)
62
- })
63
-
64
- test('hasVisibleFocusIndicator: zero-width color-only outline change → NOT visible', () => {
65
- // `0px solid red` → `0px solid blue` paints nothing; must not false-pass
66
- const blurred = { outlineStyle: 'solid', outlineWidth: '0px', outlineColor: 'rgb(255,0,0)', boxShadow: 'none' }
67
- const focused = { outlineStyle: 'solid', outlineWidth: '0px', outlineColor: 'rgb(0,0,255)', boxShadow: 'none' }
68
- assert.equal(hasVisibleFocusIndicator(blurred, focused), false)
69
- })
@@ -1,98 +0,0 @@
1
- // Unit tests for the render-health spine — the pure core, no browser.
2
- import { test } from 'node:test'
3
- import assert from 'node:assert/strict'
4
-
5
- import {
6
- isFatalConsole,
7
- isFatalResponse,
8
- hasFatalMarker,
9
- evaluateRenderHealth,
10
- assertHealthyThrough,
11
- } from './presentation-render-health.mjs'
12
-
13
- // Minimal fake Playwright page: records listeners, lets a test emit events, and
14
- // returns body text from evaluate(). Enough to exercise the watcher/compose path
15
- // without a browser.
16
- function fakePage(bodyText = '') {
17
- const handlers = {}
18
- return {
19
- on(ev, fn) { (handlers[ev] ||= []).push(fn) },
20
- emit(ev, arg) { (handlers[ev] || []).forEach((f) => f(arg)) },
21
- async evaluate() { return bodyText },
22
- }
23
- }
24
-
25
- test('console.error is fatal; warning is not', () => {
26
- assert.equal(isFatalConsole('error', 'boom'), true)
27
- assert.equal(isFatalConsole('warning', 'meh'), false)
28
- assert.equal(isFatalConsole('log', 'hi'), false)
29
- })
30
-
31
- test('ignored console substrings are not fatal', () => {
32
- assert.equal(isFatalConsole('error', 'Download the React DevTools', { ignore: ['React DevTools'] }), false)
33
- })
34
-
35
- test('http >= 500 is fatal; < 500 is not', () => {
36
- assert.equal(isFatalResponse(500, '/api/x'), true)
37
- assert.equal(isFatalResponse(503, '/api/x'), true)
38
- assert.equal(isFatalResponse(404, '/api/x'), false)
39
- assert.equal(isFatalResponse(200, '/api/x'), false)
40
- })
41
-
42
- test('cross-origin 5xx is ignored (externals are faked, not graded)', () => {
43
- assert.equal(isFatalResponse(500, 'https://stripe.com/v1/x', { origin: 'http://localhost:3000' }), false)
44
- assert.equal(isFatalResponse(500, 'http://localhost:3000/api/x', { origin: 'http://localhost:3000' }), true)
45
- assert.equal(isFatalResponse(500, '/api/x', { origin: 'http://localhost:3000' }), true) // relative = first-party
46
- })
47
-
48
- test('error-boundary markers are detected in body text', () => {
49
- assert.equal(hasFatalMarker('Application error: a client-side exception has occurred'), true)
50
- assert.equal(hasFatalMarker("Cannot read properties of undefined (reading 'id')"), true)
51
- assert.equal(hasFatalMarker('All good here'), false)
52
- })
53
-
54
- test('the /tasks bug shape fails render-health (pageerror on mount)', () => {
55
- const v = evaluateRenderHealth({
56
- pageerrors: [{ message: "Cannot read properties of undefined (reading 'id')" }],
57
- })
58
- assert.equal(v.healthy, false)
59
- assert.equal(v.offenders[0].kind, 'pageerror')
60
- assert.match(v.offenders[0].detail, /reading 'id'/)
61
- })
62
-
63
- test('a clean render is healthy', () => {
64
- const v = evaluateRenderHealth({
65
- consoles: [{ type: 'log', text: 'mounted' }, { type: 'warning', text: 'deprecated' }],
66
- responses: [{ status: 200, url: '/api/tasks/list' }],
67
- bodyText: 'Tasks\nNo tasks match these filters.',
68
- })
69
- assert.equal(v.healthy, true)
70
- assert.deepEqual(v.offenders, [])
71
- })
72
-
73
- test('assertHealthyThrough fails when an error fires DURING the interaction (mutate-health)', async () => {
74
- const page = fakePage()
75
- await assert.rejects(
76
- assertHealthyThrough(page, async () => {
77
- // simulate an uncaught error thrown while a delete/submit runs
78
- page.emit('pageerror', { message: "Cannot read properties of undefined (reading 'id')" })
79
- }),
80
- /render-health.*reading 'id'/s,
81
- )
82
- })
83
-
84
- test('assertHealthyThrough passes a clean interaction', async () => {
85
- const page = fakePage('Tasks\nDeleted.')
86
- await assertHealthyThrough(page, async () => { /* clean mutation */ })
87
- })
88
-
89
- test('aggregates multiple offenders (pageerror + console + 5xx + boundary)', () => {
90
- const v = evaluateRenderHealth({
91
- pageerrors: [{ message: 'x is not a function' }],
92
- consoles: [{ type: 'error', text: 'render failed' }],
93
- responses: [{ status: 500, url: '/api/tasks/list' }],
94
- bodyText: 'Unhandled Runtime Error',
95
- }, { origin: 'http://localhost:3000' })
96
- assert.equal(v.healthy, false)
97
- assert.deepEqual(v.offenders.map((o) => o.kind).sort(), ['console.error', 'error-boundary', 'http>=500', 'pageerror'])
98
- })
@@ -1,71 +0,0 @@
1
- // Unit tests for the sweep's pure core (planning + summarize) — no browser.
2
- import { test } from 'node:test'
3
- import assert from 'node:assert/strict'
4
-
5
- import { planRoutes, summarize } from './presentation-sweep.mjs'
6
-
7
- test('static routes plan to themselves; route-kind filter applies', () => {
8
- const plan = planRoutes({ surfaces: [
9
- { route: '/tasks', kind: 'route' },
10
- { route: '/api/tasks/list', kind: 'api' }, // not a browser route → excluded
11
- { route: '/dashboard', kind: 'route' },
12
- ] })
13
- assert.deepEqual(plan, [
14
- { route: '/tasks', url: '/tasks' },
15
- { route: '/dashboard', url: '/dashboard' },
16
- ])
17
- })
18
-
19
- test('dynamic route with a resolver becomes a concrete url', () => {
20
- const plan = planRoutes(
21
- { routes: [{ route: '/tasks/[taskRunId]', kind: 'route' }] },
22
- { paramResolver: (r) => (r.includes('taskRunId') ? '/tasks/tr_seeded_1' : null) },
23
- )
24
- assert.deepEqual(plan, [{ route: '/tasks/[taskRunId]', url: '/tasks/tr_seeded_1' }])
25
- })
26
-
27
- test('dynamic route with NO resolver answer is blocked, not skipped or passed', () => {
28
- const plan = planRoutes({ routes: [{ route: '/tasks/[id]', kind: 'route' }] })
29
- assert.equal(plan.length, 1)
30
- assert.equal(plan[0].url, null)
31
- assert.match(plan[0].blocked, /param resolver/)
32
- })
33
-
34
- test('ignore list drops routes', () => {
35
- const plan = planRoutes(
36
- { routes: [{ route: '/tasks', kind: 'route' }, { route: '/_debug/x', kind: 'route' }] },
37
- { ignore: ['/_debug'] },
38
- )
39
- assert.deepEqual(plan.map((p) => p.route), ['/tasks'])
40
- })
41
-
42
- test('accepts a bare string manifest entry', () => {
43
- const plan = planRoutes({ routes: ['/a', '/b'] })
44
- assert.deepEqual(plan.map((p) => p.url), ['/a', '/b'])
45
- })
46
-
47
- test('summarize: a render-health fail flips the gate to non-ok', () => {
48
- const s = summarize([
49
- { route: '/dashboard', levels: [{ level: 'presentation:render-health', status: 'pass' }] },
50
- { route: '/tasks', levels: [
51
- { level: 'presentation:render-health', status: 'fail', evidence: "pageerror: reading 'id'" },
52
- { level: 'presentation:spatial', status: 'blocked', evidence: 'not assessable' },
53
- ] },
54
- ])
55
- assert.equal(s.ok, false)
56
- assert.equal(s.failedCount, 2) // the fail + the blocked
57
- assert.deepEqual(s.viewportFloor, [375, 1280])
58
- const tasksHealth = s.results.find((r) => r.route === '/tasks' && r.level === 'presentation:render-health')
59
- assert.equal(tasksHealth.status, 'fail')
60
- })
61
-
62
- test('summarize: all-pass is ok', () => {
63
- const s = summarize([
64
- { route: '/dashboard', levels: [
65
- { level: 'presentation:render-health', status: 'pass' },
66
- { level: 'presentation:spatial', status: 'pass' },
67
- ] },
68
- ])
69
- assert.equal(s.ok, true)
70
- assert.equal(s.failedCount, 0)
71
- })
@@ -1,59 +0,0 @@
1
- // Unit tests for the pure Temporal-stability cores (issue #199). Run: `node --test`.
2
- // The Playwright wrappers (assertNoFirstPartyShift / assertNoThirdPartyShift /
3
- // assertNoDynamicViewportShift) need a real browser and are exercised downstream.
4
-
5
- import { test } from 'node:test'
6
- import assert from 'node:assert/strict'
7
- import { findLayoutShift, cumulativeShiftScore } from './presentation-temporal.mjs'
8
-
9
- const r = (left, top, right, bottom) => ({ rect: { left, top, right, bottom } })
10
- const at = (selector, left, top, right, bottom) => ({ selector, ...r(left, top, right, bottom) })
11
-
12
- // ---------- findLayoutShift ----------
13
-
14
- test('no shift when geometry is unchanged', () => {
15
- const snap = [at('.header', 0, 0, 375, 60), at('.cta', 0, 100, 375, 140)]
16
- assert.deepEqual(findLayoutShift(snap, snap), [])
17
- })
18
-
19
- test('flags an element pushed down by injected content', () => {
20
- const before = [at('.cta', 0, 100, 375, 140)]
21
- const after = [at('.cta', 0, 260, 375, 300)] // shoved down 160px (a banner injected above)
22
- const shifted = findLayoutShift(before, after)
23
- assert.equal(shifted.length, 1)
24
- assert.equal(shifted[0].selector, '.cta')
25
- assert.equal(shifted[0].dy, 160)
26
- })
27
-
28
- test('sub-tolerance movement is not a shift', () => {
29
- const before = [at('.x', 0, 100, 375, 140)]
30
- const after = [at('.x', 0, 101, 375, 141)] // 1px < default 2px tolerance
31
- assert.deepEqual(findLayoutShift(before, after), [])
32
- })
33
-
34
- test('a newly-injected element is not itself counted as a shift', () => {
35
- const before = [at('.cta', 0, 100, 375, 140)]
36
- const after = [at('.cta', 0, 100, 375, 140), at('.banner', 0, 0, 375, 80)] // banner is new, cta did not move
37
- assert.deepEqual(findLayoutShift(before, after), [])
38
- })
39
-
40
- test('an element that disappears is not a shift of a stable element', () => {
41
- const before = [at('.spinner', 0, 0, 50, 50), at('.cta', 0, 100, 375, 140)]
42
- const after = [at('.cta', 0, 100, 375, 140)] // spinner gone, cta stable
43
- assert.deepEqual(findLayoutShift(before, after), [])
44
- })
45
-
46
- // ---------- cumulativeShiftScore ----------
47
-
48
- test('cumulativeShiftScore sums values without recent input', () => {
49
- const entries = [
50
- { value: 0.05, hadRecentInput: false },
51
- { value: 0.10, hadRecentInput: false },
52
- { value: 0.30, hadRecentInput: true }, // user-initiated — excluded
53
- ]
54
- assert.ok(Math.abs(cumulativeShiftScore(entries) - 0.15) < 1e-9)
55
- })
56
-
57
- test('cumulativeShiftScore is 0 for no entries', () => {
58
- assert.equal(cumulativeShiftScore([]), 0)
59
- })
@@ -1,76 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
-
4
- import { projectManifest, staleOverlayEntries } from './project-route-manifest.mjs'
5
-
6
- const INVENTORY = {
7
- surfaces: [
8
- { id: '/tasks', kind: 'route', class: 'read', reachability: 'reachable' },
9
- { id: '/tasks/[id]', kind: 'route', class: 'detail', reachability: 'reachable' },
10
- { id: '/settings/models', kind: 'route', class: 'mutation', reachability: 'reachable' },
11
- { id: '/legacy', kind: 'route', class: 'read', reachability: 'reachable' },
12
- { id: '/api/tasks/list', kind: 'api', class: 'read' }, // not a browser route
13
- { id: 'tasks_table', kind: 'table' },
14
- ],
15
- }
16
-
17
- test('projects only browser routes, default tier from class, sorted', () => {
18
- const m = projectManifest(INVENTORY)
19
- assert.equal(m.generated, true)
20
- assert.deepEqual(m.routes, [
21
- { path: '/legacy', tier: 1 },
22
- { path: '/settings/models', tier: 2 },
23
- { path: '/tasks', tier: 1 },
24
- { path: '/tasks/[id]', tier: 2 },
25
- ])
26
- })
27
-
28
- test('overlay raises tier and drops ignored routes', () => {
29
- const overlay = { routes: { '/tasks': { tier: 3 }, '/legacy': { ignore: true } }, spatialIgnore: ['.sticky-header'] }
30
- const m = projectManifest(INVENTORY, overlay)
31
- assert.equal(m.routes.find((r) => r.path === '/tasks').tier, 3)
32
- assert.ok(!m.routes.some((r) => r.path === '/legacy'), 'ignored route dropped from sweep')
33
- assert.deepEqual(m.spatialIgnore, ['.sticky-header'])
34
- })
35
-
36
- test('overlay specs are passed through but optional (informational only)', () => {
37
- const m = projectManifest(INVENTORY, { routes: { '/tasks': { specs: ['render/tasks.spec.ts'] } } })
38
- assert.deepEqual(m.routes.find((r) => r.path === '/tasks').specs, ['render/tasks.spec.ts'])
39
- // a route with no overlay has no specs key
40
- assert.equal('specs' in m.routes.find((r) => r.path === '/settings/models'), false)
41
- })
42
-
43
- test('same inventory + overlay → identical manifest (no drift, stable diff)', () => {
44
- const a = projectManifest(INVENTORY, { routes: { '/tasks': { tier: 3 } } })
45
- const b = projectManifest(INVENTORY, { routes: { '/tasks': { tier: 3 } } })
46
- assert.deepEqual(a, b)
47
- })
48
-
49
- test('staleOverlayEntries flags curation for a route no longer in inventory', () => {
50
- const stale = staleOverlayEntries(INVENTORY, { routes: { '/tasks': { tier: 3 }, '/removed': { ignore: true } } })
51
- assert.deepEqual(stale, ['/removed'])
52
- })
53
-
54
- import { spawnSync } from 'node:child_process'
55
- import { fileURLToPath } from 'node:url'
56
- import fs from 'node:fs'
57
- import os from 'node:os'
58
- import path from 'node:path'
59
-
60
- const CLI = fileURLToPath(new URL('./project-route-manifest.mjs', import.meta.url))
61
-
62
- test('CLI: --help prints usage; a missing inventory is a one-line error, not a stack trace', () => {
63
- const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'prm-cli-'))
64
- try {
65
- const help = spawnSync('node', [CLI, '--help'], { cwd, encoding: 'utf8' })
66
- assert.equal(help.status, 0)
67
- assert.match(help.stdout, /usage: project-route-manifest\.mjs/)
68
- const missing = spawnSync('node', [CLI, '--inventory', path.join(cwd, 'nope.json')], { cwd, encoding: 'utf8' })
69
- assert.equal(missing.status, 1)
70
- assert.match(missing.stderr, /inventory not found/)
71
- assert.doesNotMatch(missing.stderr, /at Object\.|node:internal/, 'no stack trace')
72
- assert.deepEqual(fs.readdirSync(cwd), [], 'nothing written')
73
- } finally {
74
- fs.rmSync(cwd, { recursive: true, force: true })
75
- }
76
- })
@@ -1,68 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
4
- import { dirname, join, relative, resolve } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
-
7
- const RUNTIME_DIR = dirname(fileURLToPath(import.meta.url));
8
- const REPO_ROOT = join(RUNTIME_DIR, '..', '..', '..');
9
- const MANIFEST = JSON.parse(readFileSync(join(REPO_ROOT, 'skills', 'distribution', 'manifest.json'), 'utf8'));
10
-
11
- // @covers package:engineering-runtime
12
- // @level integration
13
- test('runtime parser dependency metadata lives in the installed scripts directory', () => {
14
- const scripts = join(RUNTIME_DIR, 'scripts');
15
- const packageJson = JSON.parse(readFileSync(join(scripts, 'package.json'), 'utf8'));
16
- const packageLock = JSON.parse(readFileSync(join(scripts, 'package-lock.json'), 'utf8'));
17
-
18
- assert.equal(existsSync(join(RUNTIME_DIR, 'package.json')), false);
19
- assert.equal(existsSync(join(RUNTIME_DIR, 'package-lock.json')), false);
20
- // The lockfile must install exactly the version package.json declares.
21
- assert.ok(packageJson.dependencies['js-yaml'], 'the runtime parser depends on js-yaml');
22
- assert.equal(packageLock.packages['node_modules/js-yaml'].version, packageJson.dependencies['js-yaml']);
23
- });
24
-
25
- function modules(dir) {
26
- const found = [];
27
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
28
- if (entry.name === 'node_modules' || entry.name.endsWith('-workspace')) continue;
29
- const path = join(dir, entry.name);
30
- if (entry.isDirectory()) found.push(...modules(path));
31
- else if (entry.name.endsWith('.mjs') && !entry.name.endsWith('.test.mjs')) found.push(path);
32
- }
33
- return found;
34
- }
35
-
36
- // @covers package:engineering-runtime
37
- // @level integration
38
- test('every first-party Workflow consumer resolves one implementation in source and flat layouts', () => {
39
- const workflow = MANIFEST.groups.find((group) => group.id === 'engineering-workflow');
40
- const canonical = join(RUNTIME_DIR, 'scripts', 'main-module.mjs');
41
- const flatCanonical = join('/flat-skills', 'engineering-runtime', 'scripts', 'main-module.mjs');
42
- const consumers = [];
43
-
44
- for (const member of workflow.members.filter((candidate) => candidate.role === 'skill')) {
45
- const skillDir = join(REPO_ROOT, member.path);
46
- for (const file of modules(skillDir)) {
47
- const text = readFileSync(file, 'utf8');
48
- if (!/\bisMainModule\s*\(/.test(text)) continue;
49
- const staticImport = /import\s+\{\s*isMainModule\s*\}\s+from\s+['"]([^'"]+)['"]/.exec(text);
50
- const dynamicImport = /await import\(['"]([^'"]+)['"]\)/.exec(text);
51
- const imported = staticImport ?? dynamicImport;
52
- assert.ok(imported, `${relative(REPO_ROOT, file)} calls isMainModule without resolving it from the runtime package`);
53
- assert.equal(resolve(dirname(file), imported[1]), canonical, `${relative(REPO_ROOT, file)} misses the source runtime`);
54
-
55
- const installedFile = join('/flat-skills', member.installName, relative(skillDir, file));
56
- assert.equal(
57
- resolve(dirname(installedFile), imported[1]),
58
- flatCanonical,
59
- `${relative(REPO_ROOT, file)} misses the flat-installed runtime`,
60
- );
61
- consumers.push(relative(REPO_ROOT, file));
62
- }
63
- }
64
-
65
- // Shipped members only: the archived resolve-issues/resolve-release scripts left this census when
66
- // those names became aliases (they were never packaged).
67
- assert.ok(consumers.length >= 17, `the Workflow consumer census found only ${consumers.length} guarded scripts`);
68
- });