@1aboveio/skills 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/.codex-plugin/plugin.json +13 -0
  2. package/.codex-plugin/resources.json +1827 -0
  3. package/README.md +22 -0
  4. package/package.json +9 -2
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/airflow/release-check.mjs +3 -1
  7. package/skills/app-dev/android-foundation/SKILL.md +1 -1
  8. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  9. package/skills/app-dev/app-debug/SKILL.md +1 -1
  10. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  11. package/skills/app-dev/better-auth/SKILL.md +1 -1
  12. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  13. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  14. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  15. package/skills/app-dev/data-quality/SKILL.md +1 -1
  16. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  17. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  18. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  19. package/skills/app-dev/monorepo/SKILL.md +1 -1
  20. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  21. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  22. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  23. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  24. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  25. package/skills/app-dev/pyspark/SKILL.md +1 -1
  26. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  27. package/skills/app-dev/python-backend/SKILL.md +1 -1
  28. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  29. package/skills/app-dev/shadcn/SKILL.md +1 -1
  30. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  31. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  32. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  33. package/skills/cicd/cloud-build/SKILL.md +1 -1
  34. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  35. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  36. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  37. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  38. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  39. package/skills/cicd/docker/SKILL.md +1 -1
  40. package/skills/cicd/docker/release-check.mjs +3 -1
  41. package/skills/cicd/github-actions/SKILL.md +1 -1
  42. package/skills/cicd/github-actions/release-check.mjs +3 -1
  43. package/skills/cicd/github-flow/SKILL.md +1 -1
  44. package/skills/cicd/github-flow/release-check.mjs +3 -1
  45. package/skills/cicd/google-cloud/SKILL.md +1 -1
  46. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  47. package/skills/cicd/mergify/SKILL.md +1 -1
  48. package/skills/cicd/mergify/release-check.mjs +3 -1
  49. package/skills/cicd/podman/SKILL.md +1 -1
  50. package/skills/cicd/podman/release-check.mjs +3 -1
  51. package/skills/content/article-review/SKILL.md +1 -1
  52. package/skills/content/article-review/release-check.mjs +3 -1
  53. package/skills/content/article-to-image/SKILL.md +1 -1
  54. package/skills/content/article-to-image/release-check.mjs +3 -1
  55. package/skills/content/flowchart-svg/SKILL.md +1 -1
  56. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  57. package/skills/content/image-gen/SKILL.md +1 -1
  58. package/skills/content/image-gen/release-check.mjs +3 -1
  59. package/skills/content/mermaid/SKILL.md +1 -1
  60. package/skills/content/mermaid/release-check.mjs +3 -1
  61. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  62. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  63. package/skills/content/wechat-publisher/SKILL.md +1 -1
  64. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  65. package/skills/content/wechat-search/SKILL.md +1 -1
  66. package/skills/content/wechat-search/release-check.mjs +3 -1
  67. package/skills/content/xai-search/SKILL.md +88 -13
  68. package/skills/content/xai-search/release-check.mjs +3 -1
  69. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  70. package/skills/core/distill-writing/SKILL.md +1 -1
  71. package/skills/core/distill-writing/release-check.mjs +3 -1
  72. package/skills/core/doc-to-md/SKILL.md +1 -1
  73. package/skills/core/doc-to-md/release-check.mjs +3 -1
  74. package/skills/core/editorial-review/SKILL.md +1 -1
  75. package/skills/core/editorial-review/release-check.mjs +3 -1
  76. package/skills/core/house-style/SKILL.md +1 -1
  77. package/skills/core/house-style/release-check.mjs +3 -1
  78. package/skills/core/plain-language/SKILL.md +1 -1
  79. package/skills/core/plain-language/release-check.mjs +3 -1
  80. package/skills/core/polish-cn/SKILL.md +1 -1
  81. package/skills/core/polish-cn/release-check.mjs +3 -1
  82. package/skills/core/reg-parser/SKILL.md +1 -1
  83. package/skills/core/reg-parser/release-check.mjs +3 -1
  84. package/skills/core/terminology/SKILL.md +1 -1
  85. package/skills/core/terminology/release-check.mjs +3 -1
  86. package/skills/core/write-doc/SKILL.md +1 -1
  87. package/skills/core/write-doc/release-check.mjs +3 -1
  88. package/skills/engineering/better-review/SKILL.md +1 -1
  89. package/skills/engineering/better-review/release-check.mjs +3 -1
  90. package/skills/engineering/contributing/SKILL.md +1 -1
  91. package/skills/engineering/contributing/release-check.mjs +3 -1
  92. package/skills/engineering/e2e-test/SKILL.md +1 -1
  93. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  94. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  95. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  96. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  97. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  98. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  99. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  100. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  101. package/skills/engineering/golden-path/SKILL.md +1 -1
  102. package/skills/engineering/golden-path/release-check.mjs +3 -1
  103. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  104. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  105. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  106. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  107. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  108. package/skills/engineering/resolve-issues/references/task-plan.md +5 -1
  109. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  110. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  111. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  112. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  113. package/skills/engineering/resolve-release/SKILL.md +1 -1
  114. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  115. package/skills/engineering/rush-issues/SKILL.md +1 -1
  116. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  117. package/skills/engineering/rush-release/SKILL.md +1 -1
  118. package/skills/engineering/rush-release/release-check.mjs +3 -1
  119. package/skills/engineering/test-quality/SKILL.md +1 -1
  120. package/skills/engineering/test-quality/release-check.mjs +3 -1
  121. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  122. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  123. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  124. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  125. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  126. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  127. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  128. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  129. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  130. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  131. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  132. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  133. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  134. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  135. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  136. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  137. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -436
  138. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  139. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  140. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  141. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  142. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  143. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  144. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  145. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  146. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  147. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  148. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  149. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  150. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  151. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  152. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  153. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  154. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  155. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  156. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  157. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  158. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  159. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  160. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  161. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  162. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  163. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  164. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  165. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  166. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  167. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  168. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  169. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  170. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  171. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  172. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  173. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  174. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  175. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  176. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  177. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  178. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  179. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  180. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  181. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  182. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  183. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  184. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  185. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  186. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  187. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  188. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  189. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  190. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  191. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  192. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  193. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  194. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  195. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  196. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  197. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  198. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  199. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  200. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  201. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  202. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  203. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  204. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  205. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  206. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  207. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  208. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  209. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  210. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  211. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  212. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  213. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  214. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
@@ -1,552 +0,0 @@
1
- // Tests for the design-parity fidelity gate.
2
- //
3
- // The cases are derived from the incident, not invented: every divergence
4
- // fmm-express #521 shipped (page measure, card plane, the Amount input's
5
- // responsive type step, Description's scale) appears below as a test that the
6
- // gate blocks it. A gate whose tests are hand-picked from what it happens to
7
- // catch is the thing this skill calls a vacuous assertion.
8
- import { test } from 'node:test'
9
- import assert from 'node:assert/strict'
10
-
11
- import fs from 'node:fs'
12
- import os from 'node:os'
13
- import path from 'node:path'
14
- import { execFileSync, spawnSync } from 'node:child_process'
15
-
16
- import {
17
- PARITY_CATALOG, ROLES, FLOOR_VIEWPORTS,
18
- expectedRows, rowKey, isRealReason,
19
- validateManifest, compareObserved, valuesAgree, normalize,
20
- probeScript, scaffoldManifest, detectExpectationRewrites,
21
- } from './design-parity.mjs'
22
-
23
- const HERE = path.dirname(new URL(import.meta.url).pathname)
24
- const SCRIPT = path.join(HERE, 'design-parity.mjs')
25
-
26
- // The #521 element map, as it would be declared for the merchant charge form.
27
- const ELEMENTS = {
28
- container: '[data-testid="charge-form-plane"]',
29
- primary: '[data-testid="charge-amount-input"]',
30
- secondary: '[data-testid="description-input"]',
31
- coupled: '[data-testid="charge-tip-section"]',
32
- }
33
-
34
- // A complete, decided manifest — built from the catalog so the fixture cannot
35
- // drift out of sync with doctrine the way a hand-written one would.
36
- function completeManifest(overrides = {}) {
37
- const rows = expectedRows(ELEMENTS).map((r) => ({
38
- ...r,
39
- ...(r.relation ? {} : { expected: '0px' }),
40
- from: 'Dashboard.tipprototype.tsx:781',
41
- }))
42
- return {
43
- source: { ref: 'proto/tip-charge-ia', sha: 'deadbee', path: 'frontend/src/pages/Dashboard.tipprototype.tsx', kind: 'react' },
44
- surface: 'merchant-dashboard-charge-form',
45
- url: '/dashboard',
46
- viewports: FLOOR_VIEWPORTS,
47
- elements: ELEMENTS,
48
- rows,
49
- ...overrides,
50
- }
51
- }
52
-
53
- const problems = (v) => v.map((x) => `${x.row}: ${x.problem}`).join('\n')
54
-
55
- // ── the catalog ────────────────────────────────────────────────────────────
56
-
57
- test('every catalog entry carries the reason it exists (the thing that stops it being deleted)', () => {
58
- assert.ok(PARITY_CATALOG.length > 0)
59
- for (const e of PARITY_CATALOG) {
60
- assert.ok(e.id && e.group, `${e.id}: id + group`)
61
- assert.ok(e.why && e.why.length > 20, `${e.id}: a real why, not a label`)
62
- assert.ok(e.relation || (ROLES.includes(e.role) && Array.isArray(e.css) && e.css.length), `${e.id}: a known role + css, or a relation`)
63
- }
64
- })
65
-
66
- test('the catalog pins each thing #521 lost: measure, plane, per-viewport primary scale, secondary scale, coupled rhythm', () => {
67
- const ids = PARITY_CATALOG.map((e) => e.id)
68
- for (const id of ['container.measure', 'container.plane', 'primary.scale', 'secondary.scale', 'rhythm.coupled', 'hierarchy.order']) {
69
- assert.ok(ids.includes(id), `catalog carries ${id}`)
70
- }
71
- // The plane is three properties on purpose: pinning only the border lets a card
72
- // return through background + shadow.
73
- const plane = PARITY_CATALOG.find((e) => e.id === 'container.plane')
74
- assert.deepEqual(plane.css.sort(), ['background-color', 'border-top-width', 'box-shadow'])
75
- // The responsive step-up is the silent loss, so scale rows are per-viewport.
76
- assert.ok(PARITY_CATALOG.find((e) => e.id === 'primary.scale').perViewport)
77
- assert.ok(PARITY_CATALOG.find((e) => e.id === 'secondary.scale').perViewport)
78
- })
79
-
80
- // ── expansion ──────────────────────────────────────────────────────────────
81
-
82
- test('expectedRows expands per-viewport rows across the floor and skips undeclared roles', () => {
83
- const rows = expectedRows({ primary: '#a' })
84
- const scale = rows.filter((r) => r.id === 'primary.scale')
85
- assert.deepEqual(scale.map((r) => r.viewport).sort(), [...FLOOR_VIEWPORTS].sort())
86
- assert.equal(rows.filter((r) => r.role === 'container').length, 0, 'container undeclared → no container rows owed')
87
- })
88
-
89
- test('a relation is owed only when both operands are declared (no row nothing can measure)', () => {
90
- assert.equal(expectedRows({ primary: '#a' }).filter((r) => r.relation).length, 0)
91
- assert.equal(expectedRows({ primary: '#a', secondary: '#b' }).filter((r) => r.relation).length, 1)
92
- })
93
-
94
- // ── manifest validation ────────────────────────────────────────────────────
95
-
96
- test('a complete, decided manifest validates', () => {
97
- const v = validateManifest(completeManifest())
98
- assert.equal(v.length, 0, problems(v))
99
- })
100
-
101
- test('THE #521 CASE: a manifest that omits the container-plane rows is blocked, not passed', () => {
102
- // The defect in miniature — the card plane was never on anyone's list, so nobody
103
- // ever decided against it, and four rounds of user correction stood in for the gate.
104
- const m = completeManifest()
105
- m.rows = m.rows.filter((r) => r.id !== 'container.plane')
106
- const v = validateManifest(m)
107
- assert.ok(v.length >= 3, 'all three plane properties are owed')
108
- assert.match(problems(v), /missing row container\.plane/)
109
- assert.match(problems(v), /omission is not a decision/)
110
- })
111
-
112
- test('a dropped responsive viewport is blocked (the flattened Amount type scale)', () => {
113
- const m = completeManifest()
114
- m.rows = m.rows.filter((r) => !(r.id === 'primary.scale' && r.viewport === 1280))
115
- assert.match(problems(validateManifest(m)), /missing row primary\.scale\|primary\|font-size\|1280/)
116
- })
117
-
118
- test('an undecided row (expected: TODO) is blocked', () => {
119
- const m = completeManifest()
120
- m.rows[0].expected = 'TODO'
121
- assert.match(problems(validateManifest(m)), /expected is TODO/)
122
- })
123
-
124
- test('a row may be rejected — but only with a reason a reader can disagree with', () => {
125
- const weak = completeManifest()
126
- weak.rows[0] = { ...weak.rows[0], expected: undefined, reject: 'n/a' }
127
- assert.match(problems(validateManifest(weak)), /no real reason/)
128
-
129
- const real = completeManifest()
130
- real.rows[0] = { ...real.rows[0], expected: undefined, from: undefined, reject: 'source is a Figma frame with no measurable page container; measure pinned on the primary instead' }
131
- assert.equal(validateManifest(real).length, 0, problems(validateManifest(real)))
132
- })
133
-
134
- test('isRealReason refuses the ritual forms and accepts an argument', () => {
135
- for (const s of ['', ' ', 'n/a', 'N/A', 'none', '--', 'TODO', 'tbd', 'no']) assert.equal(isRealReason(s), false, `refuses ${JSON.stringify(s)}`)
136
- assert.equal(isRealReason('prototype has no fixed measure; the app shell owns width here'), true)
137
- })
138
-
139
- test('every expected value names the source line it was read off', () => {
140
- const m = completeManifest()
141
- delete m.rows[0].from
142
- assert.match(problems(validateManifest(m)), /no `from:` citation/)
143
-
144
- const bad = completeManifest()
145
- bad.rows[0].from = 'somewhere in the prototype'
146
- assert.match(problems(validateManifest(bad)), /is not a `<path>:<line>` citation/)
147
- })
148
-
149
- test('a manifest with no pinned source is blocked — an unpinned baseline is not fidelity', () => {
150
- const noSha = completeManifest({ source: { path: 'p.tsx' } })
151
- assert.match(problems(validateManifest(noSha)), /pin the design source at a commit/)
152
- const noPath = completeManifest({ source: { sha: 'abc' } })
153
- assert.match(problems(validateManifest(noPath)), /must name the design source/)
154
- })
155
-
156
- test('citations are shape-checked but warned, not blocked, when git cannot answer', () => {
157
- const v = validateManifest(completeManifest()) // no resolveSource passed
158
- assert.equal(v.length, 0)
159
- assert.ok(v.warnings.some((w) => /citation not resolved/.test(w.problem)))
160
- })
161
-
162
- test('a citation that does not resolve in the source blob is blocked', () => {
163
- const v = validateManifest(completeManifest(), {
164
- resolveSource: () => ({ ok: false, why: 'no `Dashboard.tipprototype.tsx` in deadbee' }),
165
- })
166
- assert.ok(v.length > 0)
167
- assert.match(problems(v), /does not resolve/)
168
- })
169
-
170
- test('an unknown role warns rather than blocking (its rows are simply not derived)', () => {
171
- const m = completeManifest({ elements: { ...ELEMENTS, footer: '#f' } })
172
- const v = validateManifest(m)
173
- assert.equal(v.length, 0, problems(v))
174
- assert.ok(v.warnings.some((w) => /unknown role/.test(w.problem)))
175
- })
176
-
177
- // ── comparison against the rendered page ───────────────────────────────────
178
-
179
- function observed(pairs, viewport = 375) {
180
- return { runs: [{ viewport, measurements: pairs.map(([role, css, value]) => ({ role, css, value })) }] }
181
- }
182
-
183
- function oneRow(row) {
184
- return { source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375], rows: [row] }
185
- }
186
-
187
- test('THE #521 CASE: a page laid out to the wrong measure diverges', () => {
188
- const m = oneRow({ id: 'container.measure', role: 'container', css: 'max-width', expected: '672px', from: 'p.tsx:781' })
189
- const bad = compareObserved(m, observed([['container', 'max-width', '448px']])) // max-w-md against a max-w-2xl source
190
- assert.equal(bad.length, 1)
191
- assert.match(bad[0].problem, /expected 672px, page has 448px/)
192
- assert.match(bad[0].problem, /source: p\.tsx:781/, 'the failure names the source line to go read')
193
-
194
- assert.equal(compareObserved(m, observed([['container', 'max-width', '672px']])).length, 0)
195
- })
196
-
197
- test('THE #521 CASE: the card plane coming back through any one of its three properties diverges', () => {
198
- const rows = [
199
- { id: 'container.plane', role: 'container', css: 'background-color', expected: 'rgba(0, 0, 0, 0)', from: 'p.tsx:781' },
200
- { id: 'container.plane', role: 'container', css: 'box-shadow', expected: 'none', from: 'p.tsx:781' },
201
- { id: 'container.plane', role: 'container', css: 'border-top-width', expected: '0px', from: 'p.tsx:781' },
202
- ]
203
- const m = { source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375], rows }
204
- const flat = observed([['container', 'background-color', 'rgba(0, 0, 0, 0)'], ['container', 'box-shadow', 'none'], ['container', 'border-top-width', '0px']])
205
- assert.equal(compareObserved(m, flat).length, 0)
206
-
207
- const shadowOnly = observed([['container', 'background-color', 'rgba(0, 0, 0, 0)'], ['container', 'box-shadow', 'rgba(0,0,0,0.05) 0px 1px 2px 0px'], ['container', 'border-top-width', '0px']])
208
- assert.equal(compareObserved(m, shadowOnly).length, 1, 'a shadow alone still reads as a plane')
209
- })
210
-
211
- test('THE #521 CASE: a lost responsive step-up diverges at the viewport that lost it', () => {
212
- const m = {
213
- source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375, 1280],
214
- rows: [
215
- { id: 'primary.scale', role: 'primary', css: 'font-size', viewport: 375, expected: '24px', from: 'p.tsx:825' },
216
- { id: 'primary.scale', role: 'primary', css: 'font-size', viewport: 1280, expected: '30px', from: 'p.tsx:825' },
217
- ],
218
- }
219
- const flattened = { runs: [
220
- { viewport: 375, measurements: [{ role: 'primary', css: 'font-size', value: '24px' }] },
221
- { viewport: 1280, measurements: [{ role: 'primary', css: 'font-size', value: '24px' }] }, // never stepped up
222
- ] }
223
- const bad = compareObserved(m, flattened)
224
- assert.equal(bad.length, 1)
225
- assert.match(bad[0].row, /\|1280$/, 'only the viewport that lost it fails')
226
- assert.match(bad[0].problem, /expected 30px, page has 24px/)
227
- })
228
-
229
- test('THE #521 CASE: Description at Amount weight fails the relational row', () => {
230
- const m = oneRow({ id: 'hierarchy.order', relation: 'primary.font-size > secondary.font-size', from: 'p.tsx:825' })
231
- const equal = observed([['primary', 'font-size', '24px'], ['secondary', 'font-size', '24px']])
232
- const bad = compareObserved(m, equal)
233
- assert.equal(bad.length, 1)
234
- assert.match(bad[0].problem, /primary\.font-size=24px is not > secondary\.font-size=24px/)
235
-
236
- assert.equal(compareObserved(m, observed([['primary', 'font-size', '24px'], ['secondary', 'font-size', '14px']])).length, 0)
237
- })
238
-
239
- test('a missing element fails loudly — it must never read as agreement', () => {
240
- const m = oneRow({ id: 'primary.box', role: 'primary', css: 'padding-top', expected: '12px', from: 'p.tsx:825' })
241
- const gone = compareObserved(m, observed([['primary', 'padding-top', null]]))
242
- assert.equal(gone.length, 1)
243
- assert.match(gone[0].problem, /element not found/)
244
- assert.match(gone[0].problem, /charge-amount-input/, 'names the selector that missed')
245
- })
246
-
247
- test('a row the probe never measured fails — silence is not a pass', () => {
248
- const m = oneRow({ id: 'primary.box', role: 'primary', css: 'padding-top', expected: '12px', from: 'p.tsx:825' })
249
- const bad = compareObserved(m, { runs: [] })
250
- assert.equal(bad.length, 1)
251
- assert.match(bad[0].problem, /not measured/)
252
- })
253
-
254
- test('a rejected row is not compared (the reject is the decision)', () => {
255
- const m = oneRow({ id: 'container.measure', role: 'container', css: 'max-width', reject: 'app shell owns the measure on this route; nothing to pin here' })
256
- assert.equal(compareObserved(m, { runs: [] }).length, 0)
257
- })
258
-
259
- test('a viewport-independent row that measures differently per viewport is caught, not silently passed on the first hit', () => {
260
- const m = {
261
- source: { sha: 'x', path: 'p.tsx' }, elements: ELEMENTS, viewports: [375, 1280],
262
- rows: [{ id: 'container.padding', role: 'container', css: 'padding-left', expected: '0px', from: 'p.tsx:781' }],
263
- }
264
- const responsive = { runs: [
265
- { viewport: 375, measurements: [{ role: 'container', css: 'padding-left', value: '0px' }] },
266
- { viewport: 1280, measurements: [{ role: 'container', css: 'padding-left', value: '24px' }] },
267
- ] }
268
- const bad = compareObserved(m, responsive)
269
- assert.equal(bad.length, 1)
270
- assert.match(bad[0].problem, /split it per viewport/)
271
- })
272
-
273
- test('sub-pixel layout noise is not a design decision; a real step is', () => {
274
- assert.equal(valuesAgree('24px', '24.4px'), true)
275
- assert.equal(valuesAgree('24px', '24.6px'), false)
276
- assert.equal(valuesAgree('24', '24px'), true, 'a bare number in expected means px')
277
- assert.equal(valuesAgree('none', 'none'), true)
278
- assert.equal(valuesAgree('rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)'), false, 'colour strings are compared as written — normalize in the probe, not here')
279
- assert.deepEqual(normalize(' 24PX '), { num: 24 })
280
- })
281
-
282
- // ── expectation rewrites (the re-baseline-to-green analogue) ───────────────
283
-
284
- const twoVersions = (before, after, srcAfter) => [
285
- { source: { sha: 'aaa', path: 'p.tsx' }, elements: ELEMENTS, rows: [{ id: 'container.measure', role: 'container', css: 'max-width', ...before }] },
286
- { source: { sha: srcAfter || 'aaa', path: 'p.tsx' }, elements: ELEMENTS, rows: [{ id: 'container.measure', role: 'container', css: 'max-width', ...after }] },
287
- ]
288
-
289
- test('rewriting an expectation to match the page, under an unmoved source, BLOCKS', () => {
290
- // The cheapest way to green this gate: stop fixing the page, edit the target.
291
- // One character in YAML, no PNG in the diff for a reviewer to notice.
292
- const [prev, next] = twoVersions(
293
- { expected: '672px', from: 'p.tsx:781' },
294
- { expected: '448px', from: 'p.tsx:781' },
295
- )
296
- const out = detectExpectationRewrites(prev, next)
297
- assert.equal(out.length, 1)
298
- assert.equal(out[0].level, 'block')
299
- assert.match(out[0].problem, /672px → 448px/)
300
- assert.match(out[0].problem, /moves the target to the page/)
301
- })
302
-
303
- test('the same rewrite clears when the mis-read is declared', () => {
304
- const [prev, next] = twoVersions(
305
- { expected: '672px', from: 'p.tsx:781' },
306
- { expected: '448px', from: 'p.tsx:781', rebase: 'misread the prototype: 672 was the sm: breakpoint, the base measure is 448' },
307
- )
308
- assert.equal(detectExpectationRewrites(prev, next).length, 0)
309
- })
310
-
311
- test('a `rebase:` that says nothing does not clear it', () => {
312
- const [prev, next] = twoVersions(
313
- { expected: '672px', from: 'p.tsx:781' },
314
- { expected: '448px', from: 'p.tsx:781', rebase: 'n/a' },
315
- )
316
- assert.equal(detectExpectationRewrites(prev, next)[0].level, 'block')
317
- })
318
-
319
- test('when the design source itself moved, a changed expectation warns rather than blocks', () => {
320
- // The source is no longer the same document; expectations are SUPPOSED to change.
321
- // Blocking here would make re-pinning a prototype cost a fight with the gate.
322
- const [prev, next] = twoVersions(
323
- { expected: '672px', from: 'p.tsx:781' },
324
- { expected: '448px', from: 'p.tsx:781' },
325
- 'bbb',
326
- )
327
- const out = detectExpectationRewrites(prev, next)
328
- assert.equal(out[0].level, 'warn')
329
- assert.match(out[0].problem, /design source moved/)
330
- })
331
-
332
- test('a changed expectation with a new citation warns — that is a re-read, not a capitulation', () => {
333
- const [prev, next] = twoVersions(
334
- { expected: '672px', from: 'p.tsx:781' },
335
- { expected: '448px', from: 'p.tsx:602' },
336
- )
337
- assert.equal(detectExpectationRewrites(prev, next)[0].level, 'warn')
338
- })
339
-
340
- test('unchanged rows, new rows and removed rows are not rewrites', () => {
341
- const [prev] = twoVersions({ expected: '672px', from: 'p.tsx:781' }, {})
342
- assert.equal(detectExpectationRewrites(prev, prev).length, 0, 'identical manifests')
343
- const added = { ...prev, rows: [...prev.rows, { id: 'primary.box', role: 'primary', css: 'padding-top', expected: '12px', from: 'p.tsx:825' }] }
344
- assert.equal(detectExpectationRewrites(prev, added).length, 0, 'a new row rewrote nothing')
345
- assert.equal(detectExpectationRewrites(prev, { ...prev, rows: [] }).length, 0, 'a removed row is caught by the completeness check, not here')
346
- })
347
-
348
- test('CLI: --diff blocks a manifest whose expectation was rewritten to match the page', () => {
349
- // Real git history, because the check reads the previous version with `git show`.
350
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-git-'))
351
- const git = (...a) => execFileSync('git', a, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
352
- git('init', '-q')
353
- git('config', 'user.email', 't@t'); git('config', 'user.name', 't')
354
- // A COMPLETE manifest, so the only thing left to fail on is the rewrite itself.
355
- const m = {
356
- source: { sha: 'aaa', path: 'p.tsx' }, elements: { container: '#c' }, viewports: [375],
357
- rows: expectedRows({ container: '#c' }, [375]).map((r) => ({ ...r, expected: r.css === 'max-width' ? '672px' : '0px', from: 'p.tsx:781' })),
358
- }
359
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
360
- git('add', '-A'); git('commit', '-qm', 'pin the measure')
361
-
362
- m.rows.find((r) => r.css === 'max-width').expected = '448px' // the page won the argument
363
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
364
- const r = run(['validate', 'm.json', '--diff', 'HEAD', '--root', dir], dir)
365
- assert.equal(r.status, 1)
366
- assert.match(r.stdout, /expectation\(s\) rewritten to match the page/)
367
- assert.match(r.stdout, /672px → 448px/)
368
- })
369
-
370
- test('CLI: --diff is silent when the manifest is new at that base', () => {
371
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-git-'))
372
- const git = (...a) => execFileSync('git', a, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
373
- git('init', '-q'); git('config', 'user.email', 't@t'); git('config', 'user.name', 't')
374
- fs.writeFileSync(path.join(dir, 'seed.txt'), 'x'); git('add', '-A'); git('commit', '-qm', 'seed')
375
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify({ source: { sha: 'a', path: 'p.tsx' }, elements: { container: '#c' }, viewports: [375], rows: [] }))
376
- const r = run(['validate', 'm.json', '--diff', 'HEAD', '--root', dir], dir)
377
- assert.ok(!/rewritten/.test(r.stdout), 'a manifest with no previous version reports nothing about rewrites')
378
- })
379
-
380
- // ── probe ──────────────────────────────────────────────────────────────────
381
-
382
- // Run the emitted snippet against a stub page. Asserting on its SOURCE would pass
383
- // on a snippet that throws; the only honest question about a probe is what it
384
- // returns when something evaluates it.
385
- function runProbe(js, { missing = [] } = {}) {
386
- const queried = []
387
- const document = {
388
- querySelector(sel) { queried.push(sel); return missing.includes(sel) ? null : { sel } },
389
- }
390
- const getComputedStyle = (el) => new Proxy({}, { get: (_, css) => `<${el.sel}|${String(css)}>` })
391
- const result = new Function('document', 'getComputedStyle', `return ${js}`)(document, getComputedStyle)
392
- return { result, queried }
393
- }
394
-
395
- test('the probe measures every catalog property plus the operands the relations need', () => {
396
- const { result, queried } = runProbe(probeScript({ surface: 's', elements: ELEMENTS, viewports: [375] }, 375))
397
- assert.equal(result.surface, 's')
398
- assert.equal(result.runs[0].viewport, 375)
399
-
400
- const measured = new Set(result.runs[0].measurements.map((m) => `${m.role}|${m.css}`))
401
- for (const owed of expectedRows(ELEMENTS)) {
402
- if (owed.relation) continue
403
- assert.ok(measured.has(`${owed.role}|${owed.css}`), `probe reads ${owed.role}.${owed.css}`)
404
- }
405
- // The relational row's operands must be measured even though no plain row asks
406
- // for secondary.font-size at an unqualified viewport.
407
- assert.ok(measured.has('primary|font-size') && measured.has('secondary|font-size'))
408
- for (const sel of Object.values(ELEMENTS)) assert.ok(queried.includes(sel), `probe queries ${sel}`)
409
- assert.ok(result.runs[0].measurements.every((m) => typeof m.value === 'string'))
410
- })
411
-
412
- test('the probe reports a missing element as null rather than throwing', () => {
413
- // compareObserved turns null into "element not found"; a probe that threw here
414
- // would produce no results file at all, and an absent file is easy to read as
415
- // "nothing to report".
416
- const { result } = runProbe(probeScript({ elements: ELEMENTS, viewports: [375] }, 375), { missing: [ELEMENTS.container] })
417
- const container = result.runs[0].measurements.filter((m) => m.role === 'container')
418
- assert.ok(container.length > 0)
419
- assert.ok(container.every((m) => m.value === null))
420
- })
421
-
422
- test('the probe emits one entry per role+property, not one per manifest row', () => {
423
- const js = probeScript({ elements: ELEMENTS, viewports: [375, 1280] }, 375)
424
- const fontSizeEntries = js.match(/"css": "font-size"/g) || []
425
- assert.equal(fontSizeEntries.length, 2, 'primary + secondary, not primary×2 viewports + secondary×2')
426
- })
427
-
428
- // ── scaffold ───────────────────────────────────────────────────────────────
429
-
430
- test('scaffold emits the whole catalog as TODO rows, carrying each reason into the file', () => {
431
- const md = scaffoldManifest({ source: { ref: 'proto/x', sha: 'abc123', path: 'proto.tsx' }, surface: 's', url: '/u', elements: ELEMENTS })
432
- assert.match(md, /source:\n {2}ref: proto\/x\n {2}sha: abc123/)
433
- for (const e of PARITY_CATALOG) {
434
- assert.ok(md.includes(`id: ${e.id}`), `scaffold carries ${e.id}`)
435
- assert.ok(md.includes(e.why), `scaffold carries the reason for ${e.id}`)
436
- }
437
- assert.match(md, /expected: TODO/)
438
- assert.match(md, /read off the DESIGN SOURCE/, 'the file states the fidelity-not-drift rule where it will be read')
439
- })
440
-
441
- test('scaffold leaves no TODO value on a relational row (the relation IS the expectation)', () => {
442
- const md = scaffoldManifest({ source: { path: 'p.tsx' }, elements: ELEMENTS })
443
- const block = md.slice(md.indexOf('id: hierarchy.order'))
444
- const nextRow = block.indexOf('\n - id:')
445
- assert.ok(!(nextRow === -1 ? block : block.slice(0, nextRow)).includes('expected: TODO'))
446
- })
447
-
448
- test('scaffold comments out undeclared roles instead of emitting rows nothing can measure', () => {
449
- const md = scaffoldManifest({ source: { path: 'p.tsx' }, elements: { primary: '#a' } })
450
- assert.match(md, /# container: '<selector>'/)
451
- assert.ok(!md.includes('id: container.measure'))
452
- })
453
-
454
- test('scaffold output is a manifest validate accepts once filled in', () => {
455
- // The two ends have to agree, or the skeleton teaches a shape the gate rejects.
456
- const owed = expectedRows(ELEMENTS)
457
- const md = scaffoldManifest({ source: { path: 'p.tsx' }, elements: ELEMENTS })
458
- for (const r of owed) assert.ok(md.includes(`id: ${r.id}`), `${rowKey(r)} is in the skeleton`)
459
- })
460
-
461
- // ── CLI ────────────────────────────────────────────────────────────────────
462
-
463
- const run = (args, cwd) => spawnSync(process.execPath, [SCRIPT, ...args], { cwd, encoding: 'utf8' })
464
-
465
- test('CLI: validate exits 1 and names the missing rows on an incomplete manifest', () => {
466
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
467
- const m = completeManifest()
468
- m.rows = m.rows.filter((r) => r.id !== 'container.plane')
469
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
470
- const r = run(['validate', 'm.json', '--root', dir], dir)
471
- assert.equal(r.status, 1)
472
- assert.match(r.stdout, /missing row container\.plane/)
473
- })
474
-
475
- test('CLI: a run without --observed announces on stderr that it did not check the page', () => {
476
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
477
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(completeManifest()))
478
- const r = run(['validate', 'm.json', '--root', dir], dir)
479
- // The manifest cites a source that does not exist in `dir`, so this run blocks —
480
- // what is asserted here is the NOTE, which must appear on the misleading path too.
481
- assert.match(r.stderr, /reduced form/)
482
- assert.match(r.stderr, /does NOT check the page/)
483
- assert.match(r.stdout, /does not resolve/, 'and an unresolvable source citation blocks')
484
- })
485
-
486
- test('CLI: a manifest citing a real blob at a real commit resolves and passes', () => {
487
- const repo = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: HERE, encoding: 'utf8' }).trim()
488
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
489
- const tracked = 'skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs'
490
- const m = completeManifest({ source: { ref: 'HEAD', sha: 'HEAD', path: tracked, kind: 'react' } })
491
- m.rows = m.rows.map((r) => ({ ...r, from: r.from ? `${tracked}:10` : r.from }))
492
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
493
- const r = run(['validate', path.join(dir, 'm.json'), '--root', repo], repo)
494
- assert.equal(r.status, 0, `${r.stdout}\n${r.stderr}`)
495
- assert.match(r.stdout, /page unchecked/, 'the pass says what it did not check')
496
- })
497
-
498
- test('CLI: --observed turns the reduced form into the real verdict, and divergence exits 1', () => {
499
- const repo = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: HERE, encoding: 'utf8' }).trim()
500
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
501
- const tracked = 'skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs'
502
- const m = completeManifest({ source: { ref: 'HEAD', sha: 'HEAD', path: tracked, kind: 'react' } })
503
- m.rows = m.rows.map((r) => ({ ...r, from: r.from ? `${tracked}:10` : r.from }))
504
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify(m))
505
-
506
- // Every row expects '0px'; measure one of them wrong.
507
- const runs = FLOOR_VIEWPORTS.map((viewport) => ({
508
- viewport,
509
- measurements: [...new Set(expectedRows(ELEMENTS).filter((r) => !r.relation).map((r) => `${r.role}|${r.css}`))]
510
- .map((k) => { const [role, css] = k.split('|'); return { role, css, value: role === 'container' && css === 'max-width' ? '448px' : '0px' } }),
511
- }))
512
- fs.writeFileSync(path.join(dir, 'o.json'), JSON.stringify({ runs }))
513
-
514
- const r = run(['validate', path.join(dir, 'm.json'), '--observed', path.join(dir, 'o.json'), '--root', repo], repo)
515
- assert.equal(r.status, 1)
516
- assert.match(r.stdout, /divergence\(s\) from the design source/)
517
- assert.match(r.stdout, /expected 0px, page has 448px/)
518
- assert.ok(!/reduced form/.test(r.stderr), 'with --observed there is no reduced-form caveat')
519
- })
520
-
521
- test('CLI: scaffold prints a manifest skeleton; probe prints evaluatable JS', () => {
522
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
523
- const s = run(['scaffold', '--source', 'proto/x:proto.tsx', '--surface', 'charge-form', '--url', '/dashboard', '--role', `primary=${ELEMENTS.primary}`], dir)
524
- assert.equal(s.status, 0, s.stderr)
525
- assert.match(s.stdout, /surface: charge-form/)
526
- assert.match(s.stdout, /id: primary\.scale/)
527
-
528
- fs.writeFileSync(path.join(dir, 'm.json'), JSON.stringify({ surface: 's', elements: ELEMENTS, viewports: [375] }))
529
- const p = run(['probe', 'm.json', '--viewport', '375'], dir)
530
- assert.equal(p.status, 0, p.stderr)
531
- assert.match(p.stdout, /getComputedStyle/)
532
- assert.doesNotThrow(() => new Function(`return typeof (${p.stdout.trim()})`.replace('getComputedStyle', 'globalThis.__x')))
533
- })
534
-
535
- test('CLI: an unresolvable ref leaves `sha:` unfilled rather than copying the moving ref in', () => {
536
- // A branch that lives in another repo (the prototype case) must not come back as
537
- // a manifest that LOOKS pinned. The placeholder is what makes `validate` and the
538
- // next reader both notice.
539
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
540
- const r = run(['scaffold', '--source', 'proto/not-here:proto.tsx', '--surface', 's', '--url', '/u', '--role', 'primary=#a'], dir)
541
- assert.equal(r.status, 0)
542
- assert.match(r.stdout, /sha: <commit-sha>/)
543
- assert.ok(!r.stdout.includes('sha: proto/not-here'))
544
- assert.match(r.stderr, /could not resolve/)
545
- })
546
-
547
- test('CLI: usage errors exit 2, never 0', () => {
548
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-parity-'))
549
- assert.equal(run([], dir).status, 2)
550
- assert.equal(run(['validate', 'nope.json'], dir).status, 2)
551
- assert.equal(run(['scaffold'], dir).status, 2)
552
- })