stylegallery 0.1.0

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 (346) hide show
  1. package/AGENTS.md +156 -0
  2. package/CATALOG.md +69 -0
  3. package/DOMAINS.md +65 -0
  4. package/GOVERNANCE.md +222 -0
  5. package/GUIDE.md +59 -0
  6. package/README.md +313 -0
  7. package/consumer-reference/adapters/style-dictionary.config.mjs +36 -0
  8. package/consumer-reference/agent-native/README.md +157 -0
  9. package/consumer-reference/agent-native/registry.json +640 -0
  10. package/consumer-reference/agent-native/schema/agent-native.schema.json +848 -0
  11. package/consumer-reference/agent-native/schema/epistemic.schema.json +14 -0
  12. package/consumer-reference/agent-native/schema/execution.schema.json +12 -0
  13. package/consumer-reference/agent-native/schema/identity.schema.json +10 -0
  14. package/consumer-reference/agent-native/schema/learning.schema.json +12 -0
  15. package/consumer-reference/agent-native/schema/manifest.schema.json +6 -0
  16. package/consumer-reference/agent-native/schema/operation.schema.json +12 -0
  17. package/consumer-reference/agent-native/schema/protocol-binding.schema.json +9 -0
  18. package/consumer-reference/agent-native/schema/retrieval.schema.json +10 -0
  19. package/consumer-reference/baselines/calibration.json +260 -0
  20. package/consumer-reference/baselines/manifest.json +21 -0
  21. package/consumer-reference/contract.md +83 -0
  22. package/consumer-reference/fixtures/consumer-conformance/e2e-fixture.mjs +282 -0
  23. package/consumer-reference/fixtures/consumer-conformance/manifest.json +102 -0
  24. package/consumer-reference/fixtures/consumer-conformance/runtime-proof.mjs +32 -0
  25. package/consumer-reference/fixtures/consumer-conformance/valid-migration-result.json +18 -0
  26. package/consumer-reference/fixtures/consumer-conformance/valid-migration.json +142 -0
  27. package/consumer-reference/fixtures/invalid-stable-ended-support.json +19 -0
  28. package/consumer-reference/fixtures/promotion/invalid-blocking-evidence.json +26 -0
  29. package/consumer-reference/fixtures/promotion/invalid-count-only.json +29 -0
  30. package/consumer-reference/fixtures/promotion/invalid-missing-owner-migration.json +25 -0
  31. package/consumer-reference/fixtures/promotion/invalid-normative-bypass-no-regression.json +25 -0
  32. package/consumer-reference/fixtures/promotion/invalid-related-counted-independent.json +45 -0
  33. package/consumer-reference/fixtures/promotion/invalid-stable-ended-support.json +26 -0
  34. package/consumer-reference/fixtures/promotion/manifest.json +15 -0
  35. package/consumer-reference/fixtures/promotion/valid-deferred-example.json +67 -0
  36. package/consumer-reference/fixtures/promotion/valid-normative-bypass.json +67 -0
  37. package/consumer-reference/fixtures/token-portability/invalid-cycle.json +7 -0
  38. package/consumer-reference/fixtures/token-portability/invalid-dangling-alias.json +6 -0
  39. package/consumer-reference/fixtures/token-portability/invalid-extends.json +4 -0
  40. package/consumer-reference/fixtures/token-portability/invalid-json-pointer.json +7 -0
  41. package/consumer-reference/fixtures/token-portability/invalid-resolver.json +9 -0
  42. package/consumer-reference/fixtures/token-portability/invalid-type-mismatch.json +10 -0
  43. package/consumer-reference/fixtures/token-portability/invalid-unknown-reserved.json +6 -0
  44. package/consumer-reference/fixtures/token-portability/invalid-untested-type.json +6 -0
  45. package/consumer-reference/fixtures/token-portability/valid-border.json +14 -0
  46. package/consumer-reference/fixtures/token-portability/valid-reference.json +32 -0
  47. package/consumer-reference/fixtures/token-portability/valid-whole-alias.json +7 -0
  48. package/consumer-reference/fixtures/valid-deprecated.json +21 -0
  49. package/consumer-reference/fixtures/valid-experimental.json +19 -0
  50. package/consumer-reference/generated/manifest.json +40 -0
  51. package/consumer-reference/generated/tokens.css +12 -0
  52. package/consumer-reference/index.md +32 -0
  53. package/consumer-reference/policies/shared-experimental.json +37 -0
  54. package/consumer-reference/schema/ax-evidence.schema.json +30 -0
  55. package/consumer-reference/schema/baseline-manifest.schema.json +55 -0
  56. package/consumer-reference/schema/calibration-record.schema.json +190 -0
  57. package/consumer-reference/schema/capture-session.schema.json +161 -0
  58. package/consumer-reference/schema/component-state.schema.json +150 -0
  59. package/consumer-reference/schema/consumer-conformance-record.schema.json +365 -0
  60. package/consumer-reference/schema/dom-evidence.schema.json +38 -0
  61. package/consumer-reference/schema/evidence-record.schema.json +81 -0
  62. package/consumer-reference/schema/fixture-manifest.schema.json +32 -0
  63. package/consumer-reference/schema/item.schema.json +188 -0
  64. package/consumer-reference/schema/page-evidence-manifest.schema.json +216 -0
  65. package/consumer-reference/schema/page-evidence-session.schema.json +119 -0
  66. package/consumer-reference/schema/portable-tokens.schema.json +104 -0
  67. package/consumer-reference/schema/promotion-rfc.schema.json +219 -0
  68. package/consumer-reference/schema/runtime-evidence-manifest.schema.json +33 -0
  69. package/consumer-reference/schema/visual-evidence.schema.json +31 -0
  70. package/design-engineering/consumer-migration-readiness.md +124 -0
  71. package/design-engineering/index.md +28 -0
  72. package/design-engineering/interface-craft.md +107 -0
  73. package/design-engineering/reference-profiles/external-adaptation/index.md +12 -0
  74. package/design-engineering/reference-profiles/governed-local/editorial/components/button.component.json +125 -0
  75. package/design-engineering/reference-profiles/governed-local/editorial/evidence/button.evidence.json +3504 -0
  76. package/design-engineering/reference-profiles/governed-local/editorial/fixtures/button.fixture.json +65 -0
  77. package/design-engineering/reference-profiles/governed-local/editorial/generated/evidence-coverage.md +23 -0
  78. package/design-engineering/reference-profiles/governed-local/editorial/generated/keyboard-matrix.md +19 -0
  79. package/design-engineering/reference-profiles/governed-local/editorial/generated/state-matrix.md +21 -0
  80. package/design-engineering/reference-profiles/governed-local/editorial/local-foundations.json +12 -0
  81. package/design-engineering/reference-profiles/governed-local/editorial/profile.json +43 -0
  82. package/design-engineering/reference-profiles/governed-local/editorial/states/button.states.json +195 -0
  83. package/design-engineering/reference-profiles/governed-local/editorial/tokens.dtcg.json +17 -0
  84. package/design-engineering/reference-profiles/governed-local/index.md +26 -0
  85. package/design-engineering/reference-profiles/governed-local/terminal/components/button.component.json +125 -0
  86. package/design-engineering/reference-profiles/governed-local/terminal/evidence/button.evidence.json +3504 -0
  87. package/design-engineering/reference-profiles/governed-local/terminal/fixtures/button.fixture.json +65 -0
  88. package/design-engineering/reference-profiles/governed-local/terminal/generated/evidence-coverage.md +23 -0
  89. package/design-engineering/reference-profiles/governed-local/terminal/generated/keyboard-matrix.md +19 -0
  90. package/design-engineering/reference-profiles/governed-local/terminal/generated/state-matrix.md +21 -0
  91. package/design-engineering/reference-profiles/governed-local/terminal/local-foundations.json +12 -0
  92. package/design-engineering/reference-profiles/governed-local/terminal/profile.json +43 -0
  93. package/design-engineering/reference-profiles/governed-local/terminal/states/button.states.json +195 -0
  94. package/design-engineering/reference-profiles/governed-local/terminal/tokens.dtcg.json +17 -0
  95. package/design-engineering/reference-profiles/index.md +19 -0
  96. package/game-ui/classification.md +82 -0
  97. package/game-ui/index.md +35 -0
  98. package/game-ui/reference-record.md +62 -0
  99. package/game-ui/screen-hierarchy.md +107 -0
  100. package/game-ui/unity/architecture.md +166 -0
  101. package/game-ui/unity/cli-loop.md +84 -0
  102. package/game-ui/unity/data/unity-technologies-public-repositories.json +26654 -0
  103. package/game-ui/unity/org-wiki.md +146 -0
  104. package/game-ui/unity/repository-map.md +102 -0
  105. package/game-ui/unity/ui-systems.md +85 -0
  106. package/guides/decision-tree.md +149 -0
  107. package/guides/documentation-mode-taxonomy.md +157 -0
  108. package/guides/layout-brief.md +220 -0
  109. package/guides/vocabulary.md +148 -0
  110. package/guides/webpage-generation-workflow.md +185 -0
  111. package/index.md +36 -0
  112. package/layout/index.md +26 -0
  113. package/motion/index.md +24 -0
  114. package/motion/practice-reference.md +145 -0
  115. package/motion/review-workflow.md +86 -0
  116. package/motion/vocabulary.md +82 -0
  117. package/package.json +78 -0
  118. package/patterns/centering/center.md +87 -0
  119. package/patterns/centering/index.md +6 -0
  120. package/patterns/centering/super-center.md +89 -0
  121. package/patterns/containment/box.md +87 -0
  122. package/patterns/containment/clamped-card.md +86 -0
  123. package/patterns/containment/content-limiter.md +87 -0
  124. package/patterns/containment/fluid-styles.md +86 -0
  125. package/patterns/containment/index.md +8 -0
  126. package/patterns/grid-repetition/card-grid.md +88 -0
  127. package/patterns/grid-repetition/columns.md +87 -0
  128. package/patterns/grid-repetition/dense-grid.md +88 -0
  129. package/patterns/grid-repetition/grid-wrapper.md +89 -0
  130. package/patterns/grid-repetition/index.md +12 -0
  131. package/patterns/grid-repetition/masonry-approx.md +87 -0
  132. package/patterns/grid-repetition/page-grid.md +90 -0
  133. package/patterns/grid-repetition/ram-grid.md +88 -0
  134. package/patterns/grid-repetition/twelve-span-grid.md +92 -0
  135. package/patterns/in-line-grouping/badge-list.md +94 -0
  136. package/patterns/in-line-grouping/breadcrumb.md +89 -0
  137. package/patterns/in-line-grouping/cluster.md +89 -0
  138. package/patterns/in-line-grouping/deconstructed-pancake.md +92 -0
  139. package/patterns/in-line-grouping/index.md +13 -0
  140. package/patterns/in-line-grouping/pagination.md +89 -0
  141. package/patterns/in-line-grouping/reel.md +90 -0
  142. package/patterns/in-line-grouping/split-nav.md +97 -0
  143. package/patterns/in-line-grouping/tab-strip.md +88 -0
  144. package/patterns/in-line-grouping/wrap-row.md +89 -0
  145. package/patterns/index.md +13 -0
  146. package/patterns/media-fit/frame.md +95 -0
  147. package/patterns/media-fit/icon-frame.md +87 -0
  148. package/patterns/media-fit/index.md +6 -0
  149. package/patterns/overlay-exception/imposter.md +95 -0
  150. package/patterns/overlay-exception/index.md +6 -0
  151. package/patterns/overlay-exception/overlay-stack.md +89 -0
  152. package/patterns/split-sidebar/index.md +12 -0
  153. package/patterns/split-sidebar/list-detail.md +95 -0
  154. package/patterns/split-sidebar/main-with-rail.md +95 -0
  155. package/patterns/split-sidebar/media-object.md +102 -0
  156. package/patterns/split-sidebar/sidebar.md +101 -0
  157. package/patterns/split-sidebar/split-screen.md +92 -0
  158. package/patterns/split-sidebar/sticky-aside.md +97 -0
  159. package/patterns/split-sidebar/supporting-pane.md +95 -0
  160. package/patterns/split-sidebar/switcher.md +93 -0
  161. package/patterns/stacking/feed.md +88 -0
  162. package/patterns/stacking/index.md +8 -0
  163. package/patterns/stacking/line-up.md +95 -0
  164. package/patterns/stacking/stack.md +87 -0
  165. package/patterns/stacking/step-nav.md +88 -0
  166. package/patterns/viewport-shell/cover.md +90 -0
  167. package/patterns/viewport-shell/fixed-sidenav-shell.md +97 -0
  168. package/patterns/viewport-shell/holy-grail.md +112 -0
  169. package/patterns/viewport-shell/index.md +11 -0
  170. package/patterns/viewport-shell/panel-layout.md +95 -0
  171. package/patterns/viewport-shell/scroll-body-shell.md +93 -0
  172. package/patterns/viewport-shell/sticky-footer.md +92 -0
  173. package/patterns/viewport-shell/sticky-header.md +86 -0
  174. package/platform-guides/apple-interaction.md +89 -0
  175. package/platform-guides/index.md +26 -0
  176. package/quality/claim-records/index.md +3 -0
  177. package/quality/claim-records/samples.md +92 -0
  178. package/quality/claim-records/stylegallery-multidomain-scope.md +42 -0
  179. package/quality/claims.md +77 -0
  180. package/quality/evidence/accessibility.md +58 -0
  181. package/quality/evidence/consumer-migration.md +42 -0
  182. package/quality/evidence/design-rationale.md +34 -0
  183. package/quality/evidence/executable-evidence.md +79 -0
  184. package/quality/evidence/families.md +56 -0
  185. package/quality/evidence/hci-evaluation.md +36 -0
  186. package/quality/evidence/index.md +16 -0
  187. package/quality/evidence/pattern-contract-audit.md +96 -0
  188. package/quality/evidence/search-metadata-polyhierarchy.md +86 -0
  189. package/quality/evidence/sources.md +43 -0
  190. package/quality/evidence/tokens.md +33 -0
  191. package/quality/evidence/visual-qa.md +41 -0
  192. package/quality/gates/accessibility-evidence.md +60 -0
  193. package/quality/gates/consumer-migration-evidence.md +53 -0
  194. package/quality/gates/design-claim.md +56 -0
  195. package/quality/gates/harmony-evaluation.md +78 -0
  196. package/quality/gates/index.md +13 -0
  197. package/quality/gates/layout.md +52 -0
  198. package/quality/gates/rationale.md +53 -0
  199. package/quality/gates/visual-evidence.md +47 -0
  200. package/quality/index.md +55 -0
  201. package/quality/principles.md +58 -0
  202. package/recipes/article-page.md +52 -0
  203. package/recipes/command-surface.md +55 -0
  204. package/recipes/dashboard.md +56 -0
  205. package/recipes/form-flow.md +55 -0
  206. package/recipes/homepage.md +89 -0
  207. package/recipes/index.md +13 -0
  208. package/recipes/list-detail.md +55 -0
  209. package/recipes/primitive-to-recipe-matrix.md +83 -0
  210. package/recipes/saas-settings.md +52 -0
  211. package/scripts/agent-native/a2a-projection.mjs +248 -0
  212. package/scripts/agent-native/agui-projection.mjs +178 -0
  213. package/scripts/agent-native/canonical-json.mjs +109 -0
  214. package/scripts/agent-native/cli-adapter.mjs +68 -0
  215. package/scripts/agent-native/events.mjs +113 -0
  216. package/scripts/agent-native/execution.mjs +227 -0
  217. package/scripts/agent-native/fixture.mjs +159 -0
  218. package/scripts/agent-native/identity.mjs +184 -0
  219. package/scripts/agent-native/immutable-store.mjs +102 -0
  220. package/scripts/agent-native/knowledge.mjs +168 -0
  221. package/scripts/agent-native/learning-records.mjs +244 -0
  222. package/scripts/agent-native/learning.mjs +247 -0
  223. package/scripts/agent-native/mcp-adapter.mjs +173 -0
  224. package/scripts/agent-native/queries.mjs +173 -0
  225. package/scripts/agent-native/registry.mjs +154 -0
  226. package/scripts/agent-native/retrieval.mjs +171 -0
  227. package/scripts/agent-native/self-description.mjs +160 -0
  228. package/scripts/artifact-metadata.mjs +20 -0
  229. package/scripts/audit-evidence-freshness.mjs +308 -0
  230. package/scripts/baseline-contract.mjs +189 -0
  231. package/scripts/baseline-schema-parity.mjs +40 -0
  232. package/scripts/build-reference-artifacts.mjs +127 -0
  233. package/scripts/calibration-raw-contract.mjs +172 -0
  234. package/scripts/capture-session-contract.mjs +187 -0
  235. package/scripts/component-state-artifact-fixture.mjs +44 -0
  236. package/scripts/component-state-contract.mjs +99 -0
  237. package/scripts/component-state-semantics.mjs +128 -0
  238. package/scripts/component-state-workflow-contract.mjs +67 -0
  239. package/scripts/component-state-workflow-negative-cases.mjs +64 -0
  240. package/scripts/consumer-conformance-contract.mjs +198 -0
  241. package/scripts/consumer-evidence-governance-contract.mjs +210 -0
  242. package/scripts/consumer-reference-case-runner.mjs +93 -0
  243. package/scripts/consumer-reference-schema.mjs +155 -0
  244. package/scripts/create-component-state-session.mjs +100 -0
  245. package/scripts/create-page-evidence-session.mjs +139 -0
  246. package/scripts/domain-document-boundaries.mjs +40 -0
  247. package/scripts/domain-validation-checks.mjs +141 -0
  248. package/scripts/evidence-artifact-contract.mjs +191 -0
  249. package/scripts/finalize-component-state-evidence.mjs +165 -0
  250. package/scripts/finalize-page-evidence.mjs +197 -0
  251. package/scripts/generate-consumer-reference-evidence.mjs +154 -0
  252. package/scripts/generate-patterns.mjs +218 -0
  253. package/scripts/governance-matrix-contract.mjs +73 -0
  254. package/scripts/governance-matrix-negative-cases.mjs +114 -0
  255. package/scripts/governance-policy-contract.mjs +49 -0
  256. package/scripts/governance-test-fixture.mjs +321 -0
  257. package/scripts/governance-workflow-negative-cases.mjs +241 -0
  258. package/scripts/governed-profile-registry.mjs +125 -0
  259. package/scripts/json-schema-formats.mjs +31 -0
  260. package/scripts/markdown-structure.mjs +45 -0
  261. package/scripts/page-artifact-metadata.mjs +124 -0
  262. package/scripts/page-evidence-contract.mjs +297 -0
  263. package/scripts/page-evidence-fixture.mjs +271 -0
  264. package/scripts/pattern-data.mjs +325 -0
  265. package/scripts/profile-record-contract.mjs +140 -0
  266. package/scripts/promotion-attestation-contract.mjs +91 -0
  267. package/scripts/promotion-boundary-test-contract.mjs +92 -0
  268. package/scripts/promotion-fixture-inventory.mjs +109 -0
  269. package/scripts/promotion-governance-contract.mjs +28 -0
  270. package/scripts/reference-artifact-path-contract.mjs +28 -0
  271. package/scripts/reference-profile-contract.mjs +114 -0
  272. package/scripts/reference-token-contract.mjs +156 -0
  273. package/scripts/run-consumer-page-evidence-ci.mjs +170 -0
  274. package/scripts/sg-mcp.mjs +16 -0
  275. package/scripts/sg.mjs +27 -0
  276. package/scripts/strict-json.mjs +72 -0
  277. package/scripts/summarize-sentinel-calibration.mjs +98 -0
  278. package/scripts/test-agent-adapter-conformance.mjs +250 -0
  279. package/scripts/test-agent-conformance-receipt.mjs +106 -0
  280. package/scripts/test-agent-events.mjs +81 -0
  281. package/scripts/test-agent-execution-effects.mjs +114 -0
  282. package/scripts/test-agent-execution-fixtures.mjs +140 -0
  283. package/scripts/test-agent-execution-kernel.mjs +39 -0
  284. package/scripts/test-agent-execution-learning.mjs +130 -0
  285. package/scripts/test-agent-execution-lifecycle.mjs +143 -0
  286. package/scripts/test-agent-execution-safety.mjs +158 -0
  287. package/scripts/test-agent-execution-views.mjs +117 -0
  288. package/scripts/test-agent-identity-integrity-cases.mjs +160 -0
  289. package/scripts/test-agent-knowledge-kernel.mjs +183 -0
  290. package/scripts/test-agent-learning.mjs +246 -0
  291. package/scripts/test-agent-mcp.mjs +246 -0
  292. package/scripts/test-agent-native-schemas.mjs +215 -0
  293. package/scripts/test-agent-projections.mjs +249 -0
  294. package/scripts/test-agent-registry-mcp-integrity.mjs +215 -0
  295. package/scripts/test-agent-registry-mutations.mjs +110 -0
  296. package/scripts/test-audit-evidence-freshness.mjs +177 -0
  297. package/scripts/test-component-state-sentinel.mjs +35 -0
  298. package/scripts/test-component-state-source-contract.mjs +83 -0
  299. package/scripts/test-consumer-conformance-sentinel.mjs +165 -0
  300. package/scripts/test-consumer-reference-sentinel.mjs +31 -0
  301. package/scripts/test-generate-consumer-reference-evidence.mjs +124 -0
  302. package/scripts/test-json-cli-output.mjs +151 -0
  303. package/scripts/test-json-schema-formats.mjs +40 -0
  304. package/scripts/test-reference-adapters.mjs +199 -0
  305. package/scripts/test-renderer-purity.mjs +34 -0
  306. package/scripts/test-sg-cli.mjs +129 -0
  307. package/scripts/test-summarize-sentinel-calibration.mjs +226 -0
  308. package/scripts/test-validate-baseline-manifest.mjs +234 -0
  309. package/scripts/test-validate-component-state-artifacts.mjs +243 -0
  310. package/scripts/test-validate-component-state.mjs +106 -0
  311. package/scripts/test-validate-consumer-conformance.mjs +427 -0
  312. package/scripts/test-validate-consumer-reference-cli.mjs +69 -0
  313. package/scripts/test-validate-consumer-reference.mjs +263 -0
  314. package/scripts/test-validate-domains.mjs +357 -0
  315. package/scripts/test-validate-governance.mjs +292 -0
  316. package/scripts/test-validate-ia.mjs +262 -0
  317. package/scripts/test-validate-okf.mjs +73 -0
  318. package/scripts/test-validate-page-evidence.mjs +302 -0
  319. package/scripts/test-validate-patterns.mjs +157 -0
  320. package/scripts/test-validate-promotion-rfc.mjs +250 -0
  321. package/scripts/test-validate-reference-profiles.mjs +170 -0
  322. package/scripts/test-validate-unity-org-wiki.mjs +57 -0
  323. package/scripts/test-validate-unity-source-contracts.mjs +67 -0
  324. package/scripts/test-validate-vocabulary.mjs +174 -0
  325. package/scripts/test-validate-webpage-workflow.mjs +293 -0
  326. package/scripts/validate-baseline-manifest.mjs +44 -0
  327. package/scripts/validate-catalog.mjs +65 -0
  328. package/scripts/validate-component-state.mjs +141 -0
  329. package/scripts/validate-consumer-conformance.mjs +477 -0
  330. package/scripts/validate-consumer-reference.mjs +288 -0
  331. package/scripts/validate-domains.mjs +237 -0
  332. package/scripts/validate-governance.mjs +248 -0
  333. package/scripts/validate-ia.mjs +136 -0
  334. package/scripts/validate-links.mjs +66 -0
  335. package/scripts/validate-okf.mjs +104 -0
  336. package/scripts/validate-page-evidence.mjs +191 -0
  337. package/scripts/validate-patterns.mjs +246 -0
  338. package/scripts/validate-promotion-rfc.mjs +224 -0
  339. package/scripts/validate-reference-artifacts.mjs +125 -0
  340. package/scripts/validate-renderer-purity.mjs +56 -0
  341. package/scripts/validate-unity-org-wiki.mjs +146 -0
  342. package/scripts/validate-unity-source-contracts.mjs +107 -0
  343. package/scripts/validate-vocabulary.mjs +115 -0
  344. package/scripts/validate-webpage-workflow.mjs +254 -0
  345. package/scripts/visual-expectation-contract.mjs +20 -0
  346. package/scripts/workflow-action-contract.mjs +163 -0
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { markdownLinkDestinations, structuralMarkdown } from "./markdown-structure.mjs";
6
+
7
+ const arguments_ = process.argv.slice(2);
8
+ const json = arguments_.includes("--json");
9
+ const rootIndex = arguments_.indexOf("--root");
10
+ const root = rootIndex === -1 ? process.cwd() : path.resolve(arguments_[rootIndex + 1] ?? "");
11
+ const failures = [];
12
+ const documents = [
13
+ "game-ui/unity/architecture.md",
14
+ "game-ui/unity/cli-loop.md",
15
+ "game-ui/unity/org-wiki.md",
16
+ "game-ui/unity/repository-map.md",
17
+ "game-ui/unity/ui-systems.md",
18
+ ];
19
+ const expectedRevisions = new Map([
20
+ ["Unity-Technologies/BagelGame", "5d0348bfa013f3c76299dd582c90a7549f66abce"],
21
+ ["Unity-Technologies/EntityComponentSystemSamples", "6786a741ee1f118ed14cecfa02beae8e926937b0"],
22
+ ["Unity-Technologies/InputSystem", "8a57d62081cf3546b46444e17223e0121a3568b2"],
23
+ ["Unity-Technologies/SimpleUIDemo", "c33907f1ffa0f8a30b46f958d3cf45e5da08b01c"],
24
+ ["Unity-Technologies/UIElementsExamples", "e940d1c9dddfe99fd500c9f34f4801856ad37f18"],
25
+ ["Unity-Technologies/UIElementsUniteCPH2019RuntimeDemo", "fa4f2c70cf2d6127ab624cc11a2742260ca6ff20"],
26
+ ["Unity-Technologies/UIElementsUniteLATurretDemo", "1fce133fe3a3473d418066d8005c64340e9985d5"],
27
+ ["Unity-Technologies/UIToolkitUnityRoyaleRuntimeDemo", "9b5006d2580101b4d08d59d03d7dadc697d75311"],
28
+ ["Unity-Technologies/UnityCsReference", "79f16640303119112ca5caed4d71c607d0dff6fb"],
29
+ ["Unity-Technologies/XR-Interaction-Toolkit-Examples", "881f7e197c7b0958621ebd1a05f249bbe254d36f"],
30
+ ["Unity-Technologies/a11y-public-sample", "ec14cfcb8c10bafab83ea02cd17759d77b225fae"],
31
+ ["Unity-Technologies/com.unity.uiwidgets", "0310984e947881eca6c2657f1a2fcaaa19329c22"],
32
+ ["Unity-Technologies/uGUI", "9edb4420267b6652090ece4c28c38bd98746a68e"],
33
+ ["Unity-Technologies/ui-toolkit-manual-code-examples", "7ed6e7af9fc8a8f83facea818f47928d2e03a30f"],
34
+ ["Unity-Technologies/unity-industry-viewer-template", "5d50da18f68b14c4dc15bcf3d535878cc2254497"],
35
+ ["annulusgames/UGUIAnimationSamples", "343c8110e5683be209cc01ccb4cb986175e61643"],
36
+ ["hatayama/unity-cli-loop", "61a0fe6d7da0aa9d0bcbc6d95944dd069c483ff0"],
37
+ ["tasharen/ngui", "9bbc56f11e0e25b89fd55b9b9bc67ddf1f182fe2"],
38
+ ]);
39
+
40
+ function read(relative) {
41
+ const target = path.join(root, relative);
42
+ if (!fs.existsSync(target)) {
43
+ failures.push(`${relative}: missing file`);
44
+ return "";
45
+ }
46
+ return fs.readFileSync(target, "utf8");
47
+ }
48
+
49
+ function frontmatterValue(content, key) {
50
+ const frontmatter = content.match(/^---\n([\s\S]*?)\n---\n/)?.[1] ?? "";
51
+ return frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1]?.trim() ?? "";
52
+ }
53
+
54
+ function validateReviewedOn(relative, content) {
55
+ const value = frontmatterValue(content, "reviewed_on");
56
+ const valid = /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T00:00:00Z`));
57
+ if (!valid) failures.push(`${relative}: reviewed_on must be an ISO calendar date`);
58
+ }
59
+
60
+ function validatePinnedLinks(relative, content) {
61
+ for (const destination of markdownLinkDestinations(content)) {
62
+ const match = destination.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/(?:blob|tree)\/([^/]+)(?:\/|$)/);
63
+ if (!match) continue;
64
+ const [, repository, revision] = match;
65
+ if (!/^[0-9a-f]{40}$/.test(revision) || /^0{40}$/.test(revision)) {
66
+ failures.push(`${relative}: GitHub body citation must use a non-zero 40-character revision: ${repository}`);
67
+ continue;
68
+ }
69
+ const expected = expectedRevisions.get(repository);
70
+ if (expected && revision !== expected) failures.push(`${relative}: unexpected pinned revision for ${repository}: ${revision}`);
71
+ }
72
+ }
73
+
74
+ function requireIncludes(relative, content, expected, label) {
75
+ if (!content.includes(expected)) failures.push(`${relative}: missing ${label}`);
76
+ }
77
+
78
+ for (const relative of documents) {
79
+ const content = read(relative);
80
+ if (!content) continue;
81
+ const body = structuralMarkdown(content);
82
+ validateReviewedOn(relative, content);
83
+ validatePinnedLinks(relative, body);
84
+ requireIncludes(relative, body, "## Source, License, And Attribution", "source attribution section");
85
+ }
86
+
87
+ const architecture = structuralMarkdown(read("game-ui/unity/architecture.md"));
88
+ requireIncludes("game-ui/unity/architecture.md", architecture, "https://github.com/annulusgames/UGUIAnimationSamples/blob/343c8110e5683be209cc01ccb4cb986175e61643/LICENSE", "pinned sample license");
89
+
90
+ const cliLoop = structuralMarkdown(read("game-ui/unity/cli-loop.md"));
91
+ requireIncludes("game-ui/unity/cli-loop.md", cliLoop, "https://github.com/hatayama/unity-cli-loop/blob/61a0fe6d7da0aa9d0bcbc6d95944dd069c483ff0/README.md", "pinned adapted source");
92
+ requireIncludes("game-ui/unity/cli-loop.md", cliLoop, "https://github.com/hatayama/unity-cli-loop/blob/61a0fe6d7da0aa9d0bcbc6d95944dd069c483ff0/LICENSE.md", "pinned source license");
93
+
94
+ const uiSystems = structuralMarkdown(read("game-ui/unity/ui-systems.md"));
95
+ for (const [repository, revision, license] of [
96
+ ["Unity-Technologies/uGUI", "9edb4420267b6652090ece4c28c38bd98746a68e", "com.unity.ugui/LICENSE.md"],
97
+ ["Unity-Technologies/UnityCsReference", "79f16640303119112ca5caed4d71c607d0dff6fb", "LICENSE.md"],
98
+ ["tasharen/ngui", "9bbc56f11e0e25b89fd55b9b9bc67ddf1f182fe2", "License.txt"],
99
+ ]) {
100
+ requireIncludes("game-ui/unity/ui-systems.md", uiSystems, `https://github.com/${repository}/blob/${revision}/${license}`, `pinned license for ${repository}`);
101
+ }
102
+
103
+ const result = { ok: failures.length === 0, checkedDocuments: documents.length, failures: [...new Set(failures)] };
104
+ if (json) console.log(JSON.stringify(result, null, 2));
105
+ else if (result.ok) console.log(`ok: ${result.checkedDocuments} Unity source contracts`);
106
+ else console.error(result.failures.join("\n"));
107
+ process.exit(result.ok ? 0 : 1);
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const args = new Set(process.argv.slice(2));
7
+ const json = args.has("--json");
8
+ const root = process.cwd();
9
+ const failures = [];
10
+ const warnings = [];
11
+
12
+ const glossaryFile = "guides/vocabulary.md";
13
+ const requiredTerms = [
14
+ "domain",
15
+ "category",
16
+ "pattern",
17
+ "recipe",
18
+ "gate",
19
+ "claim",
20
+ "evidence family",
21
+ "scroll ownership",
22
+ "harmony",
23
+ "debt",
24
+ "consumer reference",
25
+ "maturity",
26
+ "artifact mode",
27
+ ];
28
+
29
+ const requiredIncludes = {
30
+ [glossaryFile]: [
31
+ "## Canonical Terms",
32
+ "## Aliases",
33
+ "## Deprecated Terms",
34
+ "## Local-Only Terms",
35
+ "## Scannability Checklist",
36
+ "## Vale Proposal",
37
+ "Prose linting should wait until the vocabulary is stable.",
38
+ "Local-only: `consumer component`",
39
+ "In Layout, component implies visual or framework ownership",
40
+ ],
41
+ };
42
+
43
+ const recommendedLinks = {
44
+ "README.md": { target: "guides/vocabulary.md", preferred: "[Controlled vocabulary](guides/vocabulary.md)" },
45
+ "GUIDE.md": { target: "guides/vocabulary.md", preferred: "[Controlled vocabulary](guides/vocabulary.md)" },
46
+ "index.md": { target: "guides/vocabulary.md", preferred: "[Controlled vocabulary](guides/vocabulary.md)" },
47
+ "quality/index.md": { target: "../guides/vocabulary.md", preferred: "[Controlled vocabulary](../guides/vocabulary.md)" },
48
+ };
49
+
50
+ function read(relative) {
51
+ const target = path.join(root, relative);
52
+ if (!fs.existsSync(target)) {
53
+ failures.push(`${relative}: missing file`);
54
+ return "";
55
+ }
56
+ return fs.readFileSync(target, "utf8");
57
+ }
58
+
59
+ for (const [relative, snippets] of Object.entries(requiredIncludes)) {
60
+ const content = read(relative);
61
+ for (const snippet of snippets) {
62
+ if (!content.includes(snippet)) failures.push(`${relative}: missing ${snippet}`);
63
+ }
64
+ }
65
+
66
+ for (const [relative, link] of Object.entries(recommendedLinks)) {
67
+ const content = read(relative);
68
+ if (!content.includes(`](${link.target})`)) failures.push(`${relative}: missing link target ${link.target}`);
69
+ if (!content.includes(link.preferred)) warnings.push(`${relative}: recommended link label missing ${link.preferred}`);
70
+ }
71
+
72
+ const glossary = read(glossaryFile);
73
+ const canonicalRecords = [...glossary.matchAll(/^- Canonical: `([^`]+)`\n - Concept: ([^\n]+)/gm)].map((match) => ({
74
+ term: match[1].trim(),
75
+ concept: match[2].trim().toLowerCase(),
76
+ }));
77
+ const canonicalTerms = canonicalRecords.map((record) => record.term);
78
+
79
+ for (const term of requiredTerms) {
80
+ if (!canonicalTerms.includes(term)) failures.push(`${glossaryFile}: missing canonical term ${term}`);
81
+ }
82
+
83
+ const termsSeen = new Set();
84
+ for (const term of canonicalTerms) {
85
+ if (termsSeen.has(term)) failures.push(`${glossaryFile}: duplicate canonical term ${term}`);
86
+ termsSeen.add(term);
87
+ }
88
+
89
+ const conceptToTerm = new Map();
90
+ for (const record of canonicalRecords) {
91
+ const existing = conceptToTerm.get(record.concept);
92
+ if (existing && existing !== record.term) {
93
+ failures.push(`${glossaryFile}: concept "${record.concept}" has multiple canonical terms: ${existing}, ${record.term}`);
94
+ }
95
+ conceptToTerm.set(record.concept, record.term);
96
+ }
97
+
98
+ const result = {
99
+ ok: failures.length === 0,
100
+ checkedFiles: [...new Set([...Object.keys(requiredIncludes), ...Object.keys(recommendedLinks)])],
101
+ requiredTerms,
102
+ canonicalTerms,
103
+ failures,
104
+ warnings,
105
+ };
106
+
107
+ if (json) {
108
+ console.log(JSON.stringify(result, null, 2));
109
+ } else if (result.ok) {
110
+ console.log(`ok: ${canonicalTerms.length} canonical vocabulary terms (${warnings.length} warnings)`);
111
+ } else {
112
+ console.error(failures.join("\n"));
113
+ }
114
+
115
+ process.exitCode = result.ok ? 0 : 1;
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const args = new Set(process.argv.slice(2));
7
+ const json = args.has("--json");
8
+ const root = process.cwd();
9
+ const failures = [];
10
+ const warnings = [];
11
+ const contents = new Map();
12
+
13
+ const requiredIncludes = {
14
+ "guides/layout-brief.md": [
15
+ "## Webpage Generation Intake",
16
+ "Content-to-layout match:",
17
+ "Harmony evaluation:",
18
+ "GPT Image reference:",
19
+ "Required preconditions before prompt:",
20
+ "Image review extract-vs-ignore:",
21
+ "Implementation handoff:",
22
+ "Consumer reference: not_applicable",
23
+ "Consumer reference reason:",
24
+ "Accepted image debt:",
25
+ "## Minimum Required Fields By Use Case",
26
+ "Homepage:",
27
+ "Dashboard:",
28
+ "Article:",
29
+ "Form:",
30
+ "Command surface:",
31
+ "## Rejected Alternatives",
32
+ "Rejected pattern or recipe:",
33
+ "Reason rejected:",
34
+ ],
35
+ "guides/decision-tree.md": [
36
+ "Rejected alternatives:",
37
+ ],
38
+ "guides/webpage-generation-workflow.md": [
39
+ "type: Planning Guide",
40
+ "# Webpage Generation Workflow",
41
+ "## Use Cases",
42
+ "## Content-to-Layout Match",
43
+ "## Harmony Evaluation",
44
+ "## GPT Image Reference",
45
+ "Required preconditions before prompt:",
46
+ "Extract from image:",
47
+ "Ignore from image:",
48
+ "## Implementation Handoff",
49
+ "Implementation debt:",
50
+ "Final implementation proof:",
51
+ "Consumer reference: not_applicable",
52
+ "Consumer reference reason:",
53
+ "[Consumer Reference Receiver Contract](../consumer-reference/contract.md)",
54
+ "## Workflow Route Examples",
55
+ "### Homepage Route",
56
+ "### Dashboard Route",
57
+ "### Article Route",
58
+ "### Form Route",
59
+ "### Command Surface Route",
60
+ ],
61
+ "recipes/homepage.md": [
62
+ "type: Layout Recipe",
63
+ "screen_type: Homepage",
64
+ "## Recommended Pattern Stack",
65
+ "## Content-To-Layout Matching",
66
+ "## GPT Image Reference",
67
+ "Required preconditions before prompt:",
68
+ "Image review extract-vs-ignore:",
69
+ ],
70
+ "quality/gates/harmony-evaluation.md": [
71
+ "type: Quality Gate",
72
+ "## Required Contract",
73
+ "Content-to-layout match:",
74
+ "Overall harmony:",
75
+ "GPT Image reference:",
76
+ "Implementation handoff:",
77
+ "Rejected alternatives:",
78
+ "Do not generate a GPT Image reference before harmony evaluation approves the semantic order, section jobs, pattern stack, scroll owner, and constraints.",
79
+ "Source order, accessibility, brand correctness, and usability cannot be inferred from the image.",
80
+ "Final implementation proof:",
81
+ ],
82
+ "design-engineering/consumer-migration-readiness.md": [
83
+ "lifecycle: experimental",
84
+ "provenance_kind: local",
85
+ "# Consumer Migration Readiness",
86
+ "## Migration Dimension Contract",
87
+ "`behavior_inventory`",
88
+ "`route_parity`",
89
+ "`field_parity`",
90
+ "`action_parity`",
91
+ "`state_transitions`",
92
+ "`contract_precedence`",
93
+ "`direct_mutation`",
94
+ "`indirect_mutation`",
95
+ "`persistence_round_trip`",
96
+ "`reset_boundary`",
97
+ "`exact_time_boundary`",
98
+ "`defaults_tri_state_mapping`",
99
+ "`atomic_batch_behavior`",
100
+ "Consumer migration conformance: declared",
101
+ "Passing this method does not prove complete accessibility, product correctness, usability, visual quality, independent adoption, cross-browser equivalence, or owner approval.",
102
+ ],
103
+ "consumer-reference/contract.md": [
104
+ "### Migration-Only Extension",
105
+ "This field is migration-only.",
106
+ "Do not add it to generic blank handoffs",
107
+ "Consumer migration conformance: declared",
108
+ ],
109
+ "quality/gates/consumer-migration-evidence.md": [
110
+ "# Consumer Migration Evidence Gate",
111
+ "## Required Contract",
112
+ "## Blocking Conditions",
113
+ "thirteen migration dimensions",
114
+ "Decision: pass | block",
115
+ ],
116
+ };
117
+
118
+ const requiredLinks = {
119
+ "README.md": { target: "guides/webpage-generation-workflow.md", preferred: "[Webpage Generation Workflow](guides/webpage-generation-workflow.md)" },
120
+ "GUIDE.md": { target: "guides/webpage-generation-workflow.md", preferred: "[Webpage generation workflow](guides/webpage-generation-workflow.md)" },
121
+ "recipes/index.md": { target: "homepage.md", preferred: "[Homepage](homepage.md)" },
122
+ "quality/gates/index.md": { target: "harmony-evaluation.md", preferred: "[Harmony evaluation gate](harmony-evaluation.md)" },
123
+ };
124
+
125
+ const recommendedIncludes = {
126
+ "GUIDE.md": [
127
+ "content-to-layout match",
128
+ "harmony evaluation",
129
+ "GPT Image reference",
130
+ "implementation handoff",
131
+ ],
132
+ };
133
+
134
+ const routeSteps = [
135
+ "Content blocks",
136
+ "Section jobs",
137
+ "Recipe",
138
+ "Pattern stack",
139
+ "Scroll owner",
140
+ "Constraints",
141
+ "Harmony evaluation",
142
+ "GPT Image reference",
143
+ "Implementation handoff",
144
+ ];
145
+
146
+ const workflowRoutes = [
147
+ "Homepage",
148
+ "Dashboard",
149
+ "Article",
150
+ "Form",
151
+ "Command Surface",
152
+ ];
153
+
154
+ function read(relative) {
155
+ if (contents.has(relative)) return contents.get(relative);
156
+ const absolute = path.join(root, relative);
157
+ if (!fs.existsSync(absolute)) {
158
+ failures.push(`${relative}: missing file`);
159
+ contents.set(relative, "");
160
+ return "";
161
+ }
162
+ const content = fs.readFileSync(absolute, "utf8");
163
+ contents.set(relative, content);
164
+ return content;
165
+ }
166
+
167
+ for (const [relative, snippets] of Object.entries(requiredIncludes)) {
168
+ const content = read(relative);
169
+ for (const snippet of snippets) {
170
+ if (!content.includes(snippet)) failures.push(`${relative}: missing ${snippet}`);
171
+ }
172
+ }
173
+
174
+ for (const [relative, link] of Object.entries(requiredLinks)) {
175
+ const content = read(relative);
176
+ if (!content.includes(`](${link.target})`)) failures.push(`${relative}: missing link target ${link.target}`);
177
+ if (!content.includes(link.preferred)) warnings.push(`${relative}: recommended link label missing ${link.preferred}`);
178
+ }
179
+
180
+ for (const [relative, snippets] of Object.entries(recommendedIncludes)) {
181
+ const content = read(relative);
182
+ for (const snippet of snippets) {
183
+ if (!content.includes(snippet)) warnings.push(`${relative}: recommended prose missing ${snippet}`);
184
+ }
185
+ }
186
+
187
+ function routeSection(content, route) {
188
+ const heading = `### ${route} Route`;
189
+ const start = content.indexOf(heading);
190
+ if (start === -1) return "";
191
+ const afterHeading = content.slice(start + heading.length);
192
+ const nextHeading = afterHeading.search(/\n### /);
193
+ return nextHeading === -1 ? afterHeading : afterHeading.slice(0, nextHeading);
194
+ }
195
+
196
+ function requireRouteOrder(relative, route) {
197
+ const content = read(relative);
198
+ const section = routeSection(content, route);
199
+ if (!section) {
200
+ failures.push(`${relative}: missing ### ${route} Route`);
201
+ return;
202
+ }
203
+
204
+ const harmonyIndex = section.indexOf("Harmony evaluation");
205
+ const imageIndex = section.indexOf("GPT Image reference");
206
+ if (harmonyIndex !== -1 && imageIndex !== -1 && imageIndex < harmonyIndex) {
207
+ failures.push(`${relative}: route ${route} places GPT Image reference before Harmony evaluation`);
208
+ }
209
+
210
+ let previousIndex = -1;
211
+ let previousStep = "";
212
+ for (const step of routeSteps) {
213
+ const index = section.indexOf(step);
214
+ if (index === -1) {
215
+ failures.push(`${relative}: route ${route} missing ${step}`);
216
+ continue;
217
+ }
218
+ if (index < previousIndex) {
219
+ failures.push(`${relative}: route ${route} places ${step} before ${previousStep}`);
220
+ }
221
+ previousIndex = index;
222
+ previousStep = step;
223
+ }
224
+ }
225
+
226
+ for (const route of workflowRoutes) {
227
+ requireRouteOrder("guides/webpage-generation-workflow.md", route);
228
+ }
229
+
230
+ for (const relative of ["guides/layout-brief.md", "guides/webpage-generation-workflow.md", "quality/gates/harmony-evaluation.md"]) {
231
+ if (/^Consumer migration conformance: declared$/m.test(read(relative))) {
232
+ failures.push(`${relative}: ordinary handoff must not declare consumer migration conformance`);
233
+ }
234
+ }
235
+
236
+ const result = {
237
+ ok: failures.length === 0,
238
+ checkedFiles: [...new Set([...Object.keys(requiredIncludes), ...Object.keys(requiredLinks), ...Object.keys(recommendedIncludes)])],
239
+ checkedRequirements: Object.values(requiredIncludes).reduce((total, snippets) => total + snippets.length, 0)
240
+ + Object.keys(requiredLinks).length
241
+ + Object.values(recommendedIncludes).reduce((total, snippets) => total + snippets.length, 0),
242
+ failures,
243
+ warnings,
244
+ };
245
+
246
+ if (json) {
247
+ console.log(JSON.stringify(result, null, 2));
248
+ } else if (result.ok) {
249
+ console.log(`ok: ${result.checkedRequirements} webpage workflow requirements (${warnings.length} warnings)`);
250
+ } else {
251
+ console.error(result.failures.join("\n"));
252
+ }
253
+
254
+ process.exitCode = result.ok ? 0 : 1;
@@ -0,0 +1,20 @@
1
+ const environmentFields = Object.freeze([
2
+ "browser",
3
+ "browser_revision",
4
+ "container_image",
5
+ "platform",
6
+ "playwright",
7
+ "viewport",
8
+ ]);
9
+
10
+ function matchesEnvironment(approved, environment) {
11
+ return environmentFields.every((field) => approved[field] === environment[field]);
12
+ }
13
+
14
+ export function visualExpectationFor(scenario, environment, approvedEnvironments) {
15
+ const environments = approvedEnvironments.filter((approved) => matchesEnvironment(approved, environment));
16
+ if (environments.length !== 1) throw new Error(`capture environment requires exactly one approved visual environment, found ${environments.length}`);
17
+ const matches = scenario.expected.visual_image.filter((expectation) => expectation.environment_id === environments[0].id);
18
+ if (matches.length !== 1) throw new Error(`scenario ${scenario.id} requires exactly one approved visual expectation for ${environments[0].id}, found ${matches.length}`);
19
+ return matches[0];
20
+ }
@@ -0,0 +1,163 @@
1
+ function indentation(line) {
2
+ return line.length - line.trimStart().length;
3
+ }
4
+
5
+ function mappingKeyIndentation(line) {
6
+ const sequencePrefix = line.match(/^\s*-\s+/);
7
+ return sequencePrefix ? sequencePrefix[0].length : indentation(line);
8
+ }
9
+
10
+ function mappingEntry(line) {
11
+ const match = line.trim().replace(/^-\s+/, "").match(/^(?:([A-Za-z][A-Za-z0-9_-]*)|'([^']*)'|"([^"]*)")\s*:\s*(.*)$/);
12
+ if (!match) return null;
13
+ return { key: match[1] ?? match[2] ?? match[3], value: match[4] };
14
+ }
15
+
16
+ function isMappingHeader(entry, key) {
17
+ return entry?.key === key && (entry.value === "" || entry.value.startsWith("#"));
18
+ }
19
+
20
+ function isBlockScalarHeader(value) {
21
+ return /^[|>](?:[+-]?[1-9]|[1-9]?[+-])?(?:\s+#.*)?$/.test(value);
22
+ }
23
+
24
+ function structuralLines(lines) {
25
+ let blockScalarIndentation = null;
26
+ return lines.map((line) => {
27
+ if (line.trim() === "" || line.trimStart().startsWith("#")) return line;
28
+ const lineIndentation = indentation(line);
29
+ if (blockScalarIndentation !== null && lineIndentation > blockScalarIndentation) return "";
30
+ blockScalarIndentation = null;
31
+ const entry = mappingEntry(line);
32
+ if (entry && isBlockScalarHeader(entry.value)) blockScalarIndentation = lineIndentation;
33
+ return line;
34
+ });
35
+ }
36
+
37
+ function notationFailures(lines, relative) {
38
+ let actionBlockScalar = false;
39
+ let anchorsOrAliases = false;
40
+ let directives = false;
41
+ let documentMarkers = false;
42
+ let escapedDoubleQuotedKey = false;
43
+ let explicitMappingKey = false;
44
+ let flowCollectionValue = false;
45
+ let flowSequenceMapping = false;
46
+ let mergeKey = false;
47
+ let tags = false;
48
+ for (const line of lines) {
49
+ if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
50
+ const lineIndentation = indentation(line);
51
+ const trimmed = line.trim();
52
+ const structural = trimmed.replace(/^-\s+/, "");
53
+ if (lineIndentation === 0 && /^%(?:YAML|TAG)\b/.test(trimmed)) directives = true;
54
+ if (lineIndentation === 0 && /^(?:---|\.\.\.)(?:\s+#.*)?$/.test(trimmed)) documentMarkers = true;
55
+ if (/^\?(?:\s|$)/.test(structural)) explicitMappingKey = true;
56
+ if (/^!\S*(?:\s|$)/.test(structural)) tags = true;
57
+ if (/^[&*][^\s:[\]{},]+(?:\s|:|$)/.test(structural)) anchorsOrAliases = true;
58
+ if (/^<<\s*:/.test(structural)) mergeKey = true;
59
+ const quotedKey = trimmed.match(/^(?:-\s+)?"((?:[^"\\]|\\.)*)"\s*:/)?.[1];
60
+ if (quotedKey?.includes("\\")) escapedDoubleQuotedKey = true;
61
+ if (/^-\s*\{/.test(trimmed)) flowSequenceMapping = true;
62
+ const entry = mappingEntry(line);
63
+ if (/^[\[{]/.test(entry?.value ?? "") || (!/^-\s*\{/.test(trimmed) && /^[\[{]/.test(structural))) flowCollectionValue = true;
64
+ if (/^!\S*(?:\s|$)/.test(entry?.value ?? "")) tags = true;
65
+ if (/^[&*][^\s[\]{},]+(?:\s|$)/.test(entry?.value ?? "")) anchorsOrAliases = true;
66
+ if (entry?.key === "uses" && isBlockScalarHeader(entry.value)) actionBlockScalar = true;
67
+ }
68
+ const failures = [];
69
+ if (directives) failures.push(`${relative}: YAML directives are forbidden`);
70
+ if (documentMarkers) failures.push(`${relative}: YAML document markers are forbidden`);
71
+ if (explicitMappingKey) failures.push(`${relative}: explicit mapping keys are forbidden`);
72
+ if (tags) failures.push(`${relative}: YAML tags are forbidden`);
73
+ if (anchorsOrAliases) failures.push(`${relative}: YAML anchors and aliases are forbidden`);
74
+ if (mergeKey) failures.push(`${relative}: YAML merge keys are forbidden`);
75
+ if (actionBlockScalar) failures.push(`${relative}: block scalar action refs are forbidden`);
76
+ if (flowCollectionValue) failures.push(`${relative}: flow-style collection values are forbidden`);
77
+ if (flowSequenceMapping) failures.push(`${relative}: flow-style sequence mappings are forbidden`);
78
+ if (escapedDoubleQuotedKey) failures.push(`${relative}: escape sequences in double-quoted mapping keys are forbidden`);
79
+ return failures;
80
+ }
81
+
82
+ function directMappingLines(lines, headerIndex, headerIndentation) {
83
+ const nested = [];
84
+ for (const line of lines.slice(headerIndex + 1)) {
85
+ if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
86
+ const lineIndentation = indentation(line);
87
+ if (lineIndentation <= headerIndentation) break;
88
+ nested.push({ indentation: lineIndentation, trimmed: line.trim() });
89
+ }
90
+ if (nested.length === 0) return [];
91
+ const directIndentation = Math.min(...nested.map((line) => line.indentation));
92
+ return nested.filter((line) => line.indentation === directIndentation).map((line) => line.trimmed);
93
+ }
94
+
95
+ function actionRefFailures(lines, relative) {
96
+ const failures = [];
97
+ for (const line of lines) {
98
+ const entry = mappingEntry(line);
99
+ if (entry?.key !== "uses") continue;
100
+ const reference = entry.value.match(/^([^\s#]+)/)?.[1];
101
+ if (!reference) continue;
102
+ if (reference.startsWith("./")) continue;
103
+ if (reference.startsWith("docker://")) {
104
+ failures.push(`${relative}: docker action refs are forbidden ${line.trim().replace(/^-\s*/, "")}`);
105
+ continue;
106
+ }
107
+ if (!/^[^@\s]+@[a-f0-9]{40}$/i.test(reference)) failures.push(`${relative}: floating or unlabeled action ref ${line.trim().replace(/^-\s*/, "")}`);
108
+ }
109
+ return failures;
110
+ }
111
+
112
+ export function checkoutCredentialFailures(workflow, relative) {
113
+ const failures = [];
114
+ const lines = structuralLines(workflow.split("\n"));
115
+ for (const [index, line] of lines.entries()) {
116
+ const usesEntry = mappingEntry(line);
117
+ if (usesEntry?.key !== "uses" || !/^actions\/checkout@/i.test(usesEntry.value)) continue;
118
+ const usesIndentation = mappingKeyIndentation(line);
119
+ const withHeaders = [];
120
+ for (const [offset, following] of lines.slice(index + 1).entries()) {
121
+ if (following.trim() === "" || following.trimStart().startsWith("#")) continue;
122
+ const followingIndentation = indentation(following);
123
+ if (followingIndentation < usesIndentation) break;
124
+ if (followingIndentation === usesIndentation && isMappingHeader(mappingEntry(following), "with")) withHeaders.push(index + offset + 1);
125
+ }
126
+ const entries = withHeaders.length === 1 ? directMappingLines(lines, withHeaders[0], usesIndentation) : [];
127
+ const credentialEntries = entries.map(mappingEntry).filter((entry) => entry?.key === "persist-credentials");
128
+ const exactFalse = credentialEntries.length === 1 && /^false(?:\s+#.*)?$/.test(credentialEntries[0].value);
129
+ if (!exactFalse) failures.push(`${relative}: every actions/checkout step must set persist-credentials: false`);
130
+ }
131
+ return failures;
132
+ }
133
+
134
+ function permissionFailures(lines, relative) {
135
+ const failures = [];
136
+ const headers = lines
137
+ .map((line, index) => ({ entry: mappingEntry(line), index, indentation: indentation(line) }))
138
+ .filter(({ entry }) => entry?.key === "permissions");
139
+ if (headers.some((header) => header.indentation > 0)) failures.push(`${relative}: job-level permissions overrides are forbidden`);
140
+ const topLevel = headers.filter((header) => header.indentation === 0);
141
+ let exact = topLevel.length === 1 && isMappingHeader(topLevel[0].entry, "permissions");
142
+ if (exact) {
143
+ const entries = [];
144
+ for (const line of lines.slice(topLevel[0].index + 1)) {
145
+ if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
146
+ if (indentation(line) === 0) break;
147
+ entries.push(line.trim());
148
+ }
149
+ exact = entries.length === 1 && entries[0] === "contents: read";
150
+ }
151
+ if (!exact) failures.push(`${relative}: top-level permissions must be exactly contents: read`);
152
+ return failures;
153
+ }
154
+
155
+ export function workflowActionFailures(workflow, relative) {
156
+ const lines = structuralLines(workflow.split("\n"));
157
+ return [
158
+ ...notationFailures(lines, relative),
159
+ ...actionRefFailures(lines, relative),
160
+ ...checkoutCredentialFailures(workflow, relative),
161
+ ...permissionFailures(lines, relative),
162
+ ];
163
+ }