@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.
- package/CHANGELOG.md +33 -2
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +17 -0
- package/dist/builtin/intercom/README.md +27 -21
- package/dist/builtin/intercom/broker/broker.ts +3 -6
- package/dist/builtin/intercom/broker/paths.ts +42 -5
- package/dist/builtin/intercom/broker/spawn.ts +78 -10
- package/dist/builtin/intercom/config.ts +6 -3
- package/dist/builtin/intercom/contact-supervisor-tool.ts +6 -4
- package/dist/builtin/intercom/index-heavy.ts +1 -1
- package/dist/builtin/intercom/index.ts +162 -130
- package/dist/builtin/intercom/intercom-tool.ts +9 -3
- package/dist/builtin/intercom/package.json +10 -1
- package/dist/builtin/intercom/result-renderers.ts +1 -1
- package/dist/builtin/intercom/ui/compose.ts +2 -2
- package/dist/builtin/intercom/ui/inline-message.ts +2 -2
- package/dist/builtin/intercom/ui/session-list.ts +2 -2
- package/dist/builtin/mcp/CHANGELOG.md +11 -0
- package/dist/builtin/mcp/README.md +6 -6
- package/dist/builtin/mcp/direct-tools.ts +41 -23
- package/dist/builtin/mcp/index.ts +31 -4
- package/dist/builtin/mcp/init.ts +4 -43
- package/dist/builtin/mcp/metadata-hydration.ts +37 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/mcp/proxy-info-modes.ts +67 -14
- package/dist/builtin/mcp/startup-warmup.ts +98 -0
- package/dist/builtin/mcp/tool-result-renderer.ts +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +18 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/index.ts +14 -25
- package/dist/builtin/subagents/src/extension/startup-maintenance.ts +89 -0
- package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +23 -18
- package/dist/builtin/subagents/src/runs/background/result-watcher.ts +20 -10
- package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +13 -3
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +10 -2
- package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +28 -1
- package/dist/builtin/subagents/src/tui/render-event-formatting.ts +15 -48
- package/dist/builtin/subagents/src/tui/render-layout.ts +9 -0
- package/dist/builtin/subagents/src/tui/render-widget-graph.ts +27 -25
- package/dist/builtin/subagents/src/tui/render-widget.ts +72 -66
- package/dist/builtin/subagents/src/tui/render.ts +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +29 -0
- package/dist/builtin/workflows/README.md +4 -2
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +3 -1
- package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -2
- package/dist/builtin/workflows/builtin/goal-reducer.ts +21 -0
- package/dist/builtin/workflows/builtin/goal-review.ts +60 -6
- package/dist/builtin/workflows/builtin/goal-runner.ts +71 -22
- package/dist/builtin/workflows/builtin/goal-types.ts +7 -1
- package/dist/builtin/workflows/builtin/ralph-core.ts +52 -4
- package/dist/builtin/workflows/builtin/ralph-review-gate.ts +10 -3
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +23 -2
- package/dist/builtin/workflows/builtin/ralph-runner.ts +38 -16
- package/dist/builtin/workflows/builtin/review-convergence.ts +111 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +2 -0
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +16 -13
- package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +1 -0
- package/dist/builtin/workflows/skills/impeccable/reference/adapt.native.md +58 -0
- package/dist/builtin/workflows/skills/impeccable/reference/android.md +40 -0
- package/dist/builtin/workflows/skills/impeccable/reference/animate.md +2 -0
- package/dist/builtin/workflows/skills/impeccable/reference/audit.md +2 -0
- package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +139 -0
- package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +69 -62
- package/dist/builtin/workflows/skills/impeccable/reference/critique.md +21 -7
- package/dist/builtin/workflows/skills/impeccable/reference/document.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +92 -0
- package/dist/builtin/workflows/skills/impeccable/reference/init.md +29 -7
- package/dist/builtin/workflows/skills/impeccable/reference/ios.md +45 -0
- package/dist/builtin/workflows/skills/impeccable/reference/layout.md +25 -1
- package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +23 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +2 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +75 -14
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +31 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +64 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +118 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +16 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +66 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +42 -10
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/fonts.mjs +30 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +4 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +660 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-before-edit.mjs +516 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +1764 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +61 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +4 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/is-generated.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +13 -16
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +17 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +104 -37
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +9 -0
- package/dist/builtin/workflows/src/durable/backend.ts +12 -0
- package/dist/builtin/workflows/src/durable/child-primitive.ts +12 -8
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +63 -2
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +28 -0
- package/dist/builtin/workflows/src/durable/resume-catalog.ts +12 -0
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +5 -2
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +165 -27
- package/dist/builtin/workflows/src/durable/types.ts +30 -1
- package/dist/builtin/workflows/src/engine/primitives/task.ts +1 -0
- package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +1 -2
- package/dist/builtin/workflows/src/engine/run-returned-status.ts +50 -16
- package/dist/builtin/workflows/src/engine/run.ts +32 -32
- package/dist/builtin/workflows/src/engine/runtime.ts +5 -0
- package/dist/builtin/workflows/src/extension/extension-factory.ts +3 -0
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +9 -2
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +137 -27
- package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +22 -6
- package/dist/builtin/workflows/src/extension/public-types.ts +1 -1
- package/dist/builtin/workflows/src/extension/workflow-command-completions.ts +13 -1
- package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +19 -4
- package/dist/builtin/workflows/src/extension/workflow-command-surfaces.ts +9 -0
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +2 -0
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +48 -23
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +20 -8
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +24 -8
- package/dist/builtin/workflows/src/runs/background/status.ts +4 -3
- package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +13 -3
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +29 -8
- package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +2 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +4 -3
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +18 -19
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-unresolved-overflow.ts +38 -0
- package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +155 -20
- package/dist/builtin/workflows/src/runs/shared/worktree-types.ts +6 -0
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -1
- package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +18 -1
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +23 -5
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
- package/dist/builtin/workflows/src/shared/returned-run-status.ts +116 -0
- package/dist/builtin/workflows/src/shared/workflow-failures-decisions.ts +2 -0
- package/dist/builtin/workflows/src/tui/graph-view-render-helpers.ts +21 -3
- package/dist/builtin/workflows/src/tui/graph-view-state.ts +3 -0
- package/dist/builtin/workflows/src/tui/graph-view-types.ts +3 -0
- package/dist/builtin/workflows/src/tui/status-list.ts +22 -15
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +4 -4
- package/dist/bun/internal-intercom-broker.d.ts +6 -0
- package/dist/bun/internal-intercom-broker.d.ts.map +1 -0
- package/dist/bun/internal-intercom-broker.js +39 -0
- package/dist/bun/internal-intercom-broker.js.map +1 -0
- package/dist/bun/split-loader.d.ts.map +1 -1
- package/dist/bun/split-loader.js +21 -8
- package/dist/bun/split-loader.js.map +1 -1
- package/dist/core/agent-session-auto-compaction.d.ts +21 -2
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +163 -11
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +10 -1
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +12 -3
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-models.d.ts +2 -2
- package/dist/core/agent-session-models.d.ts.map +1 -1
- package/dist/core/agent-session-models.js.map +1 -1
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +1 -0
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts +2 -0
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +152 -4
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +15 -1
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +6 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-runner.js +36 -20
- package/dist/core/compaction/context-compaction-runner.js.map +1 -1
- package/dist/core/copilot-anthropic-sse-repair.d.ts +23 -0
- package/dist/core/copilot-anthropic-sse-repair.d.ts.map +1 -0
- package/dist/core/copilot-anthropic-sse-repair.js +340 -0
- package/dist/core/copilot-anthropic-sse-repair.js.map +1 -0
- package/dist/core/copilot-gemini-reasoning.d.ts +16 -8
- package/dist/core/copilot-gemini-reasoning.d.ts.map +1 -1
- package/dist/core/copilot-gemini-reasoning.js +27 -22
- package/dist/core/copilot-gemini-reasoning.js.map +1 -1
- package/dist/core/copilot-hosts.d.ts +12 -0
- package/dist/core/copilot-hosts.d.ts.map +1 -0
- package/dist/core/copilot-hosts.js +33 -0
- package/dist/core/copilot-hosts.js.map +1 -0
- package/dist/core/extensions/agent-events.d.ts +1 -1
- package/dist/core/extensions/agent-events.d.ts.map +1 -1
- package/dist/core/extensions/agent-events.js.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts +8 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +2 -1
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +6 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +72 -29
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +5 -5
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/openai-responses-payload-sanitizer.d.ts +1 -0
- package/dist/core/openai-responses-payload-sanitizer.d.ts.map +1 -1
- package/dist/core/openai-responses-payload-sanitizer.js +24 -9
- package/dist/core/openai-responses-payload-sanitizer.js.map +1 -1
- package/dist/core/sdk-types.d.ts +2 -0
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +1 -0
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +6 -0
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/slash-commands.d.ts +2 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +141 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/index-extensions.d.ts +1 -1
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main-deferred-startup.d.ts +12 -0
- package/dist/main-deferred-startup.d.ts.map +1 -1
- package/dist/main-deferred-startup.js +23 -2
- package/dist/main-deferred-startup.js.map +1 -1
- package/dist/main-early-input.d.ts +41 -0
- package/dist/main-early-input.d.ts.map +1 -0
- package/dist/main-early-input.js +154 -0
- package/dist/main-early-input.js.map +1 -0
- package/dist/main.d.ts +0 -6
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +20 -14
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-actions.js +0 -1
- package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +15 -0
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +25 -0
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.js +21 -9
- package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.js +41 -28
- package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -0
- package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.js +318 -185
- package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +12 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +39 -0
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +6 -2
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.js +3 -1
- package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-process-lifecycle.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-process-lifecycle.js +0 -3
- package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +36 -10
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-session-routing.js +5 -1
- package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +26 -22
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/theme/global-theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/global-theme.js +26 -2
- package/dist/modes/interactive/theme/global-theme.js.map +1 -1
- package/dist/utils/fs-watch.d.ts +24 -1
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +60 -5
- package/dist/utils/fs-watch.js.map +1 -1
- package/docs/compaction.md +8 -0
- package/docs/development.md +4 -0
- package/docs/extensions.md +5 -0
- package/docs/json.md +3 -1
- package/docs/providers.md +1 -0
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +3 -1
- package/docs/settings.md +27 -1
- package/docs/subagents.md +4 -0
- package/docs/terminal-setup.md +4 -0
- package/docs/usage.md +2 -2
- package/docs/windows.md +4 -0
- package/docs/workflows.md +21 -9
- package/npm-shrinkwrap.json +513 -23
- 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
|
|
4
|
-
* is found anywhere. The skill keys off
|
|
5
|
-
* init
|
|
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
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
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
|
|
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 (
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
if (
|
|
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
|
-
'
|
|
864
|
-
'
|
|
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
|
}
|
package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs
CHANGED
|
@@ -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,
|