@astryxdesign/cli 0.6.3 → 0.6.4-canary.10dd683

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 (643) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +117 -78
  3. package/api/blog/blog.doc.mjs +1 -0
  4. package/api/build/_adapter.d.mts +50 -0
  5. package/api/build/_adapter.mjs +60 -0
  6. package/api/build/build.doc.mjs +16 -9
  7. package/api/build/build.test.mjs +197 -8
  8. package/api/build/build.type.d.mts +91 -2
  9. package/api/build/build.type.mjs +52 -8
  10. package/api/build/help/help.d.mts +12 -5
  11. package/api/build/help/help.mjs +69 -6
  12. package/api/build/kit/kit.d.mts +4 -1
  13. package/api/build/kit/kit.mjs +165 -49
  14. package/api/build/kit/rank.d.mts +44 -0
  15. package/api/build/kit/rank.mjs +432 -0
  16. package/api/build/kit/rank.test.mjs +196 -0
  17. package/api/component/_adapter.d.mts +6 -12
  18. package/api/component/_adapter.mjs +20 -10
  19. package/api/component/component.doc.mjs +13 -3
  20. package/api/component/component.mjs +91 -14
  21. package/api/component/component.test.mjs +38 -0
  22. package/api/component/component.type.d.mts +22 -11
  23. package/api/component/component.type.mjs +32 -24
  24. package/api/component/detail/blocks/blocks.d.mts +2 -1
  25. package/api/component/detail/blocks/blocks.mjs +4 -3
  26. package/api/component/list/list.d.mts +0 -5
  27. package/api/component/list/list.mjs +40 -11
  28. package/api/discover/_adapter.d.mts +114 -6
  29. package/api/discover/_adapter.mjs +372 -17
  30. package/api/discover/_adapter.test.mjs +215 -0
  31. package/api/discover/_catalog-view.d.mts +115 -0
  32. package/api/discover/_catalog-view.mjs +203 -0
  33. package/api/discover/_catalog-view.test.mjs +128 -0
  34. package/api/discover/detail/detail.d.mts +18 -6
  35. package/api/discover/detail/detail.mjs +67 -13
  36. package/api/discover/detail/detail.test.mjs +85 -0
  37. package/api/discover/detail/item/item.d.mts +26 -0
  38. package/api/discover/detail/item/item.mjs +78 -0
  39. package/api/discover/detail/item/item.test.mjs +73 -0
  40. package/api/discover/discover.d.mts +3 -9
  41. package/api/discover/discover.doc.mjs +62 -18
  42. package/api/discover/discover.mjs +220 -36
  43. package/api/discover/discover.test.mjs +11 -2
  44. package/api/discover/discover.type.d.mts +150 -11
  45. package/api/discover/discover.type.mjs +107 -17
  46. package/api/discover/list/list.d.mts +20 -6
  47. package/api/discover/list/list.mjs +45 -12
  48. package/api/discover/list/list.test.mjs +46 -0
  49. package/api/discover/search/search.d.mts +18 -16
  50. package/api/discover/search/search.mjs +102 -56
  51. package/api/discover/search/search.test.mjs +144 -10
  52. package/api/docs/_adapter.d.mts +272 -41
  53. package/api/docs/_adapter.mjs +985 -108
  54. package/api/docs/compiled-topics.test.mjs +78 -0
  55. package/api/docs/detail/detail.mjs +22 -63
  56. package/api/docs/detail/section/section.d.mts +1 -1
  57. package/api/docs/detail/section/section.mjs +54 -19
  58. package/api/docs/detail/section/section.test.mjs +50 -0
  59. package/api/docs/docs.d.mts +10 -3
  60. package/api/docs/docs.doc.mjs +55 -16
  61. package/api/docs/docs.mjs +53 -10
  62. package/api/docs/docs.test.mjs +164 -4
  63. package/api/docs/docs.type.d.mts +221 -5
  64. package/api/docs/docs.type.mjs +153 -11
  65. package/api/docs/index/index.d.mts +18 -0
  66. package/api/docs/index/index.mjs +40 -0
  67. package/api/docs/index/index.test.mjs +62 -0
  68. package/api/docs/integration-tree.test.mjs +555 -0
  69. package/api/docs/integrationDocs.test.mjs +114 -8
  70. package/api/docs/list/list.mjs +28 -12
  71. package/api/docs/node/node.d.mts +43 -0
  72. package/api/docs/node/node.mjs +192 -0
  73. package/api/docs/reference-blocks.test.mjs +406 -0
  74. package/api/doctor/doctor.d.mts +99 -1
  75. package/api/doctor/doctor.doc.mjs +1 -0
  76. package/api/doctor/doctor.mjs +548 -1
  77. package/api/doctor/doctor.test.mjs +610 -1
  78. package/api/gap-report/gap-report.doc.mjs +8 -4
  79. package/api/hook/_adapter.mjs +19 -5
  80. package/api/hook/hook.doc.mjs +1 -0
  81. package/api/hook/hook.type.d.mts +3 -3
  82. package/api/hook/hook.type.mjs +11 -11
  83. package/api/hook/list/list.d.mts +2 -2
  84. package/api/hook/list/list.mjs +69 -17
  85. package/api/index.d.mts +1 -1
  86. package/api/index.mjs +1 -0
  87. package/api/init/init.doc.mjs +6 -1
  88. package/api/init/init.test.mjs +41 -1
  89. package/api/init/remove/remove.mjs +1 -1
  90. package/api/init/run/run.mjs +20 -10
  91. package/api/integration/add-contribution.component-names.test.mjs +120 -0
  92. package/api/integration/add-contribution.d.mts +2 -1
  93. package/api/integration/add-contribution.mjs +130 -15
  94. package/api/integration/add-contribution.test.mjs +258 -7
  95. package/api/integration/add-helpers.d.mts +5 -2
  96. package/api/integration/add-helpers.mjs +36 -9
  97. package/api/integration/add-theme.mjs +34 -64
  98. package/api/integration/add-theme.test.mjs +105 -21
  99. package/api/integration/authoring-checks.mjs +138 -28
  100. package/api/integration/authoring-checks.test.mjs +179 -7
  101. package/api/integration/authoring-checks.type.mjs +6 -1
  102. package/api/integration/integration-authoring.type.d.mts +3 -1
  103. package/api/integration/integration-authoring.type.mjs +2 -0
  104. package/api/integration/integration-block-exports.test.mjs +10 -6
  105. package/api/integration/integrationAdd.doc.mjs +14 -4
  106. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  107. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  108. package/api/integration/integrationAddComponent.doc.mjs +2 -1
  109. package/api/integration/integrationAddDoc.doc.mjs +8 -1
  110. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  111. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  112. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  113. package/api/integration/integrationDocConflicts.doc.mjs +2 -1
  114. package/api/integration/integrationPackCheck.doc.mjs +2 -1
  115. package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
  116. package/api/integration/integrationTemplateConflicts.doc.mjs +4 -0
  117. package/api/integration/pack-check.mjs +83 -7
  118. package/api/integration/pack-check.test.mjs +387 -47
  119. package/api/integration/pack-check.type.d.mts +26 -2
  120. package/api/integration/pack-check.type.mjs +14 -1
  121. package/api/integration/summarizeIssues.doc.mjs +1 -0
  122. package/api/integration/template-conflict-compatibility.test.mjs +73 -0
  123. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  124. package/api/integration/validate-integration.mjs +52 -102
  125. package/api/integration/validate-integration.test.mjs +179 -26
  126. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  127. package/api/integration/validateIntegration.doc.mjs +3 -2
  128. package/api/json/assertResponse.doc.mjs +1 -0
  129. package/api/json/envelope-types.test.mjs +76 -0
  130. package/api/json/index.ts +2 -0
  131. package/api/json/isError.doc.mjs +1 -0
  132. package/api/json/parseResponse.doc.mjs +3 -2
  133. package/api/layout/_adapter.mjs +20 -5
  134. package/api/layout/expand/expand.mjs +7 -5
  135. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  136. package/api/layout/grammar/grammar.mjs +2 -1
  137. package/api/layout/layoutCheck.doc.mjs +1 -0
  138. package/api/layout/layoutExpand.doc.mjs +2 -1
  139. package/api/layout/layoutGrammar.doc.mjs +1 -0
  140. package/api/search/search-return-type.test.mjs +54 -0
  141. package/api/search/search.d.mts +62 -11
  142. package/api/search/search.doc.mjs +8 -2
  143. package/api/search/search.mjs +471 -83
  144. package/api/search/search.test.mjs +124 -1
  145. package/api/search/search.type.d.mts +15 -3
  146. package/api/search/search.type.mjs +5 -2
  147. package/api/swizzle/copy/copy.mjs +28 -11
  148. package/api/swizzle/swizzle.doc.mjs +2 -1
  149. package/api/swizzle/swizzle.type.d.mts +2 -2
  150. package/api/swizzle/swizzle.type.mjs +2 -2
  151. package/api/template/copy/copy.mjs +17 -23
  152. package/api/template/copy/copy.test.mjs +17 -0
  153. package/api/template/list/list.mjs +1 -0
  154. package/api/template/table-floating-bulk-actions.test.mjs +66 -0
  155. package/api/template/template-integration.test.mjs +1072 -3
  156. package/api/template/template-suffix.test.mjs +41 -21
  157. package/api/template/template.d.mts +1 -1
  158. package/api/template/template.doc.mjs +30 -8
  159. package/api/template/template.mjs +45 -8
  160. package/api/template/template.type.d.mts +12 -14
  161. package/api/template/template.type.mjs +15 -14
  162. package/api/theme/_adapter.d.mts +2 -3
  163. package/api/theme/_adapter.mjs +4 -5
  164. package/api/theme/add/add.binary.test.mjs +84 -0
  165. package/api/theme/add/add.mjs +31 -22
  166. package/api/theme/add/add.rollback.test.mjs +158 -0
  167. package/api/theme/add/add.staging.test.mjs +83 -0
  168. package/api/theme/add/add.test.mjs +14 -1
  169. package/api/theme/build/build.family.test.mjs +7 -12
  170. package/api/theme/build/build.mjs +140 -59
  171. package/api/theme/build/build.public-component-vars.test.mjs +1 -1
  172. package/api/theme/build/build.receipt-doc.test.mjs +111 -0
  173. package/api/theme/build/build.rollback.test.mjs +148 -0
  174. package/api/theme/build/build.test.mjs +127 -0
  175. package/api/theme/build/font-warning.mjs +3 -3
  176. package/api/theme/build/font-warning.test.mjs +5 -2
  177. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  178. package/api/theme/integration-themes.test.mjs +39 -28
  179. package/api/theme/list/list.test.mjs +19 -20
  180. package/api/theme/listThemes.doc.mjs +6 -5
  181. package/api/theme/palette/generate/generate.mjs +8 -3
  182. package/api/theme/palette/generate/generate.test.mjs +96 -0
  183. package/api/theme/palette/generate/generator.d.mts +10 -13
  184. package/api/theme/palette/generate/generator.mjs +15 -4
  185. package/api/theme/palette/generate/generator.test.mjs +10 -0
  186. package/api/theme/template/template.mjs +11 -2
  187. package/api/theme/template/template.test.mjs +20 -0
  188. package/api/theme/theme.type.d.mts +170 -11
  189. package/api/theme/theme.type.mjs +94 -27
  190. package/api/theme/themeAdd.doc.mjs +4 -3
  191. package/api/theme/themeBuild.doc.mjs +8 -4
  192. package/api/theme/themeList.doc.mjs +6 -3
  193. package/api/theme/themeListAvailable.doc.mjs +5 -3
  194. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  195. package/api/theme/themeTargets.doc.mjs +1 -0
  196. package/api/theme/themeTemplate.doc.mjs +6 -2
  197. package/api/upgrade/_adapter.d.mts +32 -5
  198. package/api/upgrade/_adapter.mjs +139 -22
  199. package/api/upgrade/list/list.mjs +2 -1
  200. package/api/upgrade/list/list.test.mjs +73 -0
  201. package/api/upgrade/project-context.test.mjs +272 -0
  202. package/api/upgrade/provider-agreement.test.mjs +152 -0
  203. package/api/upgrade/run/run.mjs +356 -59
  204. package/api/upgrade/status/status.mjs +2 -2
  205. package/api/upgrade/upgrade.doc.mjs +12 -5
  206. package/api/upgrade/upgrade.type.d.mts +43 -5
  207. package/api/upgrade/upgrade.type.mjs +27 -11
  208. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  209. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  210. package/assets/codemods/integration-discovery.mjs +48 -4
  211. package/assets/codemods/integration-discovery.test.mjs +73 -0
  212. package/assets/codemods/integration-runner.mjs +56 -4
  213. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  214. package/assets/codemods/registry.mjs +1 -0
  215. package/assets/codemods/run-codemod.mjs +177 -34
  216. package/assets/codemods/runner.mjs +350 -102
  217. package/assets/codemods/term-log.mjs +32 -8
  218. package/assets/codemods/term-log.test.mjs +19 -1
  219. package/assets/codemods/transform-prop.mjs +109 -0
  220. package/assets/codemods/transform-prop.test.mjs +95 -0
  221. package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
  222. package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
  223. package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
  224. package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
  225. package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
  226. package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
  227. package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
  228. package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
  229. package/assets/codemods/transforms/v0.6.4/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  230. package/assets/codemods/transforms/v0.6.4/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  231. package/assets/codemods/transforms/v0.6.4/index.mjs +31 -0
  232. package/assets/codemods/transforms/v0.6.4/migrate-native-picker-to-presentation.mjs +148 -0
  233. package/assets/codemods/transforms/v0.6.4/migrate-theme-catalog-to-descriptors.mjs +141 -0
  234. package/assets/docs/README.md +9 -0
  235. package/assets/docs/authoring.doc.mjs +14 -0
  236. package/assets/docs/getting-started.doc.mjs +2 -2
  237. package/assets/docs/internationalization.doc.mjs +7 -5
  238. package/assets/docs/layout.doc.dense.mjs +2 -2
  239. package/assets/docs/layout.doc.mjs +1 -1
  240. package/assets/docs/principles.doc.mjs +6 -6
  241. package/assets/docs/styling-libraries.doc.mjs +4 -4
  242. package/assets/docs/styling.doc.mjs +4 -4
  243. package/assets/docs/theme.doc.mjs +5 -5
  244. package/assets/docs/tokens.doc.mjs +1 -1
  245. package/assets/docs/tree/api.doc.mjs +30 -0
  246. package/assets/docs/tree/cli.doc.mjs +23 -0
  247. package/assets/docs/tree/commands.doc.mjs +25 -0
  248. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +144 -26
  249. package/assets/docs/tree/integrations.test.mjs +62 -0
  250. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  251. package/assets/docs/working-with-ai.doc.mjs +4 -4
  252. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
  253. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
  254. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  255. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  256. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  257. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  258. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
  259. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
  260. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
  261. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
  262. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
  263. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
  264. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
  265. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
  266. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
  267. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
  268. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
  269. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
  270. package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
  271. package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
  272. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
  273. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
  274. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  275. package/assets/templates/blocks/components/Timer/TimerFormats.doc.mjs +14 -0
  276. package/assets/templates/blocks/components/Timer/TimerFormats.tsx +34 -0
  277. package/assets/templates/blocks/components/Timer/TimerInline.doc.mjs +14 -0
  278. package/assets/templates/blocks/components/Timer/TimerInline.tsx +14 -0
  279. package/assets/templates/blocks/components/Timer/TimerShowcase.doc.mjs +13 -0
  280. package/assets/templates/blocks/components/Timer/TimerShowcase.tsx +47 -0
  281. package/assets/templates/blocks/components/Timer/TimerTypography.doc.mjs +14 -0
  282. package/assets/templates/blocks/components/Timer/TimerTypography.tsx +31 -0
  283. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +19 -3
  284. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +383 -65
  285. package/assets/templates/pages/table-tree/page.tsx +1704 -0
  286. package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
  287. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  288. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  289. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  290. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  291. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  292. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  293. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  294. package/authoring/_shared/contract.ts +22 -0
  295. package/authoring/codemod/codemod.doc.mjs +7 -2
  296. package/authoring/codemod/parse.d.mts +8 -8
  297. package/authoring/codemod/parse.mjs +8 -6
  298. package/authoring/codemod/type.ts +12 -0
  299. package/authoring/config/config.doc.mjs +10 -2
  300. package/authoring/config/debug-composition.test.mjs +92 -0
  301. package/authoring/config/parse.d.mts +15 -13
  302. package/authoring/config/parse.mjs +27 -8
  303. package/authoring/config/parse.test.mjs +8 -0
  304. package/authoring/config/type.ts +29 -6
  305. package/authoring/debug/debug.doc.d.mts +11 -0
  306. package/authoring/debug/debug.doc.mjs +182 -0
  307. package/authoring/debug/parse.d.mts +8 -8
  308. package/authoring/debug/parse.mjs +3 -3
  309. package/authoring/discover/discover.doc.d.mts +13 -0
  310. package/authoring/discover/discover.doc.mjs +138 -0
  311. package/authoring/discover/parse.d.mts +24 -0
  312. package/authoring/discover/parse.mjs +128 -0
  313. package/authoring/discover/parse.test.mjs +124 -0
  314. package/authoring/discover/type.ts +87 -0
  315. package/authoring/doctypes/_schema.d.mts +790 -23
  316. package/authoring/doctypes/_schema.mjs +543 -39
  317. package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
  318. package/authoring/doctypes/base/graph-fields.doc.mjs +64 -0
  319. package/authoring/doctypes/base/type.ts +41 -0
  320. package/authoring/doctypes/command/command.doc.mjs +4 -3
  321. package/authoring/doctypes/command/parse.d.mts +2 -2
  322. package/authoring/doctypes/command/parse.mjs +1 -1
  323. package/authoring/doctypes/command/type.ts +5 -4
  324. package/authoring/doctypes/component/component.doc.mjs +6 -3
  325. package/authoring/doctypes/component/parse.d.mts +2 -2
  326. package/authoring/doctypes/component/parse.mjs +1 -1
  327. package/authoring/doctypes/component/type.ts +6 -5
  328. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  329. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  330. package/authoring/doctypes/enum/parse.d.mts +2 -2
  331. package/authoring/doctypes/enum/parse.mjs +1 -1
  332. package/authoring/doctypes/enum/type.ts +4 -2
  333. package/authoring/doctypes/function/function.doc.mjs +7 -2
  334. package/authoring/doctypes/function/parse.d.mts +2 -2
  335. package/authoring/doctypes/function/parse.mjs +1 -1
  336. package/authoring/doctypes/function/type.ts +9 -4
  337. package/authoring/doctypes/hook/hook.doc.mjs +4 -0
  338. package/authoring/doctypes/hook/parse.d.mts +2 -2
  339. package/authoring/doctypes/hook/parse.mjs +1 -1
  340. package/authoring/doctypes/hook/type.ts +5 -4
  341. package/authoring/doctypes/legacy.d.mts +8 -6
  342. package/authoring/doctypes/legacy.mjs +5 -4
  343. package/authoring/doctypes/load-contract.test.mjs +233 -0
  344. package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
  345. package/authoring/doctypes/namespace/namespace.doc.mjs +128 -0
  346. package/authoring/doctypes/namespace/parse.d.mts +12 -0
  347. package/authoring/doctypes/namespace/parse.mjs +25 -0
  348. package/authoring/doctypes/namespace/parse.test.mjs +163 -0
  349. package/authoring/doctypes/namespace/type.ts +74 -0
  350. package/authoring/doctypes/parse.d.mts +22 -18
  351. package/authoring/doctypes/parse.mjs +22 -11
  352. package/authoring/doctypes/parse.test.mjs +77 -3
  353. package/authoring/doctypes/reference/parse.d.mts +2 -2
  354. package/authoring/doctypes/reference/parse.mjs +8 -5
  355. package/authoring/doctypes/reference/reference.doc.mjs +48 -6
  356. package/authoring/doctypes/reference/type.ts +70 -7
  357. package/authoring/doctypes/schema/parse.d.mts +2 -2
  358. package/authoring/doctypes/schema/parse.mjs +1 -1
  359. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  360. package/authoring/doctypes/schema/type.ts +4 -4
  361. package/authoring/doctypes/template/parse.d.mts +94 -1
  362. package/authoring/doctypes/template/parse.mjs +40 -2
  363. package/authoring/doctypes/template/parse.test.mjs +26 -2
  364. package/authoring/doctypes/template/template.doc.mjs +13 -3
  365. package/authoring/doctypes/template/type.ts +13 -2
  366. package/authoring/doctypes/theme/parse.d.mts +35 -0
  367. package/authoring/doctypes/theme/parse.mjs +76 -0
  368. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  369. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  370. package/authoring/doctypes/theme/type.ts +42 -0
  371. package/authoring/doctypes/types.ts +12 -10
  372. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  373. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  374. package/authoring/gap-report/parse.d.mts +10 -10
  375. package/authoring/gap-report/parse.mjs +6 -6
  376. package/authoring/gap-report/type.ts +1 -1
  377. package/authoring/identity/identity.doc.d.mts +9 -0
  378. package/authoring/identity/identity.doc.mjs +61 -0
  379. package/authoring/identity/type.ts +132 -0
  380. package/authoring/index.d.mts +3 -0
  381. package/authoring/index.d.ts +62 -17
  382. package/authoring/index.mjs +4 -1
  383. package/authoring/integration/integration.doc.mjs +15 -8
  384. package/authoring/integration/parse.d.mts +2 -2
  385. package/authoring/integration/parse.mjs +1 -1
  386. package/authoring/integration/parse.test.mjs +10 -1
  387. package/authoring/integration/schema.d.mts +6 -4
  388. package/authoring/integration/schema.mjs +9 -3
  389. package/authoring/integration/type.ts +19 -8
  390. package/authoring/shadcn/receipt.d.mts +6 -6
  391. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  392. package/clients/cli/command-load-failure.test.mjs +83 -0
  393. package/clients/cli/commands/blog.doc.mjs +1 -1
  394. package/clients/cli/commands/blog.mjs +23 -8
  395. package/clients/cli/commands/blog.test.mjs +42 -1
  396. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  397. package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
  398. package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
  399. package/clients/cli/commands/build-theme.mjs +16 -50
  400. package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
  401. package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
  402. package/clients/cli/commands/build.doc.mjs +16 -8
  403. package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
  404. package/clients/cli/commands/build.mjs +137 -114
  405. package/clients/cli/commands/build.playbook.test.mjs +75 -0
  406. package/clients/cli/commands/build.text-fields.test.mjs +81 -0
  407. package/clients/cli/commands/component/index.mjs +3 -8
  408. package/clients/cli/commands/component-ownership.test.mjs +3 -3
  409. package/clients/cli/commands/component-package.test.mjs +46 -0
  410. package/clients/cli/commands/component-resolution.test.mjs +21 -0
  411. package/clients/cli/commands/component.doc.mjs +1 -1
  412. package/clients/cli/commands/component.test.mjs +19 -0
  413. package/clients/cli/commands/detail-levels.test.mjs +2 -2
  414. package/clients/cli/commands/discover.broken-integration.test.mjs +52 -5
  415. package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
  416. package/clients/cli/commands/discover.doc.mjs +55 -9
  417. package/clients/cli/commands/discover.mjs +393 -118
  418. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  419. package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
  420. package/clients/cli/commands/docs.doc.mjs +28 -6
  421. package/clients/cli/commands/docs.mjs +240 -26
  422. package/clients/cli/commands/docs.test.mjs +193 -1
  423. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  424. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -3
  425. package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
  426. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  427. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  428. package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
  429. package/clients/cli/commands/doctor-integration.test.mjs +90 -8
  430. package/clients/cli/commands/doctor.doc.mjs +1 -1
  431. package/clients/cli/commands/doctor.mjs +59 -32
  432. package/clients/cli/commands/doctor.test.mjs +42 -0
  433. package/clients/cli/commands/gap-report.doc.mjs +17 -6
  434. package/clients/cli/commands/gap-report.test.mjs +72 -0
  435. package/clients/cli/commands/hook/index.mjs +7 -17
  436. package/clients/cli/commands/hook.doc.mjs +1 -1
  437. package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
  438. package/clients/cli/commands/init.doc.mjs +20 -9
  439. package/clients/cli/commands/init.flag-help.test.mjs +153 -0
  440. package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
  441. package/clients/cli/commands/integration-add.doc.mjs +32 -6
  442. package/clients/cli/commands/integration-authoring.test.mjs +13 -9
  443. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  444. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  445. package/clients/cli/commands/integration.doc.mjs +1 -1
  446. package/clients/cli/commands/integration.mjs +1 -0
  447. package/clients/cli/commands/interactive-guard.test.mjs +101 -24
  448. package/clients/cli/commands/json-contract.test.mjs +33 -0
  449. package/clients/cli/commands/layout-check.doc.mjs +15 -4
  450. package/clients/cli/commands/layout-expand.doc.mjs +22 -5
  451. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  452. package/clients/cli/commands/layout.doc.mjs +3 -3
  453. package/clients/cli/commands/layout.mjs +21 -9
  454. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  455. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  456. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  457. package/clients/cli/commands/manifest.doc.mjs +1 -1
  458. package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
  459. package/clients/cli/commands/search.doc.mjs +7 -4
  460. package/clients/cli/commands/search.mjs +28 -9
  461. package/clients/cli/commands/search.test.mjs +75 -0
  462. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  463. package/clients/cli/commands/swizzle.doc.mjs +3 -2
  464. package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
  465. package/clients/cli/commands/template.doc.mjs +52 -13
  466. package/clients/cli/commands/template.flag-help.test.mjs +117 -0
  467. package/clients/cli/commands/template.mjs +4 -91
  468. package/clients/cli/commands/template.path-help.test.mjs +40 -0
  469. package/clients/cli/commands/text-json-parity.test.mjs +719 -0
  470. package/clients/cli/commands/theme-add.doc.mjs +4 -3
  471. package/clients/cli/commands/theme-build.doc.mjs +8 -7
  472. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  473. package/clients/cli/commands/theme-palette-generate.doc.mjs +9 -5
  474. package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
  475. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  476. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  477. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  478. package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
  479. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  480. package/clients/cli/commands/theme.doc.mjs +1 -1
  481. package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
  482. package/clients/cli/commands/upgrade.doc.mjs +22 -10
  483. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  484. package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
  485. package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
  486. package/clients/cli/commands/upgrade.mjs +29 -7
  487. package/clients/cli/formatters/index.mjs +164 -1
  488. package/clients/cli/formatters/index.test.mjs +97 -0
  489. package/clients/cli/index.mjs +21 -30
  490. package/clients/cli/latest-version-env.test.mjs +50 -0
  491. package/clients/cli/lib/cli-error.test.mjs +7 -0
  492. package/clients/cli/lib/component-format.mjs +9 -9
  493. package/clients/cli/lib/component-format.test.mjs +1 -1
  494. package/clients/cli/lib/define-command.mjs +32 -6
  495. package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
  496. package/clients/cli/lib/exit-codes.test.mjs +97 -0
  497. package/clients/cli/lib/hook-format.mjs +19 -10
  498. package/clients/cli/lib/json-shim.mjs +38 -2
  499. package/clients/cli/lib/json-shim.test.mjs +83 -0
  500. package/clients/cli/lib/manifest.d.ts +2 -0
  501. package/clients/cli/lib/manifest.mjs +37 -2
  502. package/clients/cli/lib/manifest.test.mjs +17 -0
  503. package/foundation/agent-docs/agent-docs.d.mts +7 -2
  504. package/foundation/agent-docs/agent-docs.mjs +82 -12
  505. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
  506. package/foundation/agent-docs/agent-docs.test.mjs +19 -1
  507. package/foundation/config/integration-debug.test.mjs +28 -3
  508. package/foundation/config/project-themes.test.mjs +11 -19
  509. package/foundation/config/project.d.mts +20 -11
  510. package/foundation/config/project.mjs +263 -91
  511. package/foundation/config/project.test.mjs +270 -21
  512. package/foundation/discovery/authoring-self-docs.d.mts +87 -0
  513. package/foundation/discovery/authoring-self-docs.mjs +237 -0
  514. package/foundation/discovery/authoring-self-docs.test.mjs +170 -0
  515. package/foundation/discovery/authoring-surface.d.mts +74 -0
  516. package/foundation/discovery/authoring-surface.mjs +525 -0
  517. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  518. package/foundation/discovery/cli-self-docs.d.mts +119 -0
  519. package/foundation/discovery/cli-self-docs.mjs +490 -0
  520. package/foundation/discovery/cli-self-docs.test.mjs +375 -0
  521. package/foundation/discovery/component-discovery.d.mts +39 -1
  522. package/foundation/discovery/component-discovery.mjs +50 -1
  523. package/foundation/discovery/component-loader.d.mts +35 -38
  524. package/foundation/discovery/component-loader.mjs +53 -222
  525. package/foundation/discovery/docs-discovery.d.mts +119 -11
  526. package/foundation/discovery/docs-discovery.mjs +423 -108
  527. package/foundation/discovery/docs-discovery.test.mjs +365 -20
  528. package/foundation/discovery/docs-output-budget.d.mts +28 -0
  529. package/foundation/discovery/docs-output-budget.mjs +50 -0
  530. package/foundation/discovery/docs-section-key.d.mts +116 -0
  531. package/foundation/discovery/docs-section-key.mjs +322 -0
  532. package/foundation/discovery/docs-section-key.test.mjs +246 -0
  533. package/foundation/discovery/template-adapter.d.mts +113 -11
  534. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
  535. package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
  536. package/foundation/discovery/template-adapter.mjs +774 -83
  537. package/foundation/discovery/template-adapter.test.mjs +57 -0
  538. package/foundation/discovery/template-conflict-release.d.mts +13 -0
  539. package/foundation/discovery/template-conflict-release.mjs +40 -0
  540. package/foundation/discovery/template-conflict-release.test.mjs +40 -0
  541. package/foundation/discovery/theme-discovery.d.mts +67 -7
  542. package/foundation/discovery/theme-discovery.mjs +916 -186
  543. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  544. package/foundation/discovery/theming-targets.test.mjs +4 -0
  545. package/foundation/doc-compiler/bundle.d.mts +47 -0
  546. package/foundation/doc-compiler/bundle.mjs +278 -0
  547. package/foundation/doc-compiler/bundle.test.mjs +266 -0
  548. package/foundation/doc-compiler/compile.d.mts +343 -0
  549. package/foundation/doc-compiler/compile.mjs +558 -0
  550. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  551. package/foundation/doc-compiler/diagnostics.mjs +305 -0
  552. package/foundation/doc-compiler/doc-compiler.test.mjs +714 -0
  553. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  554. package/foundation/doc-compiler/import.d.mts +24 -0
  555. package/foundation/doc-compiler/import.mjs +59 -0
  556. package/foundation/doc-compiler/inputs.d.mts +102 -0
  557. package/foundation/doc-compiler/inputs.mjs +291 -0
  558. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  559. package/foundation/doc-compiler/ir.d.mts +22 -0
  560. package/foundation/doc-compiler/ir.mjs +471 -0
  561. package/foundation/doc-compiler/lenses.d.mts +36 -0
  562. package/foundation/doc-compiler/lenses.mjs +173 -0
  563. package/foundation/doc-compiler/links.d.mts +162 -0
  564. package/foundation/doc-compiler/links.mjs +294 -0
  565. package/foundation/doc-compiler/links.test.mjs +192 -0
  566. package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
  567. package/foundation/doc-compiler/overlays.d.mts +37 -0
  568. package/foundation/doc-compiler/overlays.mjs +206 -0
  569. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  570. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  571. package/foundation/doc-compiler/read.d.mts +127 -0
  572. package/foundation/doc-compiler/read.mjs +325 -0
  573. package/foundation/doc-compiler/read.test.mjs +313 -0
  574. package/foundation/doc-compiler/source.d.mts +33 -0
  575. package/foundation/doc-compiler/source.mjs +128 -0
  576. package/foundation/doc-compiler/tree.d.mts +288 -0
  577. package/foundation/doc-compiler/tree.mjs +876 -0
  578. package/foundation/doc-compiler/tree.test.mjs +598 -0
  579. package/foundation/fs/file-protection.d.mts +33 -0
  580. package/foundation/fs/file-protection.mjs +825 -0
  581. package/foundation/fs/file-protection.test.mjs +250 -0
  582. package/foundation/fs/module-loader.d.mts +1 -0
  583. package/foundation/fs/module-loader.mjs +50 -1
  584. package/foundation/fs/module-loader.stdout.test.mjs +332 -0
  585. package/foundation/fs/path-safety.d.mts +3 -2
  586. package/foundation/fs/path-safety.mjs +49 -19
  587. package/foundation/fs/path-safety.test.mjs +50 -0
  588. package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
  589. package/foundation/identity/provider-identity.d.mts +90 -0
  590. package/foundation/identity/provider-identity.mjs +320 -0
  591. package/foundation/identity/provider-identity.test.mjs +254 -0
  592. package/foundation/identity/providers.d.mts +7 -0
  593. package/foundation/identity/providers.mjs +16 -0
  594. package/foundation/integrations/autolink.d.mts +58 -1
  595. package/foundation/integrations/autolink.mjs +143 -45
  596. package/foundation/integrations/autolink.test.mjs +1 -1
  597. package/foundation/integrations/cli-requirement.d.mts +45 -0
  598. package/foundation/integrations/cli-requirement.mjs +154 -0
  599. package/foundation/integrations/cli-requirement.test.mjs +84 -0
  600. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  601. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  602. package/foundation/integrations/contribution-inventory.d.mts +3 -2
  603. package/foundation/integrations/contribution-inventory.mjs +27 -24
  604. package/foundation/integrations/contribution-inventory.test.mjs +86 -27
  605. package/foundation/integrations/integration-warnings.d.mts +9 -2
  606. package/foundation/integrations/integration-warnings.mjs +52 -21
  607. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  608. package/foundation/integrations/integrations.d.mts +63 -3
  609. package/foundation/integrations/integrations.mjs +122 -9
  610. package/foundation/integrations/integrations.test.mjs +415 -1
  611. package/foundation/integrations/provider-conflicts.test.mjs +125 -0
  612. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  613. package/foundation/integrations/provider-resolution.d.mts +152 -0
  614. package/foundation/integrations/provider-resolution.mjs +576 -0
  615. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  616. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  617. package/foundation/integrations/theme-descriptor.mjs +44 -0
  618. package/foundation/integrations/validate-contributions.d.mts +2 -0
  619. package/foundation/integrations/validate-contributions.mjs +131 -29
  620. package/foundation/response/base.d.ts +8 -4
  621. package/foundation/response/error-codes.d.mts +3 -1
  622. package/foundation/response/error-codes.d.ts +2 -0
  623. package/foundation/response/error-codes.doc.mjs +13 -4
  624. package/foundation/response/error-codes.mjs +8 -2
  625. package/foundation/response/error-codes.test.mjs +137 -10
  626. package/foundation/response/json-contract.test.mjs +57 -17
  627. package/foundation/response/json.d.mts +4 -2
  628. package/foundation/response/json.mjs +8 -10
  629. package/foundation/response/response-types.doc.d.mts +5 -1
  630. package/foundation/response/response-types.doc.mjs +41 -21
  631. package/foundation/response/response-types.doc.test.mjs +158 -0
  632. package/foundation/response/response.doc.mjs +1 -1
  633. package/foundation/text/string-utils.d.mts +8 -0
  634. package/foundation/text/string-utils.mjs +40 -10
  635. package/foundation/xle/expand.d.mts +2 -0
  636. package/foundation/xle/expand.mjs +4 -3
  637. package/foundation/xle/expand.test.mjs +54 -0
  638. package/foundation/xle/xle.test.mjs +13 -0
  639. package/package.json +10 -11
  640. package/assets/templates/themes/manifest.json +0 -95
  641. package/clients/cli/lib/update-check.mjs +0 -83
  642. package/clients/cli/lib/update-check.test.mjs +0 -137
  643. package/clients/cli/update-hint-commands.test.mjs +0 -54
@@ -12,7 +12,8 @@ import * as fs from 'node:fs';
12
12
  import * as path from 'node:path';
13
13
  import * as p from './term-log.mjs';
14
14
  import {humanLog} from '../../foundation/response/json.mjs';
15
- import {runConfigCodemod} from './run-codemod.mjs';
15
+ import {runCodeCodemod, runConfigCodemod} from './run-codemod.mjs';
16
+ import {createFileProtectionResolver} from '../../foundation/fs/file-protection.mjs';
16
17
 
17
18
  // Known corruption patterns that indicate a broken transform.
18
19
  // Each entry: [regex, human-readable description]
@@ -46,20 +47,30 @@ export function fixDirectiveCorruption(code) {
46
47
  }
47
48
 
48
49
  /**
49
- * Directories a source scan must never descend into: dependencies, VCS, and
50
- * generated build output (codemods rewrite source, not artifacts).
50
+ * Directories a source scan must never descend into. Generated-looking names
51
+ * are intentionally absent: protection comes from checkout declarations, not a
52
+ * directory-name heuristic.
51
53
  */
52
54
  const IGNORED_DIRS = new Set([
53
55
  'node_modules',
56
+ 'bower_components',
57
+ 'jspm_packages',
54
58
  '.git',
55
- 'dist',
56
- 'build',
57
- 'out',
58
- '.next',
59
- 'coverage',
59
+ '.hg',
60
+ '.sl',
60
61
  ]);
61
62
  export {IGNORED_DIRS};
62
63
 
64
+ /** @param {string} root @param {string} fullPath @param {string} name */
65
+ export function isIgnoredDirectory(root, fullPath, name) {
66
+ if (IGNORED_DIRS.has(name)) return true;
67
+ const parts = path.relative(root, fullPath).split(path.sep);
68
+ return (
69
+ parts[0] === '.yarn' &&
70
+ ['__virtual__', 'cache', 'sdks', 'unplugged'].includes(parts[1])
71
+ );
72
+ }
73
+
63
74
  /**
64
75
  * Recursively find all source files in a directory.
65
76
  * @param {string} dir
@@ -81,8 +92,17 @@ function findSourceFiles(dir) {
81
92
  '.less',
82
93
  ]);
83
94
 
84
- /** @param {string} currentDir */
85
- function walk(currentDir) {
95
+ /** @param {string} currentDir @param {Set<string>} [ancestors] */
96
+ function walk(currentDir, ancestors = new Set()) {
97
+ let realDirectory;
98
+ try {
99
+ realDirectory = fs.realpathSync(currentDir);
100
+ } catch {
101
+ return;
102
+ }
103
+ if (ancestors.has(realDirectory)) return;
104
+ const nextAncestors = new Set(ancestors);
105
+ nextAncestors.add(realDirectory);
86
106
  let entries;
87
107
  try {
88
108
  entries = fs.readdirSync(currentDir, {withFileTypes: true});
@@ -91,16 +111,27 @@ function findSourceFiles(dir) {
91
111
  }
92
112
  for (const entry of entries) {
93
113
  const fullPath = path.join(currentDir, entry.name);
94
- // Never follow symlinks: readFileSync/writeFileSync would traverse a
95
- // symlinked file and rewrite its target OUTSIDE the scan tree (e.g. into
96
- // node_modules or anywhere on disk). A codemod must only edit real files
97
- // it reaches directly under the scanned path.
98
- if (entry.isSymbolicLink()) continue;
114
+ // Symlinked files and descendants remain read-only candidates so a
115
+ // required change can be reported. Real-path ancestors prevent cycles.
116
+ if (entry.isSymbolicLink()) {
117
+ try {
118
+ const target = fs.statSync(fullPath);
119
+ if (target.isFile() && extensions.has(path.extname(entry.name))) {
120
+ results.push(fullPath);
121
+ } else if (target.isDirectory()) {
122
+ walk(fullPath, nextAncestors);
123
+ }
124
+ } catch {
125
+ // Broken links have no source bytes to evaluate.
126
+ }
127
+ continue;
128
+ }
99
129
  if (entry.isDirectory()) {
100
- // Skip dependency, VCS, and generated-output dirs — codemods rewrite
101
- // source, not build artifacts.
102
- if (IGNORED_DIRS.has(entry.name)) continue;
103
- walk(fullPath);
130
+ // Only hard boundaries are skipped by name. Generated and vendored
131
+ // content is discovered, transformed in memory, and classified by the
132
+ // shared protection resolver so required changes can be reported.
133
+ if (isIgnoredDirectory(dir, fullPath, entry.name)) continue;
134
+ walk(fullPath, nextAncestors);
104
135
  } else if (extensions.has(path.extname(entry.name))) {
105
136
  results.push(fullPath);
106
137
  }
@@ -194,6 +225,187 @@ function toUnifiedEntry(transformEntry, version) {
194
225
  };
195
226
  }
196
227
 
228
+ /**
229
+ * What a core PROJECT codemod plans: whole files to write and delete under the
230
+ * project root, or the problems that stop it. Paths in `writes` and `deletes`
231
+ * are absolute; `problems` name package-relative files.
232
+ *
233
+ * @typedef {object} ProjectCodemodPlan
234
+ * @property {Array<{path: string, contents: string}>} writes
235
+ * @property {string[]} deletes
236
+ * @property {Array<{file: string, message: string}>} problems
237
+ */
238
+
239
+ /**
240
+ * Run one core PROJECT codemod (`meta.codemodType === 'project'`): instead of
241
+ * rewriting the files it is handed, it reads the project and plans whole-file
242
+ * writes and deletions, which this applies (or previews) as one unit. A plan
243
+ * that names a problem changes nothing. Core-only: integration codemods keep
244
+ * the file contract.
245
+ *
246
+ * @param {{name: string, transform: unknown}} transformEntry
247
+ * @param {{apply: boolean, root: string, protection: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, log: {success: (m: string) => void, warn: (m: string) => void, error: (m: string) => void}}} options
248
+ * @returns {Promise<import('../../authoring/codemod/type').CodemodRunResult & {stagedOverrides?: Map<string, string|null>}>}
249
+ */
250
+ async function runProjectCodemod(
251
+ {name, transform},
252
+ {apply, root, protection, log},
253
+ ) {
254
+ /** @param {string} file */
255
+ const rel = file => path.relative(root, file).split(path.sep).join('/');
256
+ /** @type {ProjectCodemodPlan} */
257
+ let plan;
258
+ try {
259
+ plan = await /** @type {(root: string) => Promise<ProjectCodemodPlan>} */ (
260
+ transform
261
+ )(root);
262
+ } catch (err) {
263
+ const message = err instanceof Error ? err.message : String(err);
264
+ log.error(` ✗ ${message}`);
265
+ return {
266
+ filesChanged: 0,
267
+ changedFiles: [],
268
+ writtenFiles: [],
269
+ protectedFiles: [],
270
+ errors: [{file: '.', codemod: name, error: message}],
271
+ };
272
+ }
273
+ if (plan.problems.length > 0) {
274
+ for (const {file, message} of plan.problems) {
275
+ log.error(` ✗ ${file} — ${message}`);
276
+ }
277
+ return {
278
+ filesChanged: 0,
279
+ changedFiles: [],
280
+ writtenFiles: [],
281
+ protectedFiles: [],
282
+ errors: plan.problems.map(({file, message}) => ({
283
+ file,
284
+ codemod: name,
285
+ error: message,
286
+ })),
287
+ };
288
+ }
289
+
290
+ const candidates = [
291
+ ...plan.writes.map(write => ({
292
+ file: write.path,
293
+ existing: fs.existsSync(write.path),
294
+ })),
295
+ ...plan.deletes.map(file => ({file, existing: true})),
296
+ ];
297
+ /** @param {{file: string, existing: boolean}} candidate */
298
+ const currentDeclarations = candidate =>
299
+ protection
300
+ .classify(candidate.file)
301
+ .filter(
302
+ item =>
303
+ candidate.existing ||
304
+ ['outside-root', 'dependency', 'vcs', 'symlink'].includes(
305
+ item.reason,
306
+ ),
307
+ );
308
+ const currentlyBlocked = new Set(
309
+ candidates
310
+ .filter(candidate => currentDeclarations(candidate).length > 0)
311
+ .map(candidate => candidate.file),
312
+ );
313
+ /** @type {Map<string, string|null>} */
314
+ const stagedOverrides = new Map();
315
+ for (const write of plan.writes) {
316
+ if (!currentlyBlocked.has(write.path)) {
317
+ stagedOverrides.set(write.path, write.contents);
318
+ }
319
+ }
320
+ for (const file of plan.deletes) {
321
+ if (!currentlyBlocked.has(file)) stagedOverrides.set(file, null);
322
+ }
323
+ // A plan cannot introduce a protection declaration and then overwrite the
324
+ // file it protects in the same transaction. Only declarations whose own
325
+ // writes are allowed participate in the staged view.
326
+ const stagedProtection = createFileProtectionResolver(root, {
327
+ overrides: stagedOverrides,
328
+ });
329
+ /** @type {import('../../authoring/codemod/type').CodemodRunResult['protectedFiles']} */
330
+ const protectedFiles = [];
331
+ const blocked = new Set();
332
+ for (const candidate of candidates) {
333
+ const file = candidate.file;
334
+ const declarations = [
335
+ ...currentDeclarations(candidate),
336
+ ...stagedProtection.classify(file),
337
+ ]
338
+ .filter(
339
+ item =>
340
+ candidate.existing ||
341
+ ['outside-root', 'dependency', 'vcs', 'symlink'].includes(
342
+ item.reason,
343
+ ),
344
+ )
345
+ .filter(
346
+ (item, index, all) =>
347
+ all.findIndex(
348
+ candidate =>
349
+ candidate.file === item.file &&
350
+ candidate.reason === item.reason &&
351
+ candidate.declaration === item.declaration,
352
+ ) === index,
353
+ );
354
+ if (declarations.length === 0) continue;
355
+ blocked.add(file);
356
+ const rows = declarations.map(item => ({
357
+ file: item.file,
358
+ codemod: name,
359
+ reason: item.reason,
360
+ declaration: item.declaration,
361
+ generated: item.reason === 'generated',
362
+ ...(item.command ? {command: item.command} : {}),
363
+ }));
364
+ protectedFiles.push(...rows);
365
+ log.warn(
366
+ ` ! ${rel(file)} — protected by ${rows.map(item => item.declaration).join('; ')}`,
367
+ );
368
+ }
369
+
370
+ const writes = plan.writes.filter(write => !blocked.has(write.path));
371
+ const deletes = plan.deletes.filter(file => !blocked.has(file));
372
+ const changedFiles = [...writes.map(write => write.path), ...deletes];
373
+ /** @type {string[]} */
374
+ const writtenFiles = [];
375
+ for (const write of writes) {
376
+ if (apply) {
377
+ fs.mkdirSync(path.dirname(write.path), {recursive: true});
378
+ fs.writeFileSync(write.path, write.contents, 'utf-8');
379
+ writtenFiles.push(write.path);
380
+ log.success(` ✓ ${rel(write.path)}`);
381
+ } else {
382
+ log.warn(` ~ ${rel(write.path)} (would write)`);
383
+ }
384
+ }
385
+ // Deletions go last, so a failed write leaves a state a rerun completes.
386
+ for (const file of deletes) {
387
+ if (apply) {
388
+ fs.rmSync(file);
389
+ writtenFiles.push(file);
390
+ log.success(` ✓ ${rel(file)} (removed)`);
391
+ } else {
392
+ log.warn(` ~ ${rel(file)} (would remove)`);
393
+ }
394
+ }
395
+ /** @type {Map<string, string|null>} */
396
+ const appliedOverrides = new Map();
397
+ for (const write of writes) appliedOverrides.set(write.path, write.contents);
398
+ for (const file of deletes) appliedOverrides.set(file, null);
399
+ return {
400
+ filesChanged: changedFiles.length,
401
+ changedFiles,
402
+ writtenFiles,
403
+ protectedFiles,
404
+ stagedOverrides: appliedOverrides,
405
+ errors: [],
406
+ };
407
+ }
408
+
197
409
  /**
198
410
  * Run codemods against source files.
199
411
  *
@@ -204,11 +416,21 @@ function toUnifiedEntry(transformEntry, version) {
204
416
  * @param {string|undefined} options.codemod - Run only this specific transform
205
417
  * @param {Set<string>} [options.skipCodemods] - Transform names to exclude
206
418
  * @param {boolean} [options.silent] - Suppress all human-facing output (for --json)
207
- * @returns {Promise<{totalFilesChanged: number, totalTransformsApplied: number, totalValidationBlocked: number, writtenFiles: string[], errors: Array<{file: string, codemod: string, error: string}>, skippedOptional: Array<{name: string, meta: {title: string, description?: string, fileExtensions?: string[], codemodType?: string}, version: string}>} | {ok: false, reason: string, resolvedPath: string}>}
419
+ * @param {string} [options.root] - Project root a project codemod reads (default: the process cwd)
420
+ * @param {{root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}} [options.protection] - Preloaded protection resolver
421
+ * @returns {Promise<{totalFilesChanged: number, totalTransformsApplied: number, totalValidationBlocked: number, changedFiles: string[], writtenFiles: string[], stagedContents: Map<string, string>, protectedFiles: import('../../authoring/codemod/type').CodemodRunResult['protectedFiles'], errors: Array<{file: string, codemod: string, error: string}>, skippedOptional: Array<{name: string, meta: {title: string, description?: string, fileExtensions?: string[], codemodType?: string}, version: string}>} | {ok: false, reason: string, resolvedPath: string}>}
208
422
  */
209
423
  export async function runCodemods(
210
424
  versionManifests,
211
- {apply, path: srcPath, codemod, skipCodemods, silent = false},
425
+ {
426
+ apply,
427
+ path: srcPath,
428
+ codemod,
429
+ skipCodemods,
430
+ silent = false,
431
+ root = process.cwd(),
432
+ protection: providedProtection,
433
+ },
212
434
  ) {
213
435
  // No-op stub object so silent mode skips log output entirely without
214
436
  // littering the body with `if (!silent)` guards.
@@ -220,12 +442,22 @@ export async function runCodemods(
220
442
  };
221
443
 
222
444
  const resolvedPath = path.resolve(srcPath);
223
-
224
- // Config codemods target the consumer's astryx.config.* and never read
225
- // source files, so a missing --path should not block them. Only hard-fail
226
- // on a missing source path when there is at least one CODE codemod to run.
445
+ // Eager construction is the fail-closed barrier: every working-tree
446
+ // declaration is read and parsed before the first codemod can write.
447
+ let protection =
448
+ providedProtection ?? createFileProtectionResolver(path.resolve(root));
449
+ /** @type {Map<string, string|null>} */
450
+ const protectionOverrides = new Map();
451
+ let protectionWriteCount = 0;
452
+
453
+ // Config and project codemods never read the files under --path, so a
454
+ // missing --path should not block them. Only hard-fail on a missing source
455
+ // path when there is at least one CODE codemod to run.
227
456
  const hasCodeCodemod = versionManifests.some(({transforms}) =>
228
- transforms.some(t => t.meta?.codemodType !== 'config'),
457
+ transforms.some(
458
+ t =>
459
+ t.meta?.codemodType !== 'config' && t.meta?.codemodType !== 'project',
460
+ ),
229
461
  );
230
462
  const sourcePathExists = fs.existsSync(resolvedPath);
231
463
 
@@ -259,8 +491,14 @@ export async function runCodemods(
259
491
  let totalValidationBlocked = 0;
260
492
  /** @type {Array<{file: string, codemod: string, error: string}>} */
261
493
  const errors = [];
494
+ /** In-memory pipeline state keeps ordered dry-runs equivalent to apply. */
495
+ const virtualContents = new Map();
496
+ /** @type {string[]} */
497
+ const changedFiles = [];
262
498
  /** @type {string[]} */
263
499
  const writtenFiles = [];
500
+ /** @type {import('../../authoring/codemod/type').CodemodRunResult['protectedFiles']} */
501
+ const protectedFiles = [];
264
502
  /** @type {Array<{name: string, meta: {title: string, description?: string, fileExtensions?: string[], codemodType?: string}, version: string}>} */
265
503
  const skippedOptional = [];
266
504
  for (const {version, transforms} of versionManifests) {
@@ -272,10 +510,7 @@ export async function runCodemods(
272
510
  // Exclude explicitly skipped codemods (by transform name).
273
511
  if (skipCodemods?.has(transformEntry.name)) continue;
274
512
 
275
- const {name, transform, meta, optional} = transformEntry;
276
- const transformExtensions = new Set(
277
- meta.fileExtensions ?? ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs'],
278
- );
513
+ const {name, meta, optional} = transformEntry;
279
514
 
280
515
  // Skip optional codemods unless explicitly requested via --codemod
281
516
  if (optional && !codemod) {
@@ -283,6 +518,13 @@ export async function runCodemods(
283
518
  continue;
284
519
  }
285
520
 
521
+ if (apply && writtenFiles.length !== protectionWriteCount) {
522
+ protection = createFileProtectionResolver(path.resolve(root), {
523
+ overrides: protectionOverrides,
524
+ });
525
+ protectionWriteCount = writtenFiles.length;
526
+ }
527
+
286
528
  log.info(` ${meta.title}`);
287
529
 
288
530
  // Config codemods are routed through the SAME shared runner that
@@ -290,90 +532,78 @@ export async function runCodemods(
290
532
  // `(file, api)` contract and targets the consumer's astryx.config.*.
291
533
  // A core entry signals "config" via `meta.codemodType === 'config'`
292
534
  // (see toUnifiedEntry).
293
- if (meta?.codemodType === 'config') {
294
- const result = runConfigCodemod(toUnifiedEntry(transformEntry, version), {
535
+ if (meta?.codemodType === 'project') {
536
+ const result = await runProjectCodemod(transformEntry, {
295
537
  apply,
538
+ root,
539
+ protection,
296
540
  log,
297
- jscodeshift,
298
541
  });
299
- if (result.errors.length > 0) {
300
- errors.push(...result.errors);
301
- } else if (result.filesChanged > 0) {
542
+ errors.push(...result.errors);
543
+ protectedFiles.push(...result.protectedFiles);
544
+ changedFiles.push(...result.changedFiles);
545
+ writtenFiles.push(...result.writtenFiles);
546
+ if (result.stagedOverrides) {
547
+ for (const [file, contents] of result.stagedOverrides) {
548
+ protectionOverrides.set(file, contents);
549
+ if (contents !== null) virtualContents.set(file, contents);
550
+ }
551
+ protection = createFileProtectionResolver(path.resolve(root), {
552
+ overrides: protectionOverrides,
553
+ });
554
+ protectionWriteCount = writtenFiles.length;
555
+ }
556
+ if (result.filesChanged > 0) {
302
557
  totalFilesChanged += result.filesChanged;
303
- totalTransformsApplied += result.filesChanged;
304
- writtenFiles.push(...result.writtenFiles);
558
+ totalTransformsApplied += 1;
305
559
  }
306
560
  continue;
307
561
  }
308
562
 
309
- let filesChanged = 0;
310
-
311
- for (const filePath of files) {
312
- const relativePath = path.relative(process.cwd(), filePath);
313
-
314
- try {
315
- const ext = path.extname(filePath);
316
- if (!transformExtensions.has(ext)) {
317
- continue;
318
- }
319
-
320
- const source = fs.readFileSync(filePath, 'utf-8');
321
- // Configure parser based on file extension
322
- const parser = ext === '.tsx' || ext === '.ts' ? 'tsx' : 'babel';
323
- const j = jscodeshift.withParser(parser);
324
- const api = {
325
- jscodeshift: j,
326
- stats: () => {},
327
- report: () => {},
328
- };
329
- const file = {source, path: filePath};
330
-
331
- let result = transform(file, api);
332
-
333
- if (result != null && result !== source) {
334
- // Fix known jscodeshift output corruption before validation
335
- result = fixDirectiveCorruption(result);
336
-
337
- // Validate output before writing
338
- const validation = validateOutput(result, source, j, {
339
- parse: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs'].includes(
340
- ext,
341
- ),
342
- });
343
- if (!validation.valid) {
344
- totalValidationBlocked++;
345
- log.error(` ✗ ${relativePath} — ${validation.reason}`);
346
- errors.push({
347
- file: relativePath,
348
- codemod: name,
349
- error: validation.reason,
350
- });
351
- continue;
352
- }
353
-
354
- filesChanged++;
355
- totalFilesChanged++;
356
- totalTransformsApplied++;
357
-
358
- if (apply) {
359
- fs.writeFileSync(filePath, result, 'utf-8');
360
- writtenFiles.push(filePath);
361
- log.success(` ✓ ${relativePath}`);
362
- } else {
363
- log.warn(` ~ ${relativePath} (would change)`);
364
- }
365
- }
366
- } catch (err) {
367
- const message = /** @type {any} */ (err).message;
368
- log.error(` ✗ ${relativePath} — ${message}`);
369
- errors.push({file: relativePath, codemod: name, error: message});
563
+ if (meta?.codemodType === 'config') {
564
+ const result = runConfigCodemod(
565
+ toUnifiedEntry(transformEntry, version),
566
+ {
567
+ apply,
568
+ log,
569
+ jscodeshift,
570
+ root,
571
+ protection,
572
+ contents: virtualContents,
573
+ },
574
+ );
575
+ errors.push(...result.errors);
576
+ protectedFiles.push(...result.protectedFiles);
577
+ changedFiles.push(...result.changedFiles);
578
+ writtenFiles.push(...result.writtenFiles);
579
+ if (result.filesChanged > 0) {
580
+ totalFilesChanged += result.filesChanged;
581
+ totalTransformsApplied += result.filesChanged;
370
582
  }
583
+ continue;
371
584
  }
372
585
 
373
- if (filesChanged > 0) {
586
+ const result = runCodeCodemod(
587
+ toUnifiedEntry(transformEntry, version),
588
+ files,
589
+ {apply, log, jscodeshift, root, protection, contents: virtualContents},
590
+ );
591
+ errors.push(...result.errors);
592
+ protectedFiles.push(...result.protectedFiles);
593
+ changedFiles.push(...result.changedFiles);
594
+ writtenFiles.push(...result.writtenFiles);
595
+ totalFilesChanged += result.filesChanged;
596
+ totalTransformsApplied += result.filesChanged;
597
+ totalValidationBlocked += result.errors.filter(
598
+ error =>
599
+ error.error.startsWith('transform produced unparseable output') ||
600
+ error.error.startsWith('detected corruption:'),
601
+ ).length;
602
+
603
+ if (result.filesChanged > 0) {
374
604
  const verb = apply ? 'Updated' : 'Would update';
375
605
  log.info(
376
- ` ${verb} ${filesChanged} file${filesChanged === 1 ? '' : 's'}`,
606
+ ` ${verb} ${result.filesChanged} file${result.filesChanged === 1 ? '' : 's'}`,
377
607
  );
378
608
  }
379
609
  }
@@ -400,8 +630,23 @@ export async function runCodemods(
400
630
  );
401
631
  }
402
632
 
403
- if (totalFilesChanged === 0 && errors.length === 0) {
633
+ if (protectedFiles.length > 0) {
634
+ const files = [...new Set(protectedFiles.map(item => item.file))];
635
+ log.warn(
636
+ `${files.length} protected file${files.length === 1 ? '' : 's'} require${files.length === 1 ? 's' : ''} regeneration or manual review.`,
637
+ );
638
+ }
639
+
640
+ if (
641
+ totalFilesChanged === 0 &&
642
+ errors.length === 0 &&
643
+ protectedFiles.length === 0
644
+ ) {
404
645
  log.success('No changes needed — your code is already up to date!');
646
+ } else if (apply && protectedFiles.length > 0) {
647
+ log.warn(
648
+ `Applied ${totalTransformsApplied} owned change${totalTransformsApplied === 1 ? '' : 's'} across ${totalFilesChanged} file${totalFilesChanged === 1 ? '' : 's'}; protected changes remain.`,
649
+ );
405
650
  } else if (apply) {
406
651
  log.success(
407
652
  `Done! Applied ${totalTransformsApplied} change${totalTransformsApplied === 1 ? '' : 's'} across ${totalFilesChanged} file${totalFilesChanged === 1 ? '' : 's'}.`,
@@ -438,7 +683,10 @@ export async function runCodemods(
438
683
  totalFilesChanged,
439
684
  totalTransformsApplied,
440
685
  totalValidationBlocked,
686
+ changedFiles,
441
687
  writtenFiles,
688
+ protectedFiles,
689
+ stagedContents: virtualContents,
442
690
  errors,
443
691
  skippedOptional,
444
692
  };
@@ -11,6 +11,8 @@
11
11
  * `p.log.step(...)`, `p.log.success(...)`, etc. All output routes through
12
12
  * `humanLog`, the CLI's stdout-discipline primitive, which is a no-op in
13
13
  * `--json` mode — so these human logs can never corrupt a JSON envelope.
14
+ * Every line is plain ASCII: level prefixes are ASCII and typographic glyphs
15
+ * in a message are transliterated before it is printed.
14
16
  *
15
17
  * The side-effecting API commands (upgrade/init/themeBuild) do NOT use this;
16
18
  * they write through the shared `logger` (api/logger.mjs).
@@ -18,8 +20,30 @@
18
20
 
19
21
  import {humanLog} from '../../foundation/response/json.mjs';
20
22
 
21
- /** @param {unknown} msg */
22
- const toStr = (msg) => (msg === undefined || msg === null ? '' : String(msg));
23
+ /** @type {Array<[RegExp, string]>} */
24
+ const ASCII_GLYPHS = [
25
+ [/[\u2014\u2013]/g, '-'],
26
+ [/\u2192/g, '->'],
27
+ [/\u2713/g, '[ok]'],
28
+ [/\u2717/g, '!!'],
29
+ [/\u26a0\ufe0f?/g, '!'],
30
+ [/\u2022/g, '-'],
31
+ [/\u2026/g, '...'],
32
+ [/[\u2018\u2019]/g, "'"],
33
+ [/[\u201c\u201d]/g, '"'],
34
+ [/\u00a0/g, ' '],
35
+ ];
36
+
37
+ /**
38
+ * A message as plain ASCII: typographic glyphs become their ASCII spelling.
39
+ * @param {unknown} msg
40
+ * @returns {string}
41
+ */
42
+ export function toAscii(msg) {
43
+ let text = msg === undefined || msg === null ? '' : String(msg);
44
+ for (const [glyph, ascii] of ASCII_GLYPHS) text = text.replace(glyph, ascii);
45
+ return text;
46
+ }
23
47
 
24
48
  /**
25
49
  * Human-facing log surface (the small `log` API codemods use). All lines go to
@@ -28,15 +52,15 @@ const toStr = (msg) => (msg === undefined || msg === null ? '' : String(msg));
28
52
  */
29
53
  export const log = {
30
54
  /** @param {unknown} msg */
31
- message: (msg) => humanLog(toStr(msg)),
55
+ message: (msg) => humanLog(toAscii(msg)),
32
56
  /** @param {unknown} msg */
33
- info: (msg) => humanLog(toStr(msg)),
57
+ info: (msg) => humanLog(toAscii(msg)),
34
58
  /** @param {unknown} msg */
35
- step: (msg) => humanLog(toStr(msg)),
59
+ step: (msg) => humanLog(toAscii(msg)),
36
60
  /** @param {unknown} msg */
37
- success: (msg) => humanLog(`✓ ${toStr(msg)}`),
61
+ success: (msg) => humanLog(`[ok] ${toAscii(msg)}`),
38
62
  /** @param {unknown} msg */
39
- warn: (msg) => humanLog(`⚠ ${toStr(msg)}`),
63
+ warn: (msg) => humanLog(`! ${toAscii(msg)}`),
40
64
  /** @param {unknown} msg */
41
- error: (msg) => humanLog(`✗ ${toStr(msg)}`),
65
+ error: (msg) => humanLog(`!! ${toAscii(msg)}`),
42
66
  };
@@ -51,7 +51,7 @@ describe('sanity: helpers are live in human mode', () => {
51
51
  setJsonMode(false);
52
52
  p.log.success('done');
53
53
  expect(logSpy).toHaveBeenCalledTimes(1);
54
- expect(logSpy).toHaveBeenCalledWith('✓ done');
54
+ expect(logSpy).toHaveBeenCalledWith('[ok] done');
55
55
  });
56
56
 
57
57
  it('every log helper emits exactly one stdout line in human mode', () => {
@@ -60,3 +60,21 @@ describe('sanity: helpers are live in human mode', () => {
60
60
  expect(logSpy).toHaveBeenCalledTimes(6);
61
61
  });
62
62
  });
63
+
64
+ describe('ASCII output', () => {
65
+ it('prints ASCII level prefixes and transliterates glyphs in messages', () => {
66
+ setJsonMode(false);
67
+ p.log.success('src/a.tsx');
68
+ p.log.warn('stale \u2014 refresh');
69
+ p.log.error(' \u2717 src/b.tsx \u2014 rename \u2192 failed');
70
+ p.log.info(' \u2022 name \u2014 title\u2026');
71
+ const lines = logSpy.mock.calls.map(call => call.join(' '));
72
+ expect(lines).toEqual([
73
+ '[ok] src/a.tsx',
74
+ '! stale - refresh',
75
+ '!! !! src/b.tsx - rename -> failed',
76
+ ' - name - title...',
77
+ ]);
78
+ for (const line of lines) expect(line).not.toMatch(/[\u0080-\uFFFF]/);
79
+ });
80
+ });