chati-dev 4.3.1 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +12 -4
  3. package/framework/agents/build/dev.md +5 -1
  4. package/framework/agents/discover/greenfield-wu.md +16 -1
  5. package/framework/agents/plan/tasks.md +31 -0
  6. package/framework/agents/plan/ux-brand-architect.md +21 -2
  7. package/framework/agents/plan/ux-component-engineer.md +10 -0
  8. package/framework/agents/quality/qa-implementation.md +1 -1
  9. package/framework/agents/quality/qa-planning.md +1 -1
  10. package/framework/agents/quality/qa-visual.md +24 -21
  11. package/framework/config.yaml +5 -4
  12. package/framework/constitution.md +58 -11
  13. package/framework/context/governance.md +12 -10
  14. package/framework/context/root.md +2 -2
  15. package/framework/data/entity-registry.yaml +12 -4
  16. package/framework/data/qa-rubrics.yaml +335 -0
  17. package/framework/domains/constitution.yaml +3 -3
  18. package/framework/executors/json-validate.js +80 -0
  19. package/framework/executors/npm-script.js +72 -0
  20. package/framework/executors/path-check.js +67 -0
  21. package/framework/executors/registry.yaml +36 -0
  22. package/framework/hooks/advance-trigger.js +47 -3
  23. package/framework/hooks/constitution-guard.js +9 -4
  24. package/framework/hooks/git-push-authority.js +113 -0
  25. package/framework/hooks/license-guard.js +59 -9
  26. package/framework/hooks/model-governance.js +40 -17
  27. package/framework/hooks/prism-engine.js +78 -11
  28. package/framework/hooks/session-digest.js +12 -3
  29. package/framework/hooks/settings.json +7 -3
  30. package/framework/hooks/style-guard.js +52 -3
  31. package/framework/hooks/team-quality-gate.js +43 -5
  32. package/framework/i18n/en.yaml +3 -3
  33. package/framework/i18n/es.yaml +3 -3
  34. package/framework/i18n/fr.yaml +3 -3
  35. package/framework/i18n/pt.yaml +3 -3
  36. package/framework/intelligence/confidence.yaml +85 -6
  37. package/framework/intelligence/context-engine.md +9 -5
  38. package/framework/intelligence/decision-engine.md +1 -1
  39. package/framework/orchestrator/chati-router.js +1 -1
  40. package/framework/orchestrator/chati-update.md +19 -3
  41. package/framework/orchestrator/chati.md +19 -1
  42. package/framework/quality-gates/planning-gate.md +2 -0
  43. package/framework/scaffold/motion-premium/scaffold.yaml +2 -1
  44. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  45. package/framework/scaffold/saas-dashboard/README.md +58 -0
  46. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  47. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  48. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  49. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  50. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  51. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  52. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  53. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  54. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  55. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  56. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  57. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  58. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  59. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  60. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  61. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  62. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  63. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  64. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  65. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  66. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  67. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  68. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  69. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  70. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  71. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  72. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  73. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  74. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  75. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  76. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  77. package/framework/schemas/session.schema.json +5 -0
  78. package/framework/schemas/task.schema.json +9 -0
  79. package/framework/scripts/visual-qa.js +87 -1
  80. package/framework/tasks/orchestrator-health.md +4 -4
  81. package/framework/tasks/qa-impl-verdict.md +11 -5
  82. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  83. package/package.json +3 -3
  84. package/src/config/context-file-generator.js +0 -6
  85. package/src/dashboard/renderer.js +0 -36
  86. package/src/executors/runner.js +204 -0
  87. package/src/installer/core.js +2 -1
  88. package/src/installer/templates.js +5 -6
  89. package/src/intelligence/registry-manager.js +1 -1
  90. package/src/license/client.js +9 -3
  91. package/src/license/commands.js +12 -2
  92. package/src/license/machine-id.js +42 -1
  93. package/src/memory/gotchas.js +58 -40
  94. package/src/memory/magic-docs.js +1 -1
  95. package/src/memory/session-digest.js +9 -4
  96. package/src/orchestrator/cli.js +174 -14
  97. package/src/orchestrator/doctor.js +98 -16
  98. package/src/orchestrator/pipeline-manager.js +44 -22
  99. package/src/orchestrator/session-manager.js +56 -8
  100. package/src/telemetry/sender.js +7 -19
  101. package/src/terminal/run-team.js +3 -3
  102. package/src/upgrade/tracked-files-detector.js +34 -16
  103. package/src/utils/feature-flags.js +1 -1
  104. package/src/utils/flatten-entities.js +4 -40
  105. package/src/utils/schema-validator.js +0 -14
  106. package/src/wizard/i18n.js +3 -3
  107. package/src/api/index.js +0 -120
  108. package/src/autonomy/autonomous-gate.js +0 -294
  109. package/src/autonomy/build-loop.js +0 -281
  110. package/src/autonomy/build-state.js +0 -286
  111. package/src/autonomy/cause-analyzer.js +0 -177
  112. package/src/autonomy/escalation.js +0 -214
  113. package/src/autonomy/index.js +0 -51
  114. package/src/autonomy/mode-manager.js +0 -225
  115. package/src/autonomy/mode-suggester.js +0 -283
  116. package/src/autonomy/progress-reporter.js +0 -275
  117. package/src/autonomy/safety-net.js +0 -370
  118. package/src/config/agent-customizer.js +0 -231
  119. package/src/decision/analyzer.js +0 -291
  120. package/src/decision/engine.js +0 -250
  121. package/src/decision/index.js +0 -38
  122. package/src/decision/registry-healer.js +0 -468
  123. package/src/decision/registry-updater.js +0 -339
  124. package/src/extensions/loader.js +0 -145
  125. package/src/extensions/registry.js +0 -134
  126. package/src/gates/circuit-breaker.js +0 -151
  127. package/src/gates/g1-planning-complete.js +0 -154
  128. package/src/gates/g2-qa-planning.js +0 -156
  129. package/src/gates/g3-implementation.js +0 -215
  130. package/src/gates/g4-qa-implementation.js +0 -240
  131. package/src/gates/g5-deploy-ready.js +0 -181
  132. package/src/gates/gate-base.js +0 -185
  133. package/src/gates/index.js +0 -46
  134. package/src/health/auto-fix.js +0 -216
  135. package/src/health/engine.js +0 -246
  136. package/src/merger/semantic-merger.js +0 -292
  137. package/src/preview/detector.js +0 -238
  138. package/src/preview/index.js +0 -20
  139. package/src/preview/launcher.js +0 -235
  140. package/src/preview/log-buffer.js +0 -103
  141. package/src/quality/metrics-collector.js +0 -281
  142. package/src/quality/test-runner.js +0 -366
  143. package/src/tasks/executor.js +0 -195
  144. package/src/tasks/index.js +0 -4
  145. package/src/tasks/loader.js +0 -210
  146. package/src/tasks/router.js +0 -182
  147. package/src/utils/event-bus.js +0 -126
@@ -0,0 +1,335 @@
1
+ # QA Rubrics - single source of truth for gate thresholds and scoring criteria.
2
+ #
3
+ # Why this file exists: the pass thresholds (95 / 90 / 85) used to live as
4
+ # literals in advance-trigger.js, the quality-gate docs, the QA agent files and
5
+ # the constitution at the same time, with nothing keeping them in sync. They now
6
+ # live here, in exactly one place. advance-trigger.js reads the threshold at
7
+ # runtime; the QA agents and gate docs reference this file as canonical.
8
+ #
9
+ # Treat the rubric like a tuned parameter, not prose: changing a threshold or a
10
+ # dimension weight changes the quality of the output, so every change bumps
11
+ # rubric_version and adds a changelog entry. No agent may edit this file or its
12
+ # own threshold during a run.
13
+
14
+ rubric_version: "1.2.0"
15
+
16
+ changelog:
17
+ - version: "1.0.0"
18
+ date: "2026-06-07"
19
+ change: "Initial extraction. Establishes the canonical gate thresholds as a single source of truth."
20
+ - version: "1.1.0"
21
+ date: "2026-06-08"
22
+ change: "Add the 8-dimension QA-Visual scoring rubric (weights, premium/slop markers, measurable + judged signals), the weak-dimension cap, and the anti-slop catalog."
23
+
24
+ # Minimum score (percent) an agent must reach to clear its gate.
25
+ thresholds:
26
+ qa-planning: 95
27
+ qa-implementation: 95
28
+ qa-visual: 90
29
+ standard: 90
30
+ discovery: 85
31
+
32
+ # Scores within this many points below the threshold trigger a human-review
33
+ # band even in autonomous mode (the gate does not auto-pass on a near miss).
34
+ review_band: 5
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # QA-Visual scoring rubric (8 dimensions). This is the canonical quality bar
38
+ # the QA-Visual agent scores generated UIs against, and the contract the UX
39
+ # agents design toward. Weights sum to 1.0. Each dimension carries premium
40
+ # markers, slop markers (the generic-template tells), measurable signals (what
41
+ # visual-qa.js can detect) and judged signals (what the agent eyeballs).
42
+ # ---------------------------------------------------------------------------
43
+ visual_rubric:
44
+ applies_to: qa-visual
45
+ scoring_protocol: |-
46
+ Application protocol for the QA-Visual agent scoring against this rubric:
47
+
48
+ SCORING ORDER: Score dimensions in this sequence: accessibility first (it has the hardest cap), then interaction, then layout, then motion, then typography, then color, then spacing_composition, then performance. Apply dimension caps before computing the weighted total. If accessibility scores below 60, apply the hard cap of 70 immediately and surface it as a BLOCKED finding before computing any other dimension.
49
+
50
+ EVIDENCE STANDARD: Every JUDGED signal that produces a finding below the premium band requires a screenshot reference cited by filename. Every MEASURABLE signal finding requires the computed value that triggered the flag. Findings without evidence are downgraded from ERROR to SUGGESTION and treated as non-blocking per the Fault Vector Protocol (Article XXII).
51
+
52
+ ANTI-SLOP CATALOG USAGE: Scan the anti-slop catalog before opening screenshots. Each catalog entry that matches the output applies a -3 to -8 point penalty against the most relevant dimension score (not the overall score directly). Document which catalog entries fired and which dimension absorbed the penalty. An output that triggers 4 or more catalog entries in the same dimension automatically scores that dimension at or below 55, triggering the weak dimension cap.
53
+
54
+ GATEWAY WEAK MODE: When Playwright is unavailable and the agent operates in WEAK gateway mode, all MEASURABLE signals are assessed from source code analysis only. JUDGED signals from screenshots are marked as CANNOT-ASSESS. The overall score is capped at 84 in WEAK mode, preventing a fallback-only verdict from clearing the 90 APPROVED threshold. Surface gateway: WEAK prominently in the report header.
55
+
56
+ WEIGHTED TOTAL FORMULA: overall_score = sum(dimension_score times dimension_weight) across all 8 dimensions, after individual dimension caps are applied, after anti-slop catalog penalties are absorbed into dimension scores, and after the weak_dimension_cap is evaluated. Do not round intermediate values; round the final overall score to the nearest integer.
57
+
58
+ CORRECTION LOOP ROUTING: When a dimension scores below its threshold, route the correction to the appropriate fault layer per Article XXII. Layout, spacing, and typography failures route to the UX-Brand-Architect for spec revision or to Dev for implementation correction. Motion and interaction failures route to Dev. Accessibility failures route to Dev with explicit WCAG violation citations. Color failures route to Dev if token references are wrong, or to UX-Brand-Architect if the palette itself needs redesign. Performance failures route to Dev.
59
+
60
+ EM-DASH ENFORCEMENT: The framework bans em dashes in all visible text. If visual-qa.js reports em_dashes_found=true, apply a -5 point penalty to the typography dimension score and raise a VISUAL-ERROR regardless of other dimension scores. This is non-negotiable per the constitution.
61
+
62
+ DIMENSION WEIGHT RATIONALE (for auditors): layout (0.18) and typography (0.15) carry the highest weights because they govern the gestalt impression formed in the first 50 to 200 milliseconds of viewing, before any interaction occurs. Motion (0.14) and interaction (0.14) share the second tier because motion is a multiplier on every other dimension and interaction quality is entirely under the generator's control with no external asset dependency. Color (0.13) is weighted below motion because a strong spatial and typographic foundation partially compensates for a mediocre palette, whereas the reverse is less true. Spacing/composition (0.11) is weighted below layout because it largely overlaps with layout discipline and its marginal contribution is captured there. Accessibility (0.10) is weighted conservatively because its primary governance instrument is the hard cap (not the weighted score): a failing accessibility score triggers the most severe overall cap (70), making its weight multiplier secondary to its gate function. Performance (0.05) carries the lowest weight because slow but structurally sound UIs can be corrected in a targeted loop and their defects are less immediately visible in static screenshots than the other dimensions.
63
+ weak_dimension_cap:
64
+ threshold: 60
65
+ cap: 74
66
+ accessibility_override:
67
+ threshold: 60
68
+ cap: 70
69
+ rule: "If any single dimension scores below 60, the overall QA-Visual score is capped at 74 regardless of other dimension scores. A score of 74 guarantees the output cannot reach the 90 APPROVED threshold on the strength of other dimensions alone, forcing a targeted correction loop. The threshold is set at 60 (not 70) to permit outputs that have a genuine weakness in one area but are strong everywhere else to still score in the high 80s, while ensuring catastrophically broken dimensions (broken interaction states, inaccessible DOM, complete motion failure) block approval. The accessibility dimension has an additional hard override: if accessibility scores below 60, the overall cap is further tightened to 70, because inaccessible UIs are structurally incomplete products, not polish gaps, per the framework constitution. MOTION ABSENCE EXEMPTION: when the resolved animation stack is none or css (a deliberately static or CSS-only design), the motion dimension is scored at a 70 baseline as CANNOT-ASSESS rather than from measured failures, its premium signals (choreography, stagger, cubic-bezier vocabulary) are excluded, and this weak-dimension cap is NOT applied on the strength of the motion dimension alone. A static site with no animation is a valid design choice, not a broken dimension."
70
+ dimensions:
71
+ - key: layout
72
+ title: Layout, Grid, Hierarchy, and Whitespace Rhythm
73
+ weight: 0.18
74
+ premium:
75
+ - "A single, coherent spatial system governs all margins, gaps, and padding: every distance is a recognizable multiple of a base unit (4px or 8px), with no arbitrary pixel values outside the system."
76
+ - Visual hierarchy is established through deliberate variation in size, weight, and position; the eye moves through the page in a predictable, intentional sequence without relying on borders, shadows, or decorative dividers as the primary separators.
77
+ - "Whitespace is deployed asymmetrically and purposefully: generous breathing room around primary content, tighter grouping between related elements, creating clear gestalt clusters without explicit containers."
78
+ - Sections have distinct spatial personalities (dense, airy, full-bleed, inset) that alternate to sustain attention and reflect content importance, rather than repeating a single section template at uniform height.
79
+ - At least one element intentionally breaks the primary content container width (a full-bleed image, an offset composition, an asymmetric grid span), signaling deliberate art direction rather than template reuse.
80
+ - "Above-the-fold layout communicates a clear information priority within 2 seconds: one undisputed primary message, one secondary message, one action, with no competing focal points at equal visual weight."
81
+ slop:
82
+ - Equal padding applied uniformly to every section (e.g., 80px top and bottom everywhere), producing a flat, metronomic rhythm with no spatial drama or variation.
83
+ - Every section shares the same container max-width with no breaks, bleeds, or offsets, creating a claustrophobic tube-like layout that signals template reuse.
84
+ - "Hero section is simply a centered title plus subtitle plus button with perfect bilateral symmetry and no compositional interest: indistinguishable from the default output of any AI generation tool."
85
+ - Cards forced to identical heights via CSS regardless of content length, or arranged in a 3-column grid with no column-span variation, producing artificial mechanical uniformity.
86
+ - Decorative horizontal rules or gradient divider lines used to separate sections instead of whitespace, revealing that the spacing system was not trusted.
87
+ - Spacing values that are multiples of 10 or 5 but do not form a coherent scale (e.g., 10px, 25px, 60px, 100px mixed), exposing ad-hoc placement rather than a defined system.
88
+ - Content stacked in a single center-aligned column with wide empty margins throughout the entire page, never using the full canvas for visual interest or composition.
89
+ - Z-axis depth (shadows, elevation) applied at identical intensity to every card, button, and container, erasing hierarchy rather than supporting it.
90
+ measurable:
91
+ - "Spacing system consistency: extract all computed margin, padding, and gap values across major layout elements; at least 85% of values must be multiples of 4px or 8px; flag any value not in a recognized scale."
92
+ - "Section height variance: compute the pixel heights of all top-level sections; the coefficient of variation must exceed 0.25, confirming sections differ meaningfully in vertical footprint rather than repeating a template height."
93
+ - "Max-width breaks: detect whether any element has a wider computed width than the primary content container; at least one element should exceed the main container width to signal intentional layout composition."
94
+ - "Grid column utilization: for each CSS Grid or Flexbox row, measure how many columns are spanned by each child; flag layouts where every sibling spans exactly the same column count with no exceptions across the entire page."
95
+ - "Above-fold element count: count interactive elements (buttons, links with role=button, inputs) visible in the 1440x900 viewport without scrolling; optimal range is 1 to 3; more than 5 is flagged as hierarchy failure."
96
+ - "Spacing uniqueness ratio: count distinct spacing values used in computed styles across all block-level elements; fewer than 6 unique values for a full-page layout signals an over-rigid system; more than 20 signals no system at all."
97
+ judged:
98
+ - Does the page feel composed or assembled? A composed layout has sections that feel like deliberate choices in relation to each other; an assembled layout feels like sections were dropped in sequence without reference to the whole.
99
+ - Is there a clear reading path? Identify the primary focal point, the secondary focal point, and the tertiary, then judge whether a naive viewer would follow that same path naturally.
100
+ - Does whitespace feel earned or accidental? Generous gaps that frame content correctly feel intentional; identical gaps that appear everywhere feel like a default margin was never overridden.
101
+ - Does any section break the expected pattern in a way that increases quality? A full-bleed image or asymmetric composition that works signals craft; a pattern break that creates visual noise signals slop.
102
+ - Does the layout work at a squint? Blur judgment to see only masses of light and dark; a premium layout has a clear tonal map with dominant, secondary, and accent zones; a slop layout is a uniform grey field.
103
+ - key: typography
104
+ title: Type Scale, Hierarchy, Rhythm, and Expressiveness
105
+ weight: 0.15
106
+ premium:
107
+ - "A modular type scale is in use: font sizes follow a consistent ratio (1.25, 1.333, or 1.5) rather than being arbitrary round numbers; every heading and body size maps to a named step in the scale with no orphan sizes."
108
+ - Display headings use tighter tracking (negative letter-spacing) at large sizes, reflecting the optical reality that large glyphs require less inter-character space; body text uses neutral or slightly positive tracking appropriate to its size.
109
+ - "Line-height is differentiated by role: display headings use 1.0 to 1.2, subheadings 1.2 to 1.4, body text 1.5 to 1.7, captions 1.4 to 1.6; a single line-height value applied globally is a direct fail signal."
110
+ - Line measure for sustained reading text falls between 55 and 75 characters; the layout constrains column widths to enforce this rather than allowing text to span full-width on large viewports.
111
+ - "Weight contrast between heading and body levels is substantive: the ratio of font-weight values between the heaviest heading and the lightest body level spans at least 2 steps (e.g., 700 vs 400), not a single-step nudge."
112
+ - "Paragraph spacing is proportional to line-height: typically 0.75 to 1.25 times the line-height value, creating paragraph rhythm that breathes without creating disconnected islands of text."
113
+ slop:
114
+ - Font sizes defined in a round-number sequence (12, 14, 16, 20, 24, 32, 48px) with no underlying ratio, revealing sizes were chosen by convention rather than a system.
115
+ - A single font-family used for all roles with differentiation achieved only through size and weight, producing a visually flat typographic palette.
116
+ - Body text set in a container wider than 80 characters at the primary breakpoint, forcing the reader's eye to travel a fatiguing horizontal distance per line.
117
+ - Uniform line-height of 1.5 applied to all text elements from display headings to captions, signaling the default was accepted without typographic consideration.
118
+ - Large display headings with positive or zero letter-spacing, producing a loose, airy appearance at display size that reads as unfinished or template-generated.
119
+ - Heading hierarchy that relies on color (a lighter grey versus a darker grey) rather than size and weight, producing a hierarchy that collapses when color is removed.
120
+ - Font size differences between adjacent heading levels smaller than 4px at any breakpoint, creating a hierarchy that is technically present but visually imperceptible.
121
+ measurable:
122
+ - "Scale ratio detection: extract all distinct computed font-size values in px; compute pairwise ratios between consecutive sizes when sorted; at least 70% of adjacent-step ratios must fall within 0.05 of a common ratio (1.25, 1.333, 1.414, or 1.5); flag if no consistent ratio is detectable."
123
+ - "Letter-spacing audit: for each element with font-size >= 32px, assert computed letter-spacing is negative (less than -0.01em); for elements with font-size < 18px, assert letter-spacing is >= 0em; flag inversions."
124
+ - "Line-height by role: query computed line-height for h1 and h2 elements (assert 1.0 to 1.25 times font-size); p elements (assert 1.5 to 1.75); small and caption elements (assert 1.3 to 1.6); flag any role outside its range."
125
+ - "Measure constraint: for each p or article element containing more than 20 words, measure computed width and divide by approximate character width (font-size times 0.5); flag if character count per line exceeds 80 or falls below 45."
126
+ - "Weight contrast ratio: extract all distinct font-weight values used on the page; assert the range (max minus min) is at least 300 weight units; flag if all weights fall within a 200-unit band."
127
+ - "Body text size floor: assert no p or li element containing more than 10 words has a computed font-size below 15px on 1440px viewport or below 14px on 375px viewport."
128
+ judged:
129
+ - Does the type feel purposefully selected or defaulted into? Assess whether any font pairing creates meaningful contrast in personality, weight, and optical size, or reads as a system default left in place.
130
+ - Does the heading hierarchy communicate priority without requiring the viewer to read the words? Can the structural outline of the page be inferred from typographic treatment alone in under 5 seconds of scanning?
131
+ - Is the body text comfortable to read for a sustained paragraph? Judge tracking, leading, measure, and size holistically, not as isolated metrics.
132
+ - Does the type scale create visual music when the page is viewed as a whole? A premium type system produces a felt cadence of large, medium, small, large, medium as the viewer scrolls; a slop system produces a monotone band interrupted by arbitrarily large headings.
133
+ - Are there any runts (single short words on a final line of a heading) or widows (single line of a paragraph at the top of a column) visible at any of the three tested breakpoints?
134
+ - key: motion
135
+ title: "Motion Quality: Easing, Duration Discipline, and Choreography"
136
+ weight: 0.14
137
+ premium:
138
+ - "Every UI transition uses a cubic-bezier curve appropriate to its physical metaphor: elements entering use a deceleration curve, elements leaving use an acceleration curve, and elements repositioning in place use a symmetric curve; keyword values without cubic-bezier overrides are treated as a fail signal."
139
+ - "Duration follows a strict scale tied to element size and distance: micro-interactions (hover color, focus ring) use 80 to 150ms; component state changes (drawer open, modal appear) use 200 to 350ms; page-level transitions do not exceed 500ms."
140
+ - Choreographed sequences stagger child element entrances by a fixed interval (30 to 60ms per item) so the eye follows a reading-order path rather than seeing everything appear simultaneously.
141
+ - Scroll-triggered animations use an intersection threshold of 0.15 to 0.25, so elements begin animating when meaningfully visible, not at the pixel they enter the viewport or after the user has already scrolled past them.
142
+ - "The prefers-reduced-motion media query is honored at the CSS layer: all transitions and animations inside the reduced-motion block collapse to instant or to opacity-only fades at 150ms maximum."
143
+ - Transform and opacity are the only animated properties in the critical path; no animation touches layout-triggering properties (width, height, top, left, margin, padding) except in explicitly justified cases with will-change declared.
144
+ slop:
145
+ - "transition: all 0.3s ease applied at a wildcard or component-root level, causing every property change to animate and producing jank on layout changes."
146
+ - All transitions use the keyword ease or linear with no cubic-bezier customization, producing a uniform, mechanical feel across every interaction regardless of physical metaphor.
147
+ - "Duration is identical across all elements regardless of size or importance: micro-interactions and full-page modals both animate at 300ms with the same curve."
148
+ - Scroll animations trigger at threshold 0 (fires before the user scrolls, animation is complete on arrival) or at threshold 1.0 (never fires on tall elements), breaking the natural reveal pattern.
149
+ - "No prefers-reduced-motion handling exists: users who have requested reduced motion receive the full animation suite including parallax, auto-playing sequences, and staggered entrances."
150
+ - "Animation choreography is absent: all elements in a list, card grid, or navigation enter simultaneously with no stagger, creating a visual flash rather than a guided sequence."
151
+ - "will-change: transform declared on every animated element globally rather than applied and removed dynamically, consuming GPU memory and causing compositing layer explosion on large DOMs."
152
+ measurable:
153
+ - "Cubic-bezier audit: query all computed transition-timing-function values across interactive and animated elements; assert fewer than 20% use the keyword values ease, linear, ease-in, or ease-out without a corresponding cubic-bezier override; flag if the proportion exceeds 20%."
154
+ - "Duration scale check: extract all transition-duration and animation-duration values; assert micro-interaction elements have duration <= 150ms and no page-level transition exceeds 500ms; flag outliers."
155
+ - "transition:all detection: search all stylesheets for transition: all or transition-property: all; flag every occurrence as a blocking finding."
156
+ - "Reduced-motion compliance: emulate prefers-reduced-motion:reduce; assert all transition-duration values collapse to <= 150ms or animation-play-state becomes paused for non-opacity animations; fail if any transform animation persists above 150ms."
157
+ - "Layout property animation detection: scan all @keyframes and transition-property declarations for width, height, top, left, right, bottom, margin, or padding; flag any found unless will-change is also declared on the same selector."
158
+ - "Stagger detection in lists: for repeated sibling elements with animation-delay or transition-delay, assert delay increments by 20 to 80ms per sibling; flag if all siblings share identical delay."
159
+ judged:
160
+ - "Screen recording or frame-by-frame screenshot of a modal or drawer open animation: the motion curve must be visibly decelerating (fast start, soft landing), not linear or symmetrical."
161
+ - "Staggered list entrance playback: items must enter in reading order with a perceptible but not distracting delay between each, guiding the eye rather than overwhelming it."
162
+ - "Scroll-reveal timing observation: elements should begin their reveal animation when roughly one quarter of the element is visible, not at the viewport edge and not after full visibility."
163
+ - "Reduced-motion mode screenshot: the interface must remain fully functional and visually complete with no missing states or broken layouts caused by stripped animations."
164
+ - "Overall motion coherence: playing through all interactive transitions in sequence, the motion vocabulary must feel unified, as if all animations were designed to the same physical ruleset rather than assembled from separate snippets."
165
+ - "No transition jank observation: rapid interaction (clicking buttons quickly, scrolling aggressively, toggling states) must not produce stuck intermediate animation states, flicker, or layout shift visible in the screenshot."
166
+ - key: interaction
167
+ title: "Interaction Quality: Affordances, States, and Feedback"
168
+ weight: 0.14
169
+ premium:
170
+ - "Every interactive element has four visually distinct states: resting, hover, active/pressed, and disabled; each state is immediately recognizable without needing to interact multiple times to notice the change."
171
+ - Hover transitions shift exactly one perceptible property (background, border, shadow, or transform) rather than several simultaneously, so the eye tracks a single clear signal.
172
+ - Focus rings use a high-contrast outline offset from the element boundary, with at least 3px offset and 2px ring width, so keyboard-navigating users receive the same spatial clarity as pointer users.
173
+ - Active/pressed states apply a subtle downward transform (translateY 1 to 2px) or shadow compression to simulate physical depression, giving tactile feedback through the screen.
174
+ - Form inputs display a colored left-border or underline accent on focus that matches the primary color token, distinguishing focused fields from adjacent text without relying only on the browser default outline.
175
+ - "Cursor changes are semantically correct throughout: pointer on clickable elements, text on editable content, grab/grabbing on draggable regions, zoom-in on expandable media."
176
+ slop:
177
+ - Hover state is identical to resting state or only changes opacity to 0.8, giving no spatial or color signal that the element is interactive.
178
+ - Focus ring is the raw browser default outline with no offset, appearing cropped by border-radius or overflow:hidden, causing it to disappear on rounded elements.
179
+ - "Active state is missing entirely: pressing a button produces no visual compression or color shift, so the click feels unconfirmed."
180
+ - Disabled inputs retain full opacity and no cursor change, making them visually identical to enabled inputs.
181
+ - "All interactive elements share one generic :hover { opacity: 0.7 } rule applied via a wildcard selector, producing a flat, undifferentiated interaction layer."
182
+ - Form validation errors appear only as red border-color changes with no text explanation, failing users who cannot distinguish the color shift.
183
+ - Focus states exist on some elements but are absent or suppressed on custom-styled components (dropdowns, toggles, sliders), creating an inconsistent keyboard navigation experience.
184
+ measurable:
185
+ - "Computed style check: every element with role=button or tag=a must have a :hover rule that changes at least one property from the set {background-color, color, box-shadow, transform, border-color}; flag any that change none."
186
+ - "Focus ring audit: query all focusable elements via Tab key sequence; for each, confirm outline-width >= 2px and outline-offset >= 2px; fail if either is 0px or if outline-style is none and no box-shadow focus substitute exists."
187
+ - "Active state presence: Playwright mousedown (without mouseup) on each primary button; assert computed background-color or transform differs from hover state; flag if identical."
188
+ - "Disabled opacity range: query all [disabled] or [aria-disabled=true] elements; assert computed opacity is between 0.35 and 0.55; flag values outside this range."
189
+ - "Cursor correctness: for all anchor and button elements, assert computed cursor === pointer; for all [disabled] elements, assert computed cursor === not-allowed."
190
+ - "Form error completeness: trigger validation on empty required fields; assert presence of role=alert or aria-live region AND visible helper text element AND color change on the input border; flag if only one signal is present."
191
+ - "State count coverage: for each unique interactive component type detected (button, input, select, checkbox, toggle), assert at least 3 of 4 states (resting, hover, focus, active) produce a distinct computed style snapshot."
192
+ judged:
193
+ - "Screenshot comparison of resting vs hover states for the primary call-to-action: the change must be perceptible within 200ms and feel deliberate, not accidental."
194
+ - "Keyboard navigation walkthrough screenshot sequence: focus ring must be visible on every focused element without clipping, color inversion, or disappearance."
195
+ - Pressed/active button screenshot must show visible compression (shadow reduction or downward shift) that communicates a physical response.
196
+ - "Error state screenshot: the affected field must communicate failure through at least two independent visual channels simultaneously visible in the screenshot."
197
+ - "Overall interaction layer coherence: hover states across all interactive element types should feel like they belong to the same visual language, not a collection of ad-hoc overrides."
198
+ - key: color
199
+ title: "Color: Palette Discipline and Semantic Token Integrity"
200
+ weight: 0.13
201
+ premium:
202
+ - "Palette built from a documented semantic layer: every color value traces to a named intent token with no raw hex literals scattered across component styles."
203
+ - Neutral scale spans at least 9 steps with perceptually even luminance intervals; the darkest and lightest neutrals are used for body text and page background respectively, not arbitrary stops.
204
+ - Accent color is used in at most two distinct UI roles (e.g., primary action plus highlight mark), never as decoration or fill on non-interactive surfaces.
205
+ - Dark and light modes share the same semantic tokens but remap to different primitive values; swapping the color scheme does not produce jarring hue or saturation shifts because the mapping was designed for both contexts simultaneously.
206
+ - Gradients, when present, are constructed from two adjacent stops on the same hue axis or two complementary hues no more than 60 degrees apart on the wheel, producing smooth transitions without muddy midpoints or neon banding.
207
+ - Inter-surface contrast is calibrated so layered backgrounds (page, card, popover) step in consistent luminance increments of at least 8 points on a 0 to 100 scale, creating clear visual depth without resorting to heavy borders.
208
+ slop:
209
+ - Raw hex literals appear inline in component styles or hardcoded in class strings, bypassing the token layer entirely.
210
+ - Primary brand color is an unmodified saturated blue that could belong to any productivity software product with no differentiation from the category default.
211
+ - Gradients span more than 90 degrees of hue rotation (rainbow effect) or include a pure white or pure black stop that creates an abrupt lightness cliff at one end.
212
+ - "Dark mode implemented by inverting lightness universally (filter: invert or brightness flips) rather than remapping semantic tokens to dark-appropriate primitives."
213
+ - Accent color appears on decorative backgrounds, section dividers, icon containers, and interactive buttons simultaneously, destroying its signaling function.
214
+ - Semantic colors (success, warning, error) are missing entirely or mapped directly to default browser green, yellow, and red without any palette adaptation.
215
+ - Color choices change arbitrarily between sections with no shared token source, producing an incoherent patchwork rather than a unified palette.
216
+ measurable:
217
+ - "Count CSS custom properties whose names begin with --color-: a premium output declares at least 20 semantic color tokens; fewer than 12 is a fail signal."
218
+ - Scan all computed background-color and color values on rendered elements; if more than 15% of unique color values do not correspond to a declared CSS custom property, flag raw-hex proliferation.
219
+ - For every visible text node, compute contrast ratio against its background using the WCAG relative luminance formula; zero violations below 4.5:1 (body text) and 3:1 (large text) required for APPROVED.
220
+ - Count distinct hue families (grouped within 30-degree arcs) in the top-20 most-used color values; a premium palette uses 1 to 3 hue families; more than 5 families signals palette sprawl.
221
+ - "In dark mode (prefers-color-scheme: dark), re-compute element color values and compare to light-mode values; if more than 30% of semantic token computations are identical (no remapping), flag missing dark-mode token mapping."
222
+ - "Count the number of UI roles where the accent token appears: buttons, links, decorative fills, icon containers, section backgrounds; more than 3 distinct role types flags accent overuse."
223
+ judged:
224
+ - Does the palette read as intentional and industry-appropriate, or does it feel like a default color scheme pulled from a utility CSS framework example page?
225
+ - Is there a clear visual hierarchy between page background, surface, raised surface, and overlay layers, or does everything feel flat at the same luminance level?
226
+ - Do gradients feel refined and directional, or garish and decorative? A premium gradient is almost invisible at first glance and reveals itself on close inspection.
227
+ - Does the dark mode feel like it was designed for darkness, with warm or cool-tinted neutrals that feel restful, or does it look like a crude inversion with harsh pure-black backgrounds?
228
+ - Is the accent color used with restraint so that interactive elements immediately pop, or is it sprinkled everywhere so it stops drawing the eye?
229
+ - key: spacing_composition
230
+ title: "Spacing and Composition: Scale Consistency, Density, and Optical Harmony"
231
+ weight: 0.11
232
+ premium:
233
+ - All spacing values resolve to a declared scale with a consistent base unit (4px or 8px); no arbitrary pixel values appear in margin, padding, or gap properties outside of single-pixel border offsets.
234
+ - "Section vertical rhythm follows a geometric or rational progression: consecutive sections alternate between at least two distinct vertical padding tiers (e.g., compact: 64px, standard: 96px, generous: 144px) rather than every section having the same padding."
235
+ - "Negative space is used deliberately: hero sections and feature callouts have substantially more breathing room than content-dense sections, creating intentional pacing as the user scrolls."
236
+ - The ratio between the densest and most spacious sections on the same page is at least 2:1, creating genuine rhythm without either feeling cramped or wastefully empty.
237
+ - Component-level spacing uses component-specific tokens that reference the base scale, so components maintain internal proportion across different viewport sizes.
238
+ - Max content width is constrained to prevent line lengths from becoming unreadable on ultra-wide viewports, with full-bleed moments clearly marked as intentional art direction rather than absent containment.
239
+ slop:
240
+ - Section padding is a single uniform value on every section, producing a monotonous drumbeat with no rhythm variation between sections of different visual weight.
241
+ - Spacing values include arbitrary pixel offsets (23px, 37px, 15px) that do not correspond to any declared scale, indicating properties were tuned by eye or left at browser defaults.
242
+ - The gap between a heading and its descriptive paragraph is identical to the gap between two sibling sections, collapsing the hierarchy of proximity and making it unclear which elements belong together.
243
+ - Cards and list items have inconsistent internal padding with no token governing the value, revealing ad-hoc construction.
244
+ - On mobile, padding is simply halved from desktop values rather than reconsidered for the viewport, causing either cramped content or excessive vertical scroll before substance.
245
+ - "Empty sections or placeholder areas appear with no deliberate negative space purpose: white space exists because content is missing, not because it was composed."
246
+ measurable:
247
+ - Extract all computed padding and margin values from section-level elements; bucket into declared-scale values vs off-scale values; a premium output has at least 90% of spacing values matching scale stops; below 75% is a fail signal.
248
+ - Identify maximum and minimum vertical padding values on top-level sections; compute the ratio (max divided by min); a ratio below 1.5 flags rhythm monotony; a ratio above 4 may flag inconsistency.
249
+ - Compute the computed width of the primary content container as a percentage of the viewport width at 1280px; values above 95% flag full-bleed content layouts lacking column constraint.
250
+ - For every card component detected (elements with a computed background-color different from the page background and a computed border-radius greater than 0), extract internal padding values; if the standard deviation of padding-top values across all cards exceeds 8px, flag inconsistent card density.
251
+ - At 375px viewport width, extract the smallest computed horizontal padding on any full-width content block; values below 16px flag edge-to-edge content collision.
252
+ - Count unique spacing values (margin, padding, gap) that do not resolve to a CSS custom property; more than 8 unique off-token values is a fail signal.
253
+ judged:
254
+ - Does scrolling through the page feel like moving through a composed sequence of sections with intentional breathing, or does it feel like a stack of identically spaced cards with no pacing?
255
+ - "Does the hierarchy of proximity work: are related elements visually grouped, and is the space between groups larger than the space within them?"
256
+ - On mobile, does the layout feel deliberately reconfigured for a narrow viewport, or does it feel like a shrunk-down desktop layout that happens to fit?
257
+ - "Does the page have a sense of visual weight distribution: heavy moments balanced by lighter moments, dense sections contrasting with generous ones?"
258
+ - key: accessibility
259
+ title: "Accessibility: Structural Integrity and Inclusive Interaction"
260
+ weight: 0.1
261
+ premium:
262
+ - Every interactive element is reachable by Tab in a logical, document-order sequence that matches the visual reading flow; no focus traps outside modal dialogs; no skip-link omissions on pages with repeated navigation blocks.
263
+ - "All text meets WCAG 2.1 AA contrast at rendered size: body text >= 4.5:1 against its computed background; large text (>= 18pt or >= 14pt bold) >= 3:1; UI component boundaries and input borders >= 3:1 against adjacent surface."
264
+ - "Semantic HTML is load-bearing, not decorative: headings form a strict h1-h2-h3 hierarchy (no skipped levels); landmark regions (main, nav, aside, footer) partition the page so a screen reader can jump between areas without scanning all content."
265
+ - Every form input has a programmatically associated label; required fields are marked with both aria-required=true and a visible indicator that does not rely on color alone; validation errors are linked to their input via aria-describedby and announced to screen readers on submission.
266
+ - "All animations and transitions respect prefers-reduced-motion: a CSS block exists, disables or replaces every keyframe animation and transition above 200ms, and the effect is verified by both computed styles and screenshot under the reduced-motion flag."
267
+ - Touch and pointer target sizes meet or exceed 44x44 CSS pixels for all interactive elements; elements that appear smaller visually achieve the target size via padding, so the hit area is large without breaking the layout.
268
+ slop:
269
+ - "Placeholder text used as the only label for inputs: the label disappears on focus, leaving the user with no visible reminder of what the field expects."
270
+ - "Focus ring removed globally with outline: none or :focus { outline: none } in a reset stylesheet, with no replacement indicator; keyboard users lose all positional tracking."
271
+ - "Heading hierarchy that mirrors visual size rather than document structure: multiple h1 elements per page, h3 used because it renders at the desired font size, headings skipped from h2 to h4 to push a section down visually."
272
+ - "Color used as the sole differentiator for state: error fields bordered in red with no icon or text label, required fields marked only with a red asterisk whose meaning is never explained."
273
+ - prefers-reduced-motion block present in CSS but covering only one or two animations while leaving parallax, entrance keyframes, and scroll-triggered motion unaffected; the block exists to pass a lint check, not to protect users.
274
+ - "Interactive elements below 32x32 CSS pixels with no padding compensation: icon-only buttons, social links, and checkbox labels that fail touch targeting on mobile."
275
+ - Click handlers on divs without tabindex=0, role=button, and keyboard event handlers for Enter and Space; modal dialogs that do not trap focus, so Tab escapes into the obscured background.
276
+ measurable:
277
+ - "Contrast audit via computed styles: flag any text element where the luminance ratio is < 4.5:1 for font-size < 18.67px or < 3:1 for font-size >= 18.67px; zero violations required for APPROVED."
278
+ - "Focus order audit: programmatically Tab through all focusable elements; assert DOM order matches visual top-to-bottom, left-to-right order; count out-of-order occurrences; threshold: 0 inversions."
279
+ - "Heading hierarchy integrity: query all h1-h6 elements in DOM order; assert exactly one h1 per page; assert no level jump greater than 1 (e.g., h2 directly followed by h4); threshold: 0 violations."
280
+ - "Landmark coverage: assert presence of exactly one main element; assert at least one nav element; assert footer exists; assert no content-bearing text nodes are direct children of body; threshold: 0 missing landmarks."
281
+ - "prefers-reduced-motion coverage: simulate the preference; assert no element has a computed animation-duration > 0.2s or transition-duration > 0.2s after simulation; threshold: 0 uncovered animations."
282
+ - "Touch target size audit: for every interactive element at 375px viewport, read getBoundingClientRect(); assert width >= 44 and height >= 44; threshold: 0 failures on mobile viewport."
283
+ - "Form label association: for every input and textarea not of type hidden or submit, assert a label with matching for attribute OR aria-label OR aria-labelledby pointing to a visible element; threshold: 0 unassociated inputs."
284
+ judged:
285
+ - "Screenshot under keyboard navigation simulation: is a visible, high-contrast focus ring present on each interactive element as it receives focus? Does the ring surround the element cleanly without clipping?"
286
+ - "Mobile screenshot (375px viewport): do all buttons, links, and form controls appear large enough to tap comfortably? Do adjacent interactive targets crowd each other below approximately 8px spacing?"
287
+ - "Screenshot with forced prefers-reduced-motion: does the page look intentionally designed in its reduced-motion state, or does it look broken with elements frozen mid-animation or empty sections where animated content was expected?"
288
+ - "Error state screenshot on a form with invalid input: is the error communicated through at least two distinct visual channels beyond color (e.g., icon plus text message, border plus inline label)?"
289
+ - "Color-blindness simulation screenshot (deuteranopia filter applied): do all state distinctions (active/inactive, error/success, selected/unselected) remain distinguishable without relying on hue alone?"
290
+ - key: performance
291
+ title: "Performance: Perceptual Speed and Rendering Stability"
292
+ weight: 0.05
293
+ premium:
294
+ - "No measurable layout shift during page load or user interaction: CLS measured via PerformanceObserver is <= 0.05 on first load; images reserve space via explicit width and height attributes or aspect-ratio CSS before image bytes arrive."
295
+ - "Animations exclusively target compositor-promoted properties: every animated property in keyframe blocks and transition declarations is transform or opacity; no animation touches width, height, top, left, margin, or padding."
296
+ - "Scroll performance is stable at 60 frames per second: no long tasks (> 50ms) recorded in the main thread during a 3-second programmatic scroll from top to bottom; no forced synchronous layouts triggered by scroll handlers."
297
+ - "Web fonts load without invisible or jarring text flash: font-display is set to optional or swap with a system fallback stack whose metrics closely match the loaded font; no FOIT lasting > 3000ms."
298
+ - "The critical rendering path is not blocked by synchronous scripts or render-blocking stylesheets: no script tags without defer or async appear in head before the closing tag except for inline critical CSS."
299
+ - Largest Contentful Paint is <= 2500ms on a simulated mid-tier connection; the LCP element is identified and its fetch is either inlined, preloaded, or server-pushed.
300
+ slop:
301
+ - "Layout shift from unresized images: img elements without width and height attributes or a CSS aspect-ratio on a container, causing the page to reflow as each image loads; a CLS score above 0.1 on the first load even when the network is fast."
302
+ - "Animations that trigger layout: CSS transitions on width, height, or max-height to animate open/close of drawers or accordions instead of using transform: scaleY() or clip-path transitions."
303
+ - "All images served at their intrinsic resolution regardless of rendered size: a 4000px wide source image displayed at 400px CSS width with no srcset or picture element, downloading 10x the necessary pixels."
304
+ - "Font loading with font-display: block (the browser default when the property is absent): text is invisible for up to 3 seconds while the font loads, then swaps in abruptly."
305
+ - "Render-blocking scripts in head without defer or async: third-party analytics or chat widgets inserted as synchronous script src before the page content, stalling first paint."
306
+ - "Unbounded stagger delays: a list of 20 cards each staggered 200ms apart means the last card does not animate in until 4 seconds after the section enters the viewport, making the page feel slow and neglected."
307
+ measurable:
308
+ - "Cumulative Layout Shift: inject a PerformanceObserver for layout-shift entries during page load and for 5 seconds after; threshold for APPROVED: CLS <= 0.05; threshold for WARNING: 0.05 < CLS <= 0.1; threshold for ERROR: CLS > 0.1."
309
+ - "Largest Contentful Paint: record last entry's startTime after page stabilizes; threshold for APPROVED: LCP <= 2500ms on an unthrottled local server; flag for WARNING if LCP element is an image without a preload hint."
310
+ - "Animated property audit: query document.getAnimations() after page load; flag any property that is not transform, opacity, filter, or a will-change-promoted custom property; threshold: 0 layout-triggering animated properties."
311
+ - "Long task detection during scroll: inject a PerformanceObserver for longtask entries; programmatically scroll the page from top to bottom over 3 seconds; count long tasks (duration > 50ms); threshold: 0 for APPROVED, >= 3 for ERROR."
312
+ - "Image sizing discipline: for each img element, compare naturalWidth against getBoundingClientRect() width multiplied by window.devicePixelRatio; flag images where intrinsicWidth > renderedCSSWidth times devicePixelRatio times 2.2; threshold: 0 on hero images."
313
+ - "Font-display policy: query all @font-face rules via document.styleSheets; flag each rule with fontDisplay === block or empty string; threshold: 0 block-display font rules."
314
+ - "Script load discipline: query all script elements in head with a src attribute; assert each has either defer, async, or type=module; count synchronous blocking scripts; threshold: 0 blocking scripts before first contentful paint."
315
+ judged:
316
+ - "Slow-network simulation screenshot sequence: do images load progressively (blur-up or skeleton placeholder visible first) or do they pop in all at once after a blank space, or does the layout shift dramatically as each image arrives?"
317
+ - "Scroll experience judgment at normal user speed: do entrance animations trigger smoothly at the expected moment (just as content enters the viewport) or do they fire while elements are still off-screen or only after the user has already scrolled past?"
318
+ - "Font rendering judgment across the load sequence: in a filmstrip of screenshots taken at 200ms intervals from navigation start, is there a visible FOIT (blank text) period longer than 300ms, or a jarring metric shift when the web font replaces the fallback?"
319
+ - "Above-the-fold judgment on a simulated mid-tier device: is the primary content visible and readable within the first 2.5 seconds of the filmstrip, or is the viewport blank or skeleton-only for a noticeably long period?"
320
+ anti_slop_catalog:
321
+ - "Generic saturated blue as the primary brand color with no evidence of deliberate palette selection: the hue communicates no industry affiliation, personality, or differentiation from the default output of any AI generation tool."
322
+ - Every section padded identically top and bottom (most commonly 80px or 5rem), producing a metronomic vertical rhythm with no spatial drama, no dense sections, no generous sections, no alternation of any kind.
323
+ - "Centered hero layout with title, subtitle, and a single primary button, perfectly bilaterally symmetric, with no typographic size contrast between title and subtitle, no compositional interest, no art direction: the single most common AI-generation fingerprint."
324
+ - "transition: all applied at a component root or wildcard selector level, visible in the stylesheet as a single rule covering the entire element: guarantees jank on any class toggle and signals the author did not reason about which properties to animate."
325
+ - "All CSS transitions using the keyword ease or linear with no cubic-bezier customization: every hover, every modal open, every drawer, every reveal uses the same mechanical timing curve with no physical metaphor."
326
+ - "Three-column card grid where every card has forced equal height via CSS, every card has the same internal padding, and the cards are the same width with no spans or offsets: the grid reads as a spreadsheet, not a composition."
327
+ - "Font sizes chosen as round integers (12, 14, 16, 20, 24, 32, 48, 64px) with no underlying ratio connecting them: sizes were chosen by convention or by copying an existing scale, not derived from a modular system."
328
+ - "outline: none or :focus { outline: none } present in a global reset or base stylesheet with no replacement focus indicator anywhere on the page: keyboard users are completely invisible from the moment focus moves."
329
+ - Heading hierarchy that skips levels (h1 to h3, h2 to h4) or uses multiple h1 elements per page because heading tags were chosen for their default visual size rather than document structure.
330
+ - "All list items, all cards, and all navigation items entering the viewport simultaneously with no stagger, no sequencing, no choreography: elements flash in as a single visual event rather than guiding the eye."
331
+ - Placeholder text used as the sole accessible label for form inputs, disappearing on focus and leaving the user with no visible reminder of the field's purpose.
332
+ - "Hardcoded hex values scattered across component CSS or className strings, bypassing a token layer entirely: the palette cannot be coherently updated because colors are not referenced symbolically."
333
+ - Decorative horizontal rules, gradient separator lines, or thick border dividers between every section, substituting for a spacing system that the author did not trust.
334
+ - All images served at their intrinsic (source) resolution regardless of their rendered size, with no srcset, no picture element, and no width/height attributes causing cumulative layout shift on load.
335
+ - "Dark mode implemented by applying filter: invert or a CSS variable swap that simply flips lightness values universally, producing washed-out colors, incorrect saturation, and harsh pure-black backgrounds instead of a designed dark palette."
@@ -1,5 +1,5 @@
1
1
  # Constitution Domain — Extracted governance rules for PRISM L0
2
- # Source: chati.dev/constitution.md (23 Articles + Preamble)
2
+ # Source: chati.dev/constitution.md (25 Articles + Preamble)
3
3
 
4
4
  summary: >
5
5
  Constitution governance: self-validation required (loop until tier-specific threshold met),
@@ -9,7 +9,7 @@ summary: >
9
9
  context brackets, memory governance, registry governance,
10
10
  session lock, model governance.
11
11
 
12
- articleCount: 22
12
+ articleCount: 25
13
13
 
14
14
  rules:
15
15
  - id: art-i
@@ -77,7 +77,7 @@ rules:
77
77
  priority: high
78
78
 
79
79
  - id: art-xvii
80
- text: "Execution mode governance: QA gates >= 95%, other agents >= 90%. Safety net with circuit breaker (3 failures)."
80
+ text: "Execution mode governance: QA gates >= 95%, other agents >= 90%. Safety net escalates to human once the correction-cycle limit is exceeded at the same gate."
81
81
  priority: critical
82
82
 
83
83
  articles:
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * json-validate executor - parse JSON files and report any that are malformed.
4
+ * A deterministic, zero-LLM well-formedness check (valid JSON, not schema
5
+ * conformance). Reads a JSON request on stdin and writes a JSON result on
6
+ * stdout (the executor contract; see executors/registry.yaml).
7
+ *
8
+ * arg is a comma-separated list of project-relative .json paths. Paths are
9
+ * contained to projectDir, and parse failures report only "not valid JSON"
10
+ * (never the parser message), so the executor cannot read or leak file content
11
+ * outside the project.
12
+ */
13
+ import { existsSync, readFileSync } from 'fs';
14
+ import { resolve, sep } from 'path';
15
+
16
+ function readStdin() {
17
+ return new Promise((resolve) => {
18
+ let data = '';
19
+ process.stdin.setEncoding('utf-8');
20
+ process.stdin.on('data', (c) => { data += c; });
21
+ process.stdin.on('end', () => resolve(data));
22
+ });
23
+ }
24
+
25
+ /** True when `p`, resolved against the project root, stays inside the project. */
26
+ function within(root, p) {
27
+ const full = resolve(root, p);
28
+ return full === root || full.startsWith(root + sep);
29
+ }
30
+
31
+ async function main() {
32
+ let input;
33
+ try {
34
+ input = JSON.parse((await readStdin()) || '{}');
35
+ } catch {
36
+ process.stdout.write(JSON.stringify({ status: 'error', summary: 'json-validate: request was not valid JSON.' }));
37
+ return;
38
+ }
39
+
40
+ const root = resolve(input.projectDir || process.cwd());
41
+ const files = String(input.arg || '').split(',').map((p) => p.trim()).filter(Boolean);
42
+
43
+ if (files.length === 0) {
44
+ process.stdout.write(JSON.stringify({ status: 'error', summary: 'json-validate: no files given (arg is a comma-separated list).' }));
45
+ return;
46
+ }
47
+
48
+ const escaping = files.filter((f) => !within(root, f));
49
+ if (escaping.length > 0) {
50
+ process.stdout.write(JSON.stringify({
51
+ status: 'error',
52
+ summary: `json-validate: ${escaping.length} path(s) are outside the project directory and were refused.`,
53
+ details: { refused: escaping },
54
+ }));
55
+ return;
56
+ }
57
+
58
+ const errors = [];
59
+ for (const f of files) {
60
+ const full = resolve(root, f);
61
+ if (!existsSync(full)) { errors.push({ file: f, error: 'not found' }); continue; }
62
+ try {
63
+ JSON.parse(readFileSync(full, 'utf-8'));
64
+ } catch {
65
+ // Report only that the file is not valid JSON. The parser message can echo
66
+ // file content, so it is never forwarded.
67
+ errors.push({ file: f, error: 'not valid JSON' });
68
+ }
69
+ }
70
+
71
+ process.stdout.write(JSON.stringify({
72
+ status: errors.length === 0 ? 'passed' : 'failed',
73
+ summary: errors.length === 0
74
+ ? `All ${files.length} JSON file(s) are well-formed.`
75
+ : `${errors.length} of ${files.length} file(s) failed to parse.`,
76
+ details: { errors },
77
+ }));
78
+ }
79
+
80
+ main();
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * npm-script executor - run a whitelisted project npm script as a deterministic,
4
+ * zero-LLM task. Reads a JSON request on stdin and writes a JSON result on
5
+ * stdout (the executor contract; see executors/registry.yaml).
6
+ *
7
+ * Only a fixed set of terminating script names is allowed, so a script task can
8
+ * never run an arbitrary or long-running command: a task declares execution_ref
9
+ * "npm-script:test" and this runs `npm run test` and reports pass or fail from
10
+ * the exit code.
11
+ */
12
+ import { spawn } from 'child_process';
13
+
14
+ const ALLOWED = new Set(['lint', 'test', 'typecheck', 'build', 'format']);
15
+
16
+ function readStdin() {
17
+ return new Promise((resolve) => {
18
+ let data = '';
19
+ process.stdin.setEncoding('utf-8');
20
+ process.stdin.on('data', (c) => { data += c; });
21
+ process.stdin.on('end', () => resolve(data));
22
+ });
23
+ }
24
+
25
+ async function main() {
26
+ // Emit exactly once: spawn can fire both 'error' and 'close', and emitting
27
+ // twice would put two JSON objects on stdout and break the runner's parse.
28
+ let settled = false;
29
+ const emit = (result) => {
30
+ if (settled) return;
31
+ settled = true;
32
+ process.stdout.write(JSON.stringify(result));
33
+ };
34
+
35
+ let input;
36
+ try {
37
+ input = JSON.parse((await readStdin()) || '{}');
38
+ } catch {
39
+ emit({ status: 'error', summary: 'npm-script: request was not valid JSON.' });
40
+ return;
41
+ }
42
+
43
+ const projectDir = input.projectDir || process.cwd();
44
+ const script = String(input.arg || '').trim();
45
+
46
+ if (!ALLOWED.has(script)) {
47
+ emit({
48
+ status: 'error',
49
+ summary: `npm-script: "${script || '(none)'}" is not an allowed script. Allowed: ${[...ALLOWED].join(', ')}.`,
50
+ });
51
+ return;
52
+ }
53
+
54
+ const child = spawn('npm', ['run', script], { cwd: projectDir, shell: false });
55
+ let out = '';
56
+ let err = '';
57
+ child.stdout.on('data', (c) => { out += c; });
58
+ child.stderr.on('data', (c) => { err += c; });
59
+ child.on('error', (e) => {
60
+ emit({ status: 'error', summary: `npm-script: could not spawn npm (${e.message}).` });
61
+ });
62
+ child.on('close', (code) => {
63
+ const tail = (out + err).split('\n').filter(Boolean).slice(-20).join('\n');
64
+ emit({
65
+ status: code === 0 ? 'passed' : 'failed',
66
+ summary: `npm run ${script} exited ${code}.`,
67
+ details: { exitCode: code, tail },
68
+ });
69
+ });
70
+ }
71
+
72
+ main();
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * path-check executor - assert that required paths exist. A deterministic,
4
+ * zero-LLM verification gate (for example "the build output and the generated
5
+ * client both exist"). Reads a JSON request on stdin and writes a JSON result
6
+ * on stdout (the executor contract; see executors/registry.yaml).
7
+ *
8
+ * arg is a comma-separated list of project-relative paths. Paths are contained
9
+ * to projectDir: an absolute path or a "../" sequence that escapes the project
10
+ * is rejected, so the executor cannot be used to probe the wider filesystem.
11
+ */
12
+ import { existsSync } from 'fs';
13
+ import { resolve, sep } from 'path';
14
+
15
+ function readStdin() {
16
+ return new Promise((resolve) => {
17
+ let data = '';
18
+ process.stdin.setEncoding('utf-8');
19
+ process.stdin.on('data', (c) => { data += c; });
20
+ process.stdin.on('end', () => resolve(data));
21
+ });
22
+ }
23
+
24
+ /** True when `p`, resolved against the project root, stays inside the project. */
25
+ function within(root, p) {
26
+ const full = resolve(root, p);
27
+ return full === root || full.startsWith(root + sep);
28
+ }
29
+
30
+ async function main() {
31
+ let input;
32
+ try {
33
+ input = JSON.parse((await readStdin()) || '{}');
34
+ } catch {
35
+ process.stdout.write(JSON.stringify({ status: 'error', summary: 'path-check: request was not valid JSON.' }));
36
+ return;
37
+ }
38
+
39
+ const root = resolve(input.projectDir || process.cwd());
40
+ const paths = String(input.arg || '').split(',').map((p) => p.trim()).filter(Boolean);
41
+
42
+ if (paths.length === 0) {
43
+ process.stdout.write(JSON.stringify({ status: 'error', summary: 'path-check: no paths given (arg is a comma-separated list).' }));
44
+ return;
45
+ }
46
+
47
+ const escaping = paths.filter((p) => !within(root, p));
48
+ if (escaping.length > 0) {
49
+ process.stdout.write(JSON.stringify({
50
+ status: 'error',
51
+ summary: `path-check: ${escaping.length} path(s) are outside the project directory and were refused.`,
52
+ details: { refused: escaping },
53
+ }));
54
+ return;
55
+ }
56
+
57
+ const missing = paths.filter((p) => !existsSync(resolve(root, p)));
58
+ process.stdout.write(JSON.stringify({
59
+ status: missing.length === 0 ? 'passed' : 'failed',
60
+ summary: missing.length === 0
61
+ ? `All ${paths.length} path(s) exist.`
62
+ : `${missing.length} of ${paths.length} path(s) missing: ${missing.join(', ')}.`,
63
+ details: { checked: paths, missing },
64
+ }));
65
+ }
66
+
67
+ main();