@bastani/atomic 0.9.5-alpha.7 → 0.9.5-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/CHANGELOG.md +33 -2
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/CHANGELOG.md +17 -0
  4. package/dist/builtin/intercom/README.md +27 -21
  5. package/dist/builtin/intercom/broker/broker.ts +3 -6
  6. package/dist/builtin/intercom/broker/paths.ts +42 -5
  7. package/dist/builtin/intercom/broker/spawn.ts +78 -10
  8. package/dist/builtin/intercom/config.ts +6 -3
  9. package/dist/builtin/intercom/contact-supervisor-tool.ts +6 -4
  10. package/dist/builtin/intercom/index-heavy.ts +1 -1
  11. package/dist/builtin/intercom/index.ts +162 -130
  12. package/dist/builtin/intercom/intercom-tool.ts +9 -3
  13. package/dist/builtin/intercom/package.json +10 -1
  14. package/dist/builtin/intercom/result-renderers.ts +1 -1
  15. package/dist/builtin/intercom/ui/compose.ts +2 -2
  16. package/dist/builtin/intercom/ui/inline-message.ts +2 -2
  17. package/dist/builtin/intercom/ui/session-list.ts +2 -2
  18. package/dist/builtin/mcp/CHANGELOG.md +11 -0
  19. package/dist/builtin/mcp/README.md +6 -6
  20. package/dist/builtin/mcp/direct-tools.ts +41 -23
  21. package/dist/builtin/mcp/index.ts +31 -4
  22. package/dist/builtin/mcp/init.ts +4 -43
  23. package/dist/builtin/mcp/metadata-hydration.ts +37 -0
  24. package/dist/builtin/mcp/package.json +1 -1
  25. package/dist/builtin/mcp/proxy-info-modes.ts +67 -14
  26. package/dist/builtin/mcp/startup-warmup.ts +98 -0
  27. package/dist/builtin/mcp/tool-result-renderer.ts +3 -3
  28. package/dist/builtin/subagents/CHANGELOG.md +18 -0
  29. package/dist/builtin/subagents/package.json +1 -1
  30. package/dist/builtin/subagents/src/extension/index.ts +14 -25
  31. package/dist/builtin/subagents/src/extension/startup-maintenance.ts +89 -0
  32. package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +23 -18
  33. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +20 -10
  34. package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +13 -3
  35. package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +10 -2
  36. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +28 -1
  37. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +15 -48
  38. package/dist/builtin/subagents/src/tui/render-layout.ts +9 -0
  39. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +27 -25
  40. package/dist/builtin/subagents/src/tui/render-widget.ts +72 -66
  41. package/dist/builtin/subagents/src/tui/render.ts +1 -1
  42. package/dist/builtin/web-access/package.json +1 -1
  43. package/dist/builtin/workflows/CHANGELOG.md +29 -0
  44. package/dist/builtin/workflows/README.md +4 -2
  45. package/dist/builtin/workflows/builtin/goal-artifacts.ts +3 -1
  46. package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -2
  47. package/dist/builtin/workflows/builtin/goal-reducer.ts +21 -0
  48. package/dist/builtin/workflows/builtin/goal-review.ts +60 -6
  49. package/dist/builtin/workflows/builtin/goal-runner.ts +71 -22
  50. package/dist/builtin/workflows/builtin/goal-types.ts +7 -1
  51. package/dist/builtin/workflows/builtin/ralph-core.ts +52 -4
  52. package/dist/builtin/workflows/builtin/ralph-review-gate.ts +10 -3
  53. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +23 -2
  54. package/dist/builtin/workflows/builtin/ralph-runner.ts +38 -16
  55. package/dist/builtin/workflows/builtin/review-convergence.ts +111 -0
  56. package/dist/builtin/workflows/package.json +1 -1
  57. package/dist/builtin/workflows/skills/create-spec/SKILL.md +2 -0
  58. package/dist/builtin/workflows/skills/impeccable/SKILL.md +16 -13
  59. package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +1 -0
  60. package/dist/builtin/workflows/skills/impeccable/reference/adapt.native.md +58 -0
  61. package/dist/builtin/workflows/skills/impeccable/reference/android.md +40 -0
  62. package/dist/builtin/workflows/skills/impeccable/reference/animate.md +2 -0
  63. package/dist/builtin/workflows/skills/impeccable/reference/audit.md +2 -0
  64. package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +139 -0
  65. package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +69 -62
  66. package/dist/builtin/workflows/skills/impeccable/reference/critique.md +21 -7
  67. package/dist/builtin/workflows/skills/impeccable/reference/document.md +1 -1
  68. package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +92 -0
  69. package/dist/builtin/workflows/skills/impeccable/reference/init.md +29 -7
  70. package/dist/builtin/workflows/skills/impeccable/reference/ios.md +45 -0
  71. package/dist/builtin/workflows/skills/impeccable/reference/layout.md +25 -1
  72. package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +23 -1
  73. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +2 -1
  74. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +75 -14
  75. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +1 -1
  76. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +31 -0
  77. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +64 -0
  78. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +118 -11
  79. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +16 -11
  80. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +66 -0
  81. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +42 -10
  82. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/fonts.mjs +30 -0
  83. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +4 -6
  84. package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +660 -0
  85. package/dist/builtin/workflows/skills/impeccable/scripts/hook-before-edit.mjs +516 -0
  86. package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +1764 -0
  87. package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +61 -0
  88. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +4 -2
  89. package/dist/builtin/workflows/skills/impeccable/scripts/lib/is-generated.mjs +2 -2
  90. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +13 -16
  91. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +17 -11
  92. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +104 -37
  93. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +9 -0
  94. package/dist/builtin/workflows/src/durable/backend.ts +12 -0
  95. package/dist/builtin/workflows/src/durable/child-primitive.ts +12 -8
  96. package/dist/builtin/workflows/src/durable/dbos-backend.ts +63 -2
  97. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +28 -0
  98. package/dist/builtin/workflows/src/durable/resume-catalog.ts +12 -0
  99. package/dist/builtin/workflows/src/durable/resume-runtime.ts +5 -2
  100. package/dist/builtin/workflows/src/durable/stage-primitive.ts +165 -27
  101. package/dist/builtin/workflows/src/durable/types.ts +30 -1
  102. package/dist/builtin/workflows/src/engine/primitives/task.ts +1 -0
  103. package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +1 -2
  104. package/dist/builtin/workflows/src/engine/run-returned-status.ts +50 -16
  105. package/dist/builtin/workflows/src/engine/run.ts +32 -32
  106. package/dist/builtin/workflows/src/engine/runtime.ts +5 -0
  107. package/dist/builtin/workflows/src/extension/extension-factory.ts +3 -0
  108. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +9 -2
  109. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +137 -27
  110. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +22 -6
  111. package/dist/builtin/workflows/src/extension/public-types.ts +1 -1
  112. package/dist/builtin/workflows/src/extension/workflow-command-completions.ts +13 -1
  113. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +19 -4
  114. package/dist/builtin/workflows/src/extension/workflow-command-surfaces.ts +9 -0
  115. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +2 -0
  116. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +48 -23
  117. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +20 -8
  118. package/dist/builtin/workflows/src/extension/workflow-tool.ts +24 -8
  119. package/dist/builtin/workflows/src/runs/background/status.ts +4 -3
  120. package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +13 -3
  121. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +29 -8
  122. package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +2 -1
  123. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +4 -3
  124. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +18 -19
  125. package/dist/builtin/workflows/src/runs/foreground/stage-runner-unresolved-overflow.ts +38 -0
  126. package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +155 -20
  127. package/dist/builtin/workflows/src/runs/shared/worktree-types.ts +6 -0
  128. package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -1
  129. package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +18 -1
  130. package/dist/builtin/workflows/src/shared/persistence-restore.ts +23 -5
  131. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
  132. package/dist/builtin/workflows/src/shared/returned-run-status.ts +116 -0
  133. package/dist/builtin/workflows/src/shared/workflow-failures-decisions.ts +2 -0
  134. package/dist/builtin/workflows/src/tui/graph-view-render-helpers.ts +21 -3
  135. package/dist/builtin/workflows/src/tui/graph-view-state.ts +3 -0
  136. package/dist/builtin/workflows/src/tui/graph-view-types.ts +3 -0
  137. package/dist/builtin/workflows/src/tui/status-list.ts +22 -15
  138. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +4 -4
  139. package/dist/bun/internal-intercom-broker.d.ts +6 -0
  140. package/dist/bun/internal-intercom-broker.d.ts.map +1 -0
  141. package/dist/bun/internal-intercom-broker.js +39 -0
  142. package/dist/bun/internal-intercom-broker.js.map +1 -0
  143. package/dist/bun/split-loader.d.ts.map +1 -1
  144. package/dist/bun/split-loader.js +21 -8
  145. package/dist/bun/split-loader.js.map +1 -1
  146. package/dist/core/agent-session-auto-compaction.d.ts +21 -2
  147. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  148. package/dist/core/agent-session-auto-compaction.js +163 -11
  149. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  150. package/dist/core/agent-session-events.d.ts.map +1 -1
  151. package/dist/core/agent-session-events.js +10 -1
  152. package/dist/core/agent-session-events.js.map +1 -1
  153. package/dist/core/agent-session-methods.d.ts +12 -3
  154. package/dist/core/agent-session-methods.d.ts.map +1 -1
  155. package/dist/core/agent-session-methods.js.map +1 -1
  156. package/dist/core/agent-session-models.d.ts +2 -2
  157. package/dist/core/agent-session-models.d.ts.map +1 -1
  158. package/dist/core/agent-session-models.js.map +1 -1
  159. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  160. package/dist/core/agent-session-prompt.js +1 -0
  161. package/dist/core/agent-session-prompt.js.map +1 -1
  162. package/dist/core/agent-session-retry.d.ts +2 -0
  163. package/dist/core/agent-session-retry.d.ts.map +1 -1
  164. package/dist/core/agent-session-retry.js +152 -4
  165. package/dist/core/agent-session-retry.js.map +1 -1
  166. package/dist/core/agent-session-services.d.ts +1 -0
  167. package/dist/core/agent-session-services.d.ts.map +1 -1
  168. package/dist/core/agent-session-services.js +1 -0
  169. package/dist/core/agent-session-services.js.map +1 -1
  170. package/dist/core/agent-session-types.d.ts +15 -1
  171. package/dist/core/agent-session-types.d.ts.map +1 -1
  172. package/dist/core/agent-session-types.js.map +1 -1
  173. package/dist/core/agent-session.d.ts +6 -0
  174. package/dist/core/agent-session.d.ts.map +1 -1
  175. package/dist/core/agent-session.js +6 -0
  176. package/dist/core/agent-session.js.map +1 -1
  177. package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
  178. package/dist/core/compaction/context-compaction-runner.js +36 -20
  179. package/dist/core/compaction/context-compaction-runner.js.map +1 -1
  180. package/dist/core/copilot-anthropic-sse-repair.d.ts +23 -0
  181. package/dist/core/copilot-anthropic-sse-repair.d.ts.map +1 -0
  182. package/dist/core/copilot-anthropic-sse-repair.js +340 -0
  183. package/dist/core/copilot-anthropic-sse-repair.js.map +1 -0
  184. package/dist/core/copilot-gemini-reasoning.d.ts +16 -8
  185. package/dist/core/copilot-gemini-reasoning.d.ts.map +1 -1
  186. package/dist/core/copilot-gemini-reasoning.js +27 -22
  187. package/dist/core/copilot-gemini-reasoning.js.map +1 -1
  188. package/dist/core/copilot-hosts.d.ts +12 -0
  189. package/dist/core/copilot-hosts.d.ts.map +1 -0
  190. package/dist/core/copilot-hosts.js +33 -0
  191. package/dist/core/copilot-hosts.js.map +1 -0
  192. package/dist/core/extensions/agent-events.d.ts +1 -1
  193. package/dist/core/extensions/agent-events.d.ts.map +1 -1
  194. package/dist/core/extensions/agent-events.js.map +1 -1
  195. package/dist/core/extensions/loader-virtual-modules.d.ts +8 -1
  196. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  197. package/dist/core/extensions/loader-virtual-modules.js +2 -1
  198. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  199. package/dist/core/footer-data-provider.d.ts +6 -0
  200. package/dist/core/footer-data-provider.d.ts.map +1 -1
  201. package/dist/core/footer-data-provider.js +72 -29
  202. package/dist/core/footer-data-provider.js.map +1 -1
  203. package/dist/core/http-dispatcher.d.ts.map +1 -1
  204. package/dist/core/http-dispatcher.js +5 -5
  205. package/dist/core/http-dispatcher.js.map +1 -1
  206. package/dist/core/openai-responses-payload-sanitizer.d.ts +1 -0
  207. package/dist/core/openai-responses-payload-sanitizer.d.ts.map +1 -1
  208. package/dist/core/openai-responses-payload-sanitizer.js +24 -9
  209. package/dist/core/openai-responses-payload-sanitizer.js.map +1 -1
  210. package/dist/core/sdk-types.d.ts +2 -0
  211. package/dist/core/sdk-types.d.ts.map +1 -1
  212. package/dist/core/sdk-types.js.map +1 -1
  213. package/dist/core/sdk.d.ts.map +1 -1
  214. package/dist/core/sdk.js +1 -0
  215. package/dist/core/sdk.js.map +1 -1
  216. package/dist/core/settings-manager-basic-accessors.d.ts +1 -0
  217. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  218. package/dist/core/settings-manager-basic-accessors.js +6 -0
  219. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  220. package/dist/core/settings-types.d.ts +1 -0
  221. package/dist/core/settings-types.d.ts.map +1 -1
  222. package/dist/core/settings-types.js.map +1 -1
  223. package/dist/core/slash-commands.d.ts +2 -0
  224. package/dist/core/slash-commands.d.ts.map +1 -1
  225. package/dist/core/slash-commands.js +141 -0
  226. package/dist/core/slash-commands.js.map +1 -1
  227. package/dist/index-extensions.d.ts +1 -1
  228. package/dist/index-extensions.d.ts.map +1 -1
  229. package/dist/index-extensions.js.map +1 -1
  230. package/dist/index.d.ts +1 -0
  231. package/dist/index.d.ts.map +1 -1
  232. package/dist/index.js +1 -0
  233. package/dist/index.js.map +1 -1
  234. package/dist/main-deferred-startup.d.ts +12 -0
  235. package/dist/main-deferred-startup.d.ts.map +1 -1
  236. package/dist/main-deferred-startup.js +23 -2
  237. package/dist/main-deferred-startup.js.map +1 -1
  238. package/dist/main-early-input.d.ts +41 -0
  239. package/dist/main-early-input.d.ts.map +1 -0
  240. package/dist/main-early-input.js +154 -0
  241. package/dist/main-early-input.js.map +1 -0
  242. package/dist/main.d.ts +0 -6
  243. package/dist/main.d.ts.map +1 -1
  244. package/dist/main.js +20 -14
  245. package/dist/main.js.map +1 -1
  246. package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
  247. package/dist/modes/interactive/components/chat-session-host-actions.js +0 -1
  248. package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
  249. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  250. package/dist/modes/interactive/components/chat-session-host-events.js +15 -0
  251. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  252. package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
  253. package/dist/modes/interactive/interactive-agent-events.js +25 -0
  254. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  255. package/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  256. package/dist/modes/interactive/interactive-autocomplete.js +21 -9
  257. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  258. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  259. package/dist/modes/interactive/interactive-deferred-startup.js +41 -28
  260. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  261. package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
  262. package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -0
  263. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  264. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  265. package/dist/modes/interactive/interactive-input-handling.js +318 -185
  266. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  267. package/dist/modes/interactive/interactive-mode-base.d.ts +12 -0
  268. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  269. package/dist/modes/interactive/interactive-mode-base.js +39 -0
  270. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  271. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
  272. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  273. package/dist/modes/interactive/interactive-mode-deps.js +1 -1
  274. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  275. package/dist/modes/interactive/interactive-mode-surface.d.ts +6 -2
  276. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  277. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  278. package/dist/modes/interactive/interactive-mode-types.d.ts +3 -0
  279. package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
  280. package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
  281. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  282. package/dist/modes/interactive/interactive-model-routing.js +3 -1
  283. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  284. package/dist/modes/interactive/interactive-process-lifecycle.d.ts.map +1 -1
  285. package/dist/modes/interactive/interactive-process-lifecycle.js +0 -3
  286. package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
  287. package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
  288. package/dist/modes/interactive/interactive-render-chat.js +36 -10
  289. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  290. package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
  291. package/dist/modes/interactive/interactive-session-routing.js +5 -1
  292. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  293. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  294. package/dist/modes/interactive/interactive-startup.js +26 -22
  295. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  296. package/dist/modes/interactive/theme/global-theme.d.ts.map +1 -1
  297. package/dist/modes/interactive/theme/global-theme.js +26 -2
  298. package/dist/modes/interactive/theme/global-theme.js.map +1 -1
  299. package/dist/utils/fs-watch.d.ts +24 -1
  300. package/dist/utils/fs-watch.d.ts.map +1 -1
  301. package/dist/utils/fs-watch.js +60 -5
  302. package/dist/utils/fs-watch.js.map +1 -1
  303. package/docs/compaction.md +8 -0
  304. package/docs/development.md +4 -0
  305. package/docs/extensions.md +5 -0
  306. package/docs/json.md +3 -1
  307. package/docs/providers.md +1 -0
  308. package/docs/quickstart.md +1 -1
  309. package/docs/rpc.md +3 -1
  310. package/docs/settings.md +27 -1
  311. package/docs/subagents.md +4 -0
  312. package/docs/terminal-setup.md +4 -0
  313. package/docs/usage.md +2 -2
  314. package/docs/windows.md +4 -0
  315. package/docs/workflows.md +21 -9
  316. package/npm-shrinkwrap.json +513 -23
  317. package/package.json +3 -2
@@ -0,0 +1,45 @@
1
+ # iOS platform
2
+
3
+ For native iOS / iPadOS apps: SwiftUI, UIKit, React Native, Expo, Flutter shipping to Apple hardware.
4
+
5
+ On native, register narrows. HIG conformance governs structure, navigation, and interaction whatever the register; brand expresses through the expressive layer the platform provides (tint, type, motion, content). Calm, Duolingo, and Spotify carry strong identity entirely inside HIG conventions.
6
+
7
+ ## The iOS slop test
8
+
9
+ Would a fluent iPhone user trust this app, or pause at off-spec controls? The tell is "ported from a website": reinvented navigation bars, custom back gestures, web-shaped buttons, hover-dependent affordances. Default to the platform's components; depart only for a reason the user would thank you for.
10
+
11
+ ## Layout & structure
12
+
13
+ - **Safe area.** Lay out inside the safe-area insets. No controls under the notch, Dynamic Island, home indicator, or rounded corners.
14
+ - **System navigation.** Tab bar for 2–5 top-level sections (sections, never actions), navigation stack for hierarchy, sheet for self-contained tasks. No custom global nav, no mixed metaphors.
15
+ - **Edge-swipe back stays alive.** The left-edge back gesture is muscle memory; never disable or overlay it.
16
+ - **Large titles** on top-level screens, collapsing to inline on scroll. Deep detail screens stay inline.
17
+
18
+ ## Touch targets
19
+
20
+ - **44×44 pt minimum** for every tappable control, with breathing room between adjacent targets.
21
+
22
+ ## Typography
23
+
24
+ - **Dynamic Type.** Use the system text styles (Large Title through Caption) so text follows the user's reading size. No hard-coded point sizes.
25
+ - **San Francisco carries the UI.** Body, labels, and controls stay on SF Pro / SF Compact; a brand face may appear in display moments.
26
+ - **11 pt floor**; Body is 17 pt.
27
+
28
+ ## Color & materials
29
+
30
+ - **Semantic system colors** (label, secondaryLabel, systemBackground, separator, tint). They adapt to Dark Mode and increased contrast automatically; raw hex breaks there.
31
+ - **Dark Mode is a first-class appearance.** Design and test both.
32
+ - **One tint color** drives interactive elements; decoration is not its job.
33
+ - **System materials** for blur and translucency behind bars and sheets; no hand-rolled glassmorphism.
34
+
35
+ ## Components & controls
36
+
37
+ - **Platform controls.** Switch, segmented control, stepper, system pickers, action sheets, alerts, context menus, swipe actions. Reinventing these for flavor is the most common native slop.
38
+ - **SF Symbols** for iconography: baseline-aligned, Dynamic Type-aware, weight and scale variants. Don't mix in a web icon set.
39
+ - **Deliberate modality.** Sheet for a focused dismissible sub-task, full-screen cover for immersion. Clear Cancel/Done; honor swipe-to-dismiss unless data loss requires a guard.
40
+ - **Grouped/inset lists** for settings-shaped content; no bespoke card stacks.
41
+
42
+ ## Motion
43
+
44
+ - **System transitions.** Push slides, sheets rise, dismiss reverses the entrance. Custom transitions that fight the navigation model disorient.
45
+ - **Honor Reduce Motion.** Crossfade instead of parallax and large slides.
@@ -8,11 +8,33 @@ Brand: asymmetric compositions, fluid spacing with `clamp()`, intentional grid-b
8
8
 
9
9
  Product: predictable grids, consistent densities, familiar navigation patterns. Responsive behavior is structural (collapse sidebar, responsive table), not fluid typography. Consistency IS an affordance.
10
10
 
11
+ Native (`ios` / `android` / `adaptive`): structure follows the Layout section of [ios.md](ios.md) / [android.md](android.md) (read it first if Setup hasn't already): platform navigation, insets, and touch targets, never the CSS tooling below.
12
+
13
+ ---
14
+
15
+ ## Two isolated assessments (required)
16
+
17
+ Spawn two parallel sub-agents whenever a sub-agent/Task tool is exposed: one for the layout assessment, one for the mechanical pre-scan. If the harness needs explicit user permission for sub-agents, stop and ask before proceeding. Isolation is the point: detector output anchors visual judgment toward what the scan can see, so neither sub-agent gets the other's output. Each assessment runs in its own sub-agent; running either one in this context when a sub-agent tool exists is not permitted, even when it is faster; the fallback below is only for sessions with no sub-agent tool. Give each a self-contained prompt (target files, register, documented spacing scale when present, and its instructions below); do not assume it can read this file.
18
+
19
+ **Sub-agent A (layout assessment)**: give it the full [Assess Current Layout](#assess-current-layout) checklist below, verbatim, in its prompt. It works through every item and returns per-item findings citing file, selector, or value.
20
+
21
+ **Sub-agent B (mechanical pre-scan)**: run the bundled detector scoped to layout:
22
+
23
+ ```bash
24
+ node .agents/skills/impeccable/scripts/detect.mjs --json --scope layout [target files or dirs]
25
+ ```
26
+
27
+ A missing `node` on PATH is not permission to skip: hunt for a runtime (`command -v node`, nvm or Homebrew paths, the harness's own bundled node) and run it by full path. If none exists, halt the scan and report that Node must be installed (the parent relays this to the user); do **not** substitute grep for the detector or proceed unscanned. The detector abstains on arbitrary Tailwind spacing (`gap-[13px]`, `p-[7px]`) and ad-hoc `z-index` stacks, so when the project documents a spacing scale, also grep `gap-\[`, `p[trblxy]?-\[`, `m[trblxy]?-\[`, `z-\[` and judge those hits against it. Return the findings JSON plus the grep verdicts.
28
+
29
+ **If no sub-agent tool is exposed (or the user declined)**: run both yourself, assessment first, pre-scan second, so the deterministic findings can't anchor the visual judgment. Keep that order even when the scan feels quicker to start with.
30
+
31
+ **Synthesize** once both are done: merge into a single findings list, noting where they agree and what each caught alone. Fix every finding, or list it as a deliberate exception for the user to accept. A clean scan is a floor, not a verdict: a monotone grid with uniform spacing passes every detector rule, which is exactly what the assessment exists to catch. State in your final summary which path ran (parallel sub-agents or single-context fallback).
32
+
11
33
  ---
12
34
 
13
35
  ## Assess Current Layout
14
36
 
15
- Analyze what's weak about the current spatial design:
37
+ This checklist is sub-agent A's brief (on the fallback path, work through it yourself before the pre-scan). Analyze what's weak about the current spatial design:
16
38
 
17
39
  1. **Spacing**:
18
40
  - Is spacing consistent or arbitrary? (Random padding/margin values)
@@ -138,6 +160,8 @@ Create a systematic plan:
138
160
  - **Consistency**: Is the spacing system applied uniformly?
139
161
  - **Responsiveness**: Does the layout adapt gracefully across screen sizes?
140
162
 
163
+ Answer each item above by citing the file, selector, or value that satisfies it; never a bare yes. Then re-run the pre-scan and fix until the count of unresolved items and unaccepted findings is zero.
164
+
141
165
  When the rhythm and hierarchy land, hand off to `$impeccable polish` for the final pass.
142
166
 
143
167
  ## Live-mode signature params
@@ -10,9 +10,29 @@ Product: system fonts and familiar sans stacks are legitimate here. One well-tun
10
10
 
11
11
  ---
12
12
 
13
+ ## Two isolated assessments (required)
14
+
15
+ Spawn two parallel sub-agents whenever a sub-agent/Task tool is exposed: one for the typography assessment, one for the mechanical pre-scan. If the harness needs explicit user permission for sub-agents, stop and ask before proceeding. Isolation is the point: detector output anchors visual judgment toward what the scan can see, so neither sub-agent gets the other's output. Each assessment runs in its own sub-agent; running either one in this context when a sub-agent tool exists is not permitted, even when it is faster; the fallback below is only for sessions with no sub-agent tool. Give each a self-contained prompt (target files, register, **DESIGN.md** content when present, and its instructions below); do not assume it can read this file.
16
+
17
+ **Sub-agent A (typography assessment)**: give it the full [Assess Current Typography](#assess-current-typography) checklist below, verbatim, in its prompt. It works through every item and returns per-item findings citing file, selector, or value.
18
+
19
+ **Sub-agent B (mechanical pre-scan)**: run the bundled detector scoped to type:
20
+
21
+ ```bash
22
+ node .agents/skills/impeccable/scripts/detect.mjs --json --scope type [target files or dirs]
23
+ ```
24
+
25
+ A missing `node` on PATH is not permission to skip: hunt for a runtime (`command -v node`, nvm or Homebrew paths, the harness's own bundled node) and run it by full path. If none exists, halt the scan and report that Node must be installed (the parent relays this to the user); do **not** substitute grep for the detector or proceed unscanned. The scan checks literal font sizes against the **DESIGN.md** ramp but abstains on `em`, `%`, `clamp()`, and line-heights, so also grep `font-size\s*:`, `fontSize`, `text-\[`, `leading-\[` and judge those hits against the spec. Return the findings JSON plus the grep verdicts.
26
+
27
+ **If no sub-agent tool is exposed (or the user declined)**: run both yourself, assessment first, pre-scan second, so the deterministic findings can't anchor the visual judgment. Keep that order even when the scan feels quicker to start with.
28
+
29
+ **Synthesize** once both are done: merge into a single findings list, noting where they agree and what each caught alone. Fix every finding, or list it as a deliberate exception for the user to accept. A clean scan is a floor, not a verdict: a generic font stack at a flat scale passes every detector rule, which is exactly what the assessment exists to catch. State in your final summary which path ran (parallel sub-agents or single-context fallback).
30
+
31
+ ---
32
+
13
33
  ## Assess Current Typography
14
34
 
15
- Analyze what's weak or generic about the current type:
35
+ This checklist is sub-agent A's brief (on the fallback path, work through it yourself before the pre-scan). Analyze what's weak or generic about the current type:
16
36
 
17
37
  1. **Font choices**:
18
38
  - Are we using invisible defaults? (Inter, Roboto, Arial, Open Sans, system defaults)
@@ -109,6 +129,8 @@ Build a clear type scale:
109
129
  - **Performance**: Are web fonts loading efficiently without layout shift?
110
130
  - **Accessibility**: Does text meet WCAG contrast ratios? Is it zoomable to 200%?
111
131
 
132
+ Answer each item above by citing the file, selector, or value that satisfies it; never a bare yes. Then re-run the pre-scan and fix until the count of unresolved items and unaccepted findings is zero.
133
+
112
134
  When the type carries the hierarchy on its own, hand off to `$impeccable polish` for the final pass.
113
135
 
114
136
  ## Live-mode signature params
@@ -21,7 +21,7 @@ import net from 'node:net';
21
21
  import path from 'node:path';
22
22
  import { fileURLToPath } from 'node:url';
23
23
  import { execFileSync } from 'node:child_process';
24
- import { loadContext, extractRegister } from './context.mjs';
24
+ import { loadContext, extractRegister, extractPlatform } from './context.mjs';
25
25
  import { getCritiqueDir } from './lib/impeccable-paths.mjs';
26
26
 
27
27
  /** Is there code here at all, or just context files / an empty repo? */
@@ -197,6 +197,7 @@ export async function gatherSignals(cwd = process.cwd()) {
197
197
  designPath: ctx.designPath,
198
198
  hasCode: hasCode(cwd),
199
199
  register: extractRegister(ctx.product),
200
+ platform: extractPlatform(ctx.product),
200
201
  },
201
202
  critique: { latest: latestCritique(cwd) },
202
203
  git,
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Context loader: prints PRODUCT.md (and DESIGN.md if present) as one
3
- * markdown block on stdout, or exits with empty stdout when no PRODUCT.md
4
- * is found anywhere. The skill keys off "empty stdout" to branch into the
5
- * init flow.
3
+ * markdown block on stdout, or prints a `NO_PRODUCT_MD:` message when no
4
+ * PRODUCT.md is found anywhere. The skill keys off that message to branch:
5
+ * from-scratch build commands (init / teach / craft / shape) and clear
6
+ * build/shape intent divert into the init flow, while scoped commands proceed
7
+ * using the existing code as context.
6
8
  *
7
9
  * Path resolution (first match wins):
8
10
  * 1. Active project root, if PRODUCT.md or DESIGN.md is there
@@ -692,27 +694,63 @@ function escapeRegExp(value) {
692
694
  }
693
695
 
694
696
  /**
695
- * Pull the register (`brand` or `product`) out of PRODUCT.md by looking
696
- * for a `## Register` section and reading the first non-empty line that
697
- * follows it. Returns null when the file is legacy / register-less.
697
+ * Read the first non-empty line under a bare `## <heading>` section of
698
+ * PRODUCT.md (e.g. `## Register`, `## Platform`). Returns null when the
699
+ * section is absent. The heading match is exact (`\s*$`) so near-miss
700
+ * headings like `## Register guidelines` don't shadow the real field.
698
701
  */
699
- export function extractRegister(product) {
702
+ export function extractSectionValue(product, heading) {
700
703
  if (!product) return null;
704
+ const headingRe = new RegExp(`^##\\s+${escapeRegExp(heading)}\\s*$`, 'i');
701
705
  const lines = product.split('\n');
702
706
  for (let i = 0; i < lines.length; i++) {
703
- if (/^##\s+Register\b/i.test(lines[i].trim())) {
707
+ if (headingRe.test(lines[i].trim())) {
704
708
  for (let j = i + 1; j < lines.length; j++) {
705
709
  const next = lines[j].trim();
706
- if (!next) continue;
707
- const word = next.toLowerCase();
708
- if (word === 'brand' || word === 'product') return word;
709
- return null;
710
+ // A new heading before any value means the section is empty.
711
+ if (/^#{1,6}\s/.test(next)) return null;
712
+ if (next) return next;
710
713
  }
711
714
  }
712
715
  }
713
716
  return null;
714
717
  }
715
718
 
719
+ /**
720
+ * Pull the register (`brand` or `product`) out of PRODUCT.md by looking
721
+ * for a `## Register` section and reading the first non-empty line that
722
+ * follows it. Returns null when the file is legacy / register-less.
723
+ */
724
+ export function extractRegister(product) {
725
+ const word = (extractSectionValue(product, 'Register') || '').toLowerCase();
726
+ return word === 'brand' || word === 'product' ? word : null;
727
+ }
728
+
729
+ /**
730
+ * Pull the platform (`web`, `ios`, `android`, or `adaptive`) out of PRODUCT.md
731
+ * by looking for a `## Platform` section and reading the first non-empty line
732
+ * that follows it. `adaptive` is for cross-platform apps (Flutter, React
733
+ * Native) that ship both iOS and Android from one codebase; a line that names
734
+ * both targets (e.g. `ios, android`) is also read as `adaptive`. Returns null
735
+ * when the file is legacy / platform-less, which the skill treats as `web`
736
+ * (the default the general rules already assume).
737
+ */
738
+ export function extractPlatform(product) {
739
+ const value = (extractSectionValue(product, 'Platform') || '').toLowerCase();
740
+ if (!value) return null;
741
+ if (value === 'web' || value === 'ios' || value === 'android' || value === 'adaptive') return value;
742
+ // A short list naming both native targets (`ios, android`, `ios and
743
+ // android`) = adaptive. Only list separators and the two platform words may
744
+ // appear; anything else (prose, negations) is unrecognized and falls
745
+ // through to the CLI's WARNING path.
746
+ const tokens = value.split(/[\s,+&/]+/).filter(t => t && t !== 'and');
747
+ if (tokens.length >= 2 && tokens.every(t => t === 'ios' || t === 'android')
748
+ && tokens.includes('ios') && tokens.includes('android')) {
749
+ return 'adaptive';
750
+ }
751
+ return null;
752
+ }
753
+
716
754
  /**
717
755
  * Read the installed skill's own version from the sibling SKILL.md frontmatter
718
756
  * (this file lives at `<skill>/scripts/context.mjs`). Returns null when the
@@ -860,8 +898,11 @@ async function cli() {
860
898
  // — cheap models miss the empty case more often than the explicit one.
861
899
  const parts = [
862
900
  'NO_PRODUCT_MD: This project has no PRODUCT.md yet. ' +
863
- 'Stop the current task, load reference/init.md, and follow its ' +
864
- 'instructions to write PRODUCT.md before resuming.',
901
+ 'Follow SKILL.md Setup step 1: for `init`, `teach`, `craft`, `shape`, ' +
902
+ 'or wording that clearly maps to a from-scratch build/shape flow, load ' +
903
+ 'reference/init.md and write PRODUCT.md first; for any other (scoped) ' +
904
+ 'command against existing code, proceed using the code as context and ' +
905
+ 'offer `/impeccable init` as a suggestion (do not block).',
865
906
  ];
866
907
  parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists }));
867
908
  if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
@@ -884,6 +925,26 @@ async function cli() {
884
925
  ? `NEXT STEP: This project's register is \`${register}\`. You MUST now read \`reference/${register}.md\` before producing any design output.`
885
926
  : `NEXT STEP: You MUST now read the matching register reference (\`reference/brand.md\` or \`reference/product.md\`) before producing any design output. Pick based on PRODUCT.md above.`;
886
927
  parts.push(next);
928
+ const platform = extractPlatform(ctx.product);
929
+ const nativeRefs =
930
+ platform === 'adaptive' ? ['ios', 'android'] : platform === 'ios' || platform === 'android' ? [platform] : [];
931
+ if (nativeRefs.length) {
932
+ const refList = nativeRefs.map(p => `\`reference/${p}.md\``).join(' and ');
933
+ const label = platform === 'adaptive' ? '`adaptive` (both iOS and Android)' : `\`${platform}\``;
934
+ parts.push(
935
+ `NEXT STEP: This project targets ${label}. Also read ${refList} for native conventions, in addition to the register reference.`,
936
+ );
937
+ } else if (!platform) {
938
+ // A `## Platform` section that names something we don't recognize (a
939
+ // toolchain like `flutter`, a typo) would otherwise silently fall back to
940
+ // web — the wrong default exactly when the user tried to say "native".
941
+ const rawPlatform = extractSectionValue(ctx.product, 'Platform');
942
+ if (rawPlatform) {
943
+ parts.push(
944
+ `WARNING: PRODUCT.md's \`## Platform\` value \`${rawPlatform}\` is not recognized; treating the project as \`web\`. Valid values are \`web\`, \`ios\`, \`android\`, or \`adaptive\` (cross-platform, ships both). If this project is native, fix the field (name the design language the app renders, not the toolchain) and surface it to the user.`,
945
+ );
946
+ }
947
+ }
887
948
  if (updateDirective) parts.push(updateDirective);
888
949
  process.stdout.write(parts.join('\n\n---\n\n') + '\n');
889
950
  }
@@ -1276,7 +1276,7 @@ if (IS_BROWSER) {
1276
1276
 
1277
1277
  function browserPrimaryFont(stack) {
1278
1278
  if (!stack || /var\(/i.test(stack)) return '';
1279
- return String(stack)
1279
+ return String(stack || '')
1280
1280
  .split(',')
1281
1281
  .map(normalizeBrowserFontName)
1282
1282
  .find(font => font && !GENERIC_FONTS.has(font)) || '';
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
4
  import { loadDesignSystemForCwd } from '../design-system.mjs';
5
+ import { RULE_SCOPES, filterByScopes } from '../registry/antipatterns.mjs';
5
6
  import { createBrowserDetector, detectUrl } from '../engines/browser/detect-url.mjs';
6
7
  import { detectHtml } from '../engines/static-html/detect-html.mjs';
7
8
  import { detectText } from '../engines/regex/detect-text.mjs';
@@ -93,6 +94,8 @@ Options:
93
94
  --quiet In text mode, only print the final findings count
94
95
  --gpt Also report GPT-specific provider tells (off by default)
95
96
  --gemini Also report Gemini-specific provider tells (off by default)
97
+ --scope <name> Only report rules in the given design domain
98
+ (type, layout). Comma-separated.
96
99
  --no-config Do not apply project config, detector ignores, inline
97
100
  ignore comments, or DESIGN.md
98
101
  --no-inline-ignores Do not honor in-file impeccable-disable* ignore comments
@@ -151,6 +154,33 @@ async function detectCli() {
151
154
  const providers = [];
152
155
  if (args.includes('--gpt')) providers.push('gpt');
153
156
  if (args.includes('--gemini')) providers.push('gemini');
157
+ const scopes = [];
158
+ for (let i = 0; i < args.length; i++) {
159
+ if (args[i] !== '--scope' && !args[i].startsWith('--scope=')) continue;
160
+ const inline = args[i].startsWith('--scope=');
161
+ const value = inline ? args[i].slice('--scope='.length) : args[i + 1];
162
+ const parsed = (value && !value.startsWith('--'))
163
+ ? value.split(',').map(s => s.trim()).filter(Boolean)
164
+ : [];
165
+ // A bare `--scope` would otherwise fall out of `targets` and scan unscoped;
166
+ // fail loudly so a mistyped pre-scan never runs the wrong rule set.
167
+ if (parsed.length === 0) {
168
+ process.stderr.write(
169
+ `Error: --scope requires a value. Valid scopes: ${[...RULE_SCOPES].join(', ')}\n`,
170
+ );
171
+ process.exit(1);
172
+ }
173
+ scopes.push(...parsed);
174
+ args.splice(i, inline ? 1 : 2);
175
+ i -= 1;
176
+ }
177
+ const unknownScopes = scopes.filter(s => !RULE_SCOPES.has(s));
178
+ if (unknownScopes.length > 0) {
179
+ process.stderr.write(
180
+ `Error: unknown --scope value(s): ${unknownScopes.join(', ')}. Valid scopes: ${[...RULE_SCOPES].join(', ')}\n`,
181
+ );
182
+ process.exit(1);
183
+ }
154
184
  const designSystemEnabled = configEnabled && !args.includes('--no-design-system') && detectionConfig.designSystem?.enabled !== false;
155
185
  const designSystem = designSystemEnabled ? loadDesignSystemForCwd(process.cwd()) : null;
156
186
  // Inline `impeccable-disable*` waivers are part of the scanned file, so they
@@ -276,6 +306,7 @@ async function detectCli() {
276
306
  }
277
307
 
278
308
  allFindings = filterDetectionFindings(allFindings, detectionConfig);
309
+ allFindings = filterByScopes(allFindings, scopes);
279
310
 
280
311
  if (allFindings.length > 0) {
281
312
  if (jsonMode) process.stdout.write(formatFindings(allFindings, true) + '\n');
@@ -9,6 +9,8 @@ const DESIGN_NAMES = ['DESIGN.md', 'Design.md', 'design.md'];
9
9
  const FALLBACK_DIRS = ['.agents/context', 'docs'];
10
10
  const COLOR_CHANNEL_TOLERANCE = 6;
11
11
  const RADIUS_TOLERANCE_PX = 0.5;
12
+ const FONT_SIZE_TOLERANCE_PX = 0.5;
13
+ const FONT_SIZE_LITERAL_RE = /^-?[\d.]+(?:px|rem)$/;
12
14
 
13
15
  const CSS_COLOR_RE = /#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|oklch\([^)]+\)|hsla?\([^)]+\)/gi;
14
16
  const FONT_DECL_RE = /font-family\s*:\s*([^;}\n]+)/gi;
@@ -16,6 +18,9 @@ const FONT_JS_RE = /fontFamily\s*[:=]\s*["'`]([^"'`]+)["'`]/g;
16
18
  const GOOGLE_FONT_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
17
19
  const BORDER_RADIUS_RE = /border-radius\s*:\s*([^;}\n]+)/gi;
18
20
  const BORDER_RADIUS_JS_RE = /borderRadius\s*[:=]\s*["'`]([^"'`]+)["'`]/g;
21
+ const FONT_SIZE_DECL_RE = /font-size\s*:\s*([^;}\n]+)/gi;
22
+ const FONT_SIZE_JS_RE = /fontSize\s*[:=]\s*["'`]([^"'`]+)["'`]/g;
23
+ const TAILWIND_FONT_SIZE_RE = /\btext-\[(-?[\d.]+(?:px|rem))\]/g;
19
24
  const STATIC_DESIGN_SKIP_TAGS = new Set(['head', 'title', 'meta', 'link', 'style', 'script', 'noscript', 'template', 'source']);
20
25
 
21
26
  function firstExisting(dir, names) {
@@ -283,6 +288,18 @@ function addTypographyFonts(out, typography) {
283
288
  }
284
289
  }
285
290
 
291
+ function addTypographySizes(out, typography) {
292
+ if (!typography || typeof typography !== 'object') return;
293
+ for (const role of Object.values(typography)) {
294
+ if (!role || typeof role !== 'object') continue;
295
+ const raw = String(role.fontSize ?? '').trim().toLowerCase();
296
+ if (!FONT_SIZE_LITERAL_RE.test(raw)) continue;
297
+ const px = resolveLengthPx(raw, 16);
298
+ if (px == null || !Number.isFinite(px) || px <= 0) continue;
299
+ out.allowedFontSizes.push({ value: raw, px });
300
+ }
301
+ }
302
+
286
303
  function addRoundedScale(out, rounded) {
287
304
  if (!rounded || typeof rounded !== 'object') return;
288
305
  for (const [rawName, value] of Object.entries(rounded)) {
@@ -340,10 +357,12 @@ function normalizeDesignSystem(input = {}) {
340
357
  allowedFonts: new Set(),
341
358
  allowedColorKeys: new Map(),
342
359
  allowedRadii: [],
360
+ allowedFontSizes: [],
343
361
  hasPillRadius: false,
344
362
  };
345
363
 
346
364
  addTypographyFonts(out, frontmatter.typography);
365
+ addTypographySizes(out, frontmatter.typography);
347
366
  addColorObject(out, frontmatter.colors);
348
367
  addSidecarColors(out, sidecar);
349
368
  addRoundedScale(out, frontmatter.rounded);
@@ -352,6 +371,7 @@ function normalizeDesignSystem(input = {}) {
352
371
  out.hasFonts = out.allowedFonts.size > 0;
353
372
  out.hasColors = out.allowedColorKeys.size > 0;
354
373
  out.hasRadii = out.allowedRadii.length > 0;
374
+ out.hasFontSizes = out.allowedFontSizes.length > 0;
355
375
  return out;
356
376
  }
357
377
 
@@ -418,6 +438,17 @@ function isAllowedRadiusRaw(raw, designSystem) {
418
438
  return designSystem.allowedRadii.some(entry => Math.abs(entry.px - px) <= RADIUS_TOLERANCE_PX);
419
439
  }
420
440
 
441
+ function isAllowedFontSizeRaw(raw, designSystem) {
442
+ if (!designSystem?.hasFontSizes) return true;
443
+ const text = String(raw || '').trim().toLowerCase().replace(/\s*!important\s*$/, '');
444
+ if (!FONT_SIZE_LITERAL_RE.test(text)) return true;
445
+ const px = resolveLengthPx(text, 16);
446
+ if (px == null || !Number.isFinite(px) || px <= 0) return true;
447
+ return designSystem.allowedFontSizes.some(
448
+ entry => Math.abs(entry.px - px) <= FONT_SIZE_TOLERANCE_PX,
449
+ );
450
+ }
451
+
421
452
  function lineLooksCommented(line) {
422
453
  const trimmed = String(line || '').trim();
423
454
  return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') || trimmed.startsWith('<!--');
@@ -509,6 +540,18 @@ function checkRadiusValue(value, filePath, line, designSystem, context) {
509
540
  return findings;
510
541
  }
511
542
 
543
+ function checkFontSizeValue(value, filePath, line, designSystem, context) {
544
+ const token = String(value || '').trim();
545
+ if (isAllowedFontSizeRaw(token, designSystem)) return [];
546
+ return [makeDesignFinding(
547
+ 'design-system-font-size',
548
+ filePath,
549
+ `${context}: ${token} is off the DESIGN.md type ramp`,
550
+ line,
551
+ { ignoreValue: token },
552
+ )];
553
+ }
554
+
512
555
  function checkSourceDesignSystem(content, filePath, options = {}) {
513
556
  const designSystem = options.designSystem;
514
557
  if (!designSystem?.present) return [];
@@ -567,6 +610,18 @@ function checkSourceDesignSystem(content, filePath, options = {}) {
567
610
  findings.push(...checkRadiusValue(match[1], filePath, lineNum, designSystem, 'borderRadius'));
568
611
  }
569
612
  }
613
+
614
+ if (designSystem.hasFontSizes) {
615
+ for (const match of line.matchAll(FONT_SIZE_DECL_RE)) {
616
+ findings.push(...checkFontSizeValue(match[1], filePath, lineNum, designSystem, 'font-size'));
617
+ }
618
+ for (const match of line.matchAll(FONT_SIZE_JS_RE)) {
619
+ findings.push(...checkFontSizeValue(match[1], filePath, lineNum, designSystem, 'fontSize'));
620
+ }
621
+ for (const match of line.matchAll(TAILWIND_FONT_SIZE_RE)) {
622
+ findings.push(...checkFontSizeValue(match[1], filePath, lineNum, designSystem, 'text-[…] class'));
623
+ }
624
+ }
570
625
  }
571
626
 
572
627
  return dedupeDesignFindings(findings);
@@ -581,6 +636,8 @@ function sampleText(el) {
581
636
  return text ? ` "${text.slice(0, 40)}"` : '';
582
637
  }
583
638
 
639
+ // Font-size design-system checks are source-scan-only (see checkSourceDesignSystem).
640
+ // Computed font-size cascades and clamp() ramps resolve to off-ramp px in the browser.
584
641
  function collectStaticDesignSystemFindings(document, window, filePath, designSystem) {
585
642
  if (!designSystem?.present) return [];
586
643
  const findings = [];
@@ -698,6 +755,12 @@ function canonicalDesignFindingKey(item) {
698
755
  const label = String(value || '').trim().toLowerCase();
699
756
  return label ? `${item.antipattern}:radius:${label}` : null;
700
757
  }
758
+ if (item.antipattern === 'design-system-font-size') {
759
+ const px = resolveLengthPx(String(value || '').trim(), 16);
760
+ if (px != null && Number.isFinite(px)) return `${item.antipattern}:font-size:${Math.round(px * 100) / 100}`;
761
+ const label = String(value || '').trim().toLowerCase();
762
+ return label ? `${item.antipattern}:font-size:${label}` : null;
763
+ }
701
764
  return null;
702
765
  }
703
766
 
@@ -744,6 +807,7 @@ export {
744
807
  isAllowedFont,
745
808
  isAllowedColorRaw,
746
809
  isAllowedRadiusRaw,
810
+ isAllowedFontSizeRaw,
747
811
  checkSourceDesignSystem,
748
812
  collectStaticDesignSystemFindings,
749
813
  mergeDesignSystemFindings,