chati-dev 4.1.4 → 4.1.6

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/bin/chati.js CHANGED
@@ -105,6 +105,16 @@ async function main() {
105
105
 
106
106
  console.log(` ${migrationResult.migrationsRun} migration(s) applied.`);
107
107
 
108
+ // 2.5. Sync framework files (copy new/updated agents, templates, hooks, etc.)
109
+ console.log(' Syncing framework files...');
110
+ try {
111
+ const { copyFrameworkFiles } = await import('../src/installer/core.js');
112
+ copyFrameworkFiles(join(targetDir, 'chati.dev'));
113
+ console.log(' Framework files synced.');
114
+ } catch (err) {
115
+ console.log(` Framework sync skipped: ${err.message}`);
116
+ }
117
+
108
118
  // 3. Validate
109
119
  console.log(' Validating...');
110
120
  const validation = await validateInstallation(targetDir);
@@ -39,8 +39,12 @@ Implement each task from the approved task breakdown with high quality, followin
39
39
  3. Read Tasks: `chati.dev/artifacts/6-Tasks/tasks.md`
40
40
  4. Read Architecture: `chati.dev/artifacts/3-Architecture/architecture.md`
41
41
  5. Read UX: `chati.dev/artifacts/4-UX/ux-specification.md` (Design System tokens)
42
- 6. Read Intelligence: `chati.dev/intelligence/gotchas.yaml` (known pitfalls)
43
- 7. Acknowledge inherited context
42
+ 6. Read Brandbook: `chati.dev/artifacts/4-UX/brandbook.md` (visual identity, token values)
43
+ 7. Read Intelligence: `chati.dev/intelligence/gotchas.yaml` (known pitfalls)
44
+ 8. If browser MCP available: open `chati.dev/artifacts/4-UX/brandbook.html` in browser
45
+ via `browser_navigate` and take a screenshot to establish visual reference baseline.
46
+ If not available: skip silently, rely on brandbook.md text values only.
47
+ 9. Acknowledge inherited context
44
48
 
45
49
  **Agent-Driven Opening:**
46
50
  > "QA-Planning approved the plan. I'll now implement the tasks starting with Phase 1.
@@ -89,6 +93,24 @@ For each task:
89
93
  RULE: architecture.md is the source of truth. NEVER implement against architectural decisions.
90
94
  RULE: Do NOT silently reconcile conflicts — always surface them.
91
95
  If no architecture.md present → proceed with best practices, note in handoff.
96
+ 2.5. Component Research (MANDATORY for any task that creates or modifies a UI component):
97
+ BEFORE writing any UI code, search production component libraries:
98
+
99
+ a. WebFetch `https://ui.shadcn.com/r/{component-name}.json` — structural components
100
+ b. WebFetch `https://21st.dev/r/{component-slug}` — animated/styled components
101
+ c. WebFetch `https://magicui.design/r/{component-name}` — effects, backgrounds, text animations
102
+ d. Check Aceternity UI (`https://ui.aceternity.com`) — landing pages, 3D cards, spotlight effects
103
+ e. Check React Bits (`https://reactbits.dev`) — interactive animated components
104
+
105
+ Decision:
106
+ ADOPT: use component code as-is, adapt tokens to project values
107
+ ADAPT: use structure, replace visual values with project tokens
108
+ CUSTOM: build from scratch (only if nothing suitable in any library)
109
+
110
+ Log in task output: "Component source: {ADOPTED/ADAPTED/CUSTOM} from {source}/{name}"
111
+ If WebFetch unavailable: log "Research skipped — unavailable" and proceed.
112
+ Skip entirely for backend-only, config, or test-only tasks.
113
+
92
114
  3. Implement code
93
115
  -> Output: "Implementation done. Running self-critique (5.5)..."
94
116
  4. Run self-critique (Step 5.5) — 1 fix pass, then proceed
@@ -116,7 +138,24 @@ For each task:
116
138
  [ ] No console.log (use proper logging)
117
139
  [ ] No commented-out code
118
140
  [ ] Commit message follows conventional format
141
+ [ ] Visual review passed (Step 9.7) — UI tasks only
119
142
  If ANY item fails: fix before committing.
143
+ 9.7. Visual Self-Review (MANDATORY for tasks that touch UI files):
144
+ Skip entirely for backend-only, config, or test-only tasks.
145
+
146
+ a. Ensure dev server is running (start with `npm run dev` if needed).
147
+ b. Use Playwright MCP: `browser_navigate` to the affected page.
148
+ c. Take screenshot: `browser_take_screenshot`
149
+ d. Analyze screenshot against:
150
+ - Brandbook tokens: colors, spacing, typography visually matching?
151
+ - Component states: default, hover, error, loading states correct?
152
+ - Layout integrity: no broken layouts, overflow, or misalignment?
153
+ e. If visual issues found: fix (1 pass only), re-screenshot.
154
+ f. Output: "Visual review PASSED — screenshot attached"
155
+ Or: "Visual review DEFERRED — browser MCP unavailable"
156
+
157
+ If browser MCP unavailable: mark DEFERRED, note in handoff. Do NOT block.
158
+
120
159
  10. Commit and move to next task
121
160
 
122
161
  ANTI-LOOP RULE: Steps 5.5 and 6.5 execute ONCE per task. No cycles allowed.
@@ -154,6 +193,11 @@ WHILE tasks_pending:
154
193
  → API/DB/Auth/module tasks: verify patterns match architecture.md
155
194
  → If conflict → mark task blocked (G05), skip to next task
156
195
  → If no architecture.md → proceed with best practices
196
+ 2.5. Component Research (UI tasks only — same rules as Interactive Mode):
197
+ → WebFetch shadcn/ui, 21st.dev, Magic UI, Aceternity UI, React Bits
198
+ → ADOPT/ADAPT if match found; CUSTOM only as last resort
199
+ → Log: "Component source: {status} [{source}]"
200
+ → If unavailable: log "Research skipped" and proceed
157
201
  3. Implement code
158
202
  -> Output: "T{X} implementation done. Self-critique (5.5)..."
159
203
  4. Run self-critique (Step 5.5) — 1 fix pass, then proceed
@@ -163,6 +207,10 @@ WHILE tasks_pending:
163
207
  6. Run post-test critique (Step 6.5) — 1 fix pass, then score
164
208
  -> Output: "T{X} review done. Scoring..."
165
209
  7. Self-validate against acceptance criteria
210
+ 7.5. Visual Self-Review (UI tasks only — same rules as Interactive Mode Step 9.7):
211
+ → browser_navigate to affected page, browser_take_screenshot
212
+ → Analyze vs brandbook + spec. Fix (1 pass) if issues found, re-screenshot
213
+ → If browser MCP unavailable: mark DEFERRED in handoff
166
214
  8. Calculate score
167
215
 
168
216
  IF score >= 95:
@@ -54,39 +54,72 @@ For each component specify:
54
54
  - Props interface (what data it accepts)
55
55
  - Token dependencies (which design tokens it uses)
56
56
 
57
- ### 3C. Component Discovery (21st.dev)
57
+ ### 3C. Component Discovery
58
58
 
59
- BEFORE designing any Molecule or Organism from scratch, search 21st.dev for existing
60
- production-ready components that can be adapted.
59
+ BEFORE designing any Molecule or Organism from scratch, search production component libraries
60
+ in priority order. Use WebFetch to pull registry JSON and inspect actual component source.
61
61
 
62
62
  **Component Library Strategy** (verified, scrapable sources):
63
63
 
64
64
  Primary: Shadcn/ui (MIT license, React + Radix UI + Tailwind + cva)
65
- - Registry: ui.shadcn.com (component docs with code examples)
66
- - GitHub: github.com/shadcn-ui/ui (source code, fully scrapable)
65
+ - Registry JSON: WebFetch `https://ui.shadcn.com/r/{component-name}.json`
66
+ (returns full component source + dependencies)
67
+ - GitHub: github.com/shadcn-ui/ui
67
68
  - Components: Button, Input, Select, Dialog, Sheet, Table, Card, Tabs, Command, etc.
68
69
 
69
- Secondary: Headless UI (Tailwind Labs, MIT license)
70
+ Secondary: 21st.dev (community-built, animated, high-quality React/Tailwind)
71
+ - Registry JSON: WebFetch `https://21st.dev/r/{component-slug}`
72
+ (slug = kebab-case, e.g. "animated-hero", "pricing-cards", "feature-grid")
73
+ - Strength: production-quality animated components, scroll effects, modern layouts
74
+ - License: varies per component — check the registry JSON "license" field before adopting
75
+
76
+ Tertiary: Magic UI (150+ MIT components, animated effects)
77
+ - Registry JSON: WebFetch `https://magicui.design/r/{component-name}`
78
+ (returns full TSX source — examples: "animated-beam", "border-beam", "globe",
79
+ "morphing-text", "retro-grid", "shimmer-button", "aurora-text")
80
+ - Strength: animated effects, background patterns, text animations
81
+
82
+ Quaternary: Aceternity UI (200+ free components, Framer Motion animations)
83
+ - Website: `https://ui.aceternity.com`
84
+ - Strength: spotlight effects, parallax scrolling, 3D cards, animated layouts
85
+ - Best for: landing pages, hero sections, feature showcases, pricing tables
86
+ - License: check per component before adopting
87
+
88
+ Quinary: React Bits (animated, interactive React components)
89
+ - Website: `https://reactbits.dev`
90
+ - Strength: high-quality animated interactive components, unique micro-interactions
91
+ - Best for: interactive elements, animated lists, creative UI patterns
92
+
93
+ Senary: Headless UI (Tailwind Labs, MIT license)
70
94
  - GitHub: github.com/tailwindlabs/headlessui
71
95
  - Unstyled primitives: Menu, Listbox, Combobox, Dialog, Popover, Switch, Tabs
72
-
73
- Tertiary: Custom build (when no suitable library component exists)
74
-
75
- For EACH Molecule and Organism:
76
- 1. Check Shadcn/ui registry for matching component
77
- - If found: ADOPTED (use pattern as-is) or ADAPTED (modify to fit project tokens)
78
- 2. If not in Shadcn: check Headless UI
79
- 3. If neither: CUSTOM with documented rationale
80
-
81
- Matching criteria:
82
- (a) Supports required variants (primary/secondary/ghost/destructive)
83
- (b) Includes keyboard navigation (Tab, Enter, Escape, Arrow keys)
96
+ - Use when: accessibility-critical component, no styled match found above
97
+
98
+ Last resort: Custom build (when no suitable library component exists)
99
+
100
+ **Discovery Process** (MANDATORY for each Molecule and Organism):
101
+ 1. Identify the component need from user flows
102
+ 2. WebFetch shadcn/ui: `https://ui.shadcn.com/r/{name}.json` structural match
103
+ 3. WebFetch 21st.dev: `https://21st.dev/r/{name}` animated/styled match
104
+ 4. WebFetch Magic UI: `https://magicui.design/r/{name}` — effect/enhancement match
105
+ 5. Check Aceternity UI and React Bits websites for specialized components (landing pages, interactive elements)
106
+ 6. If still not found: check Headless UI GitHub for primitive
107
+ 7. If nothing suitable: CUSTOM
108
+
109
+ Matching criteria (any source):
110
+ (a) Supports required variants from component spec
111
+ (b) Has keyboard navigation (Tab, Enter, Escape, Arrow keys)
84
112
  (c) Has TypeScript types
85
113
  (d) Accessible by default (ARIA attributes, focus management)
86
114
  (e) Compatible with frontend framework from Architecture
115
+ (f) License is MIT or permissive (do NOT adopt GPL or unlicensed components)
87
116
 
88
117
  **Component Discovery Log** (MANDATORY):
89
- Every Molecule and Organism MUST have an entry with status (ADOPTED/ADAPTED/CUSTOM).
118
+ Every Molecule and Organism MUST have an entry:
119
+ | Component | Source Library | URL Fetched | Status | Notes |
120
+ |-----------|---------------|-------------|--------|-------|
121
+ | {Name} | {shadcn/21st.dev/magicui/headless/custom} | {url} | {ADOPTED/ADAPTED/CUSTOM} | {rationale} |
122
+
90
123
  Missing entries = FAIL for self-validation criterion.
91
124
  Zero silent skips.
92
125
 
@@ -188,11 +221,47 @@ Report: contrast ratio table with APCA Lc values for all color pairs, focus orde
188
221
 
189
222
  Define professional animation by default:
190
223
 
191
- **Library Selection** (by frontend framework from Architecture):
192
- - React/Next.js: `motion/react` (formerly framer-motion)
193
- - Vue/Nuxt: `@vueuse/motion`
194
- - Svelte: built-in transitions
195
- - Vanilla: `gsap` or Web Animations API
224
+ **Animation Library Selection** (by frontend framework from Architecture):
225
+
226
+ | Framework | Primary (micro-interactions) | Complex animations | Scroll |
227
+ |-----------|----------------------------|-------------------|--------|
228
+ | React/Next.js | `motion/react` | `anime.js` or `gsap` | `lenis` + `gsap ScrollTrigger` |
229
+ | Vue/Nuxt | `@vueuse/motion` | `anime.js` or `gsap` | `lenis` + `gsap ScrollTrigger` |
230
+ | Svelte | built-in transitions | `anime.js` or `gsap` | `lenis` |
231
+ | Vanilla | Web Animations API | `gsap` | `lenis` + `gsap ScrollTrigger` |
232
+
233
+ **motion/react** (primary for React, 8KB):
234
+ - Default for: hover, focus, click feedback, layout transitions, page transitions
235
+ - Declarative API integrates with React component lifecycle
236
+ - Do NOT use for: complex multi-element timelines or scroll-driven sequences
237
+
238
+ **GSAP** (`npm i gsap`, industry standard, now 100% free):
239
+ - Use when: complex timelines, ScrollTrigger (scroll-driven animations),
240
+ SVG morphing, text splitting, pinning sections during scroll
241
+ - ScrollTrigger: `gsap.registerPlugin(ScrollTrigger)` — the gold standard for scroll animations
242
+ - Best for: landing pages, scrollytelling, parallax, section-based reveals
243
+ - API: `gsap.to('.element', { y: -20, duration: 0.6, ease: 'power2.out' })`
244
+
245
+ **anime.js** (`npm i animejs`, 24.5KB, zero deps, MIT license):
246
+ - Use when: staggered entrance sequences, counter/number animations,
247
+ spring physics stagger, SVG path drawing
248
+ - Lighter than GSAP, simpler API for non-scroll animations
249
+ - API: `anime({ targets, translateY, opacity, delay, easing, duration })`
250
+ - Timeline: `anime.timeline({ autoplay: false }).add(...).add(...)`
251
+
252
+ **Lenis** (`npm i lenis`, smooth scroll):
253
+ - Use when: the design requires buttery-smooth momentum scrolling
254
+ - Does NOT break CSS sticky positioning (unlike other smooth scroll libs)
255
+ - Pairs perfectly with GSAP ScrollTrigger for scroll-driven animations
256
+ - API: `const lenis = new Lenis(); lenis.on('scroll', ScrollTrigger.update)`
257
+
258
+ **Decision rules:**
259
+ - Simple hover/focus/click → motion/react (default)
260
+ - Scroll-driven reveals, parallax, pinning → GSAP ScrollTrigger + Lenis
261
+ - Staggered lists, counters, SVG drawing → anime.js
262
+ - Smooth page scroll → Lenis
263
+ - Page/layout transitions → motion/react
264
+ - Do NOT combine motion/react with GSAP on the same element (conflict)
196
265
 
197
266
  **Motion Personality** (from brandbook motion philosophy):
198
267
  - Energetic: fast reveals, staggered entries, marquee effects
@@ -278,7 +347,7 @@ Missing states = quality regression. No happy-path-only components.
278
347
  3. All interactive components specify full state coverage (interactive + input + data view + page)
279
348
  4. All animations use GPU-only properties with reduced-motion alternatives
280
349
  5. Motion System defined (library, tokens, micro-interactions, scroll animations)
281
- 6. Component Discovery Log complete (EVERY Molecule + Organism has entry with ADOPTED/ADAPTED/CUSTOM status)
350
+ 6. Component Discovery Log complete (EVERY Molecule + Organism has entry): all 3 registry sources queried (shadcn/ui, 21st.dev, Magic UI), status ADOPTED/ADAPTED/CUSTOM with source URL, license verified
282
351
 
283
352
  ---
284
353
 
@@ -18,9 +18,9 @@ You are the **QA-Implementation Agent**, the quality gate between BUILD and DEPL
18
18
 
19
19
  ## Required MCPs
20
20
  - git (read-only)
21
+ - browser (Playwright MCP — required for visual review of UI changes)
21
22
 
22
23
  ## Optional MCPs
23
- - browser (for E2E testing)
24
24
  - coderabbit (AI-powered code review)
25
25
 
26
26
  ---
@@ -37,7 +37,9 @@ Validate that the implemented code meets quality standards: tests pass, coverage
37
37
  2. Read `.chati/session.yaml` for project context
38
38
  3. Read Tasks: `chati.dev/artifacts/6-Tasks/tasks.md` (acceptance criteria)
39
39
  4. Read Architecture: `chati.dev/artifacts/3-Architecture/architecture.md` (patterns)
40
- 5. Acknowledge inherited context
40
+ 5. Read UX: `chati.dev/artifacts/4-UX/ux-specification.md` (Design System tokens, component states)
41
+ 6. Read Brandbook: `chati.dev/artifacts/4-UX/brandbook.md` (token values for visual comparison)
42
+ 7. Acknowledge inherited context
41
43
 
42
44
  **Agent-Driven Opening (brief status to user):**
43
45
  > "Dev has completed implementation. Running quality validation: tests, security scan, and code review..."
@@ -268,15 +270,54 @@ If the implementation includes a bug fix, verify causation:
268
270
  If not a bug fix, skip this phase and document: "Not a bug fix -- causation verification N/A."
269
271
  ```
270
272
 
273
+ ### Phase 5d: Visual Review (Mandatory for PRs touching UI files)
274
+
275
+ CONDITION: Execute only when diff includes .tsx/.jsx/.vue/.svelte files or CSS/Tailwind changes.
276
+ If no UI files changed: document "Phase 5d: N/A — no UI files changed."
277
+
278
+ ```
279
+ 1. Start dev server if not running (`npm run dev` or equivalent).
280
+
281
+ 2. For each page/route affected by the diff:
282
+
283
+ a. Desktop (1280px): browser_navigate → browser_take_screenshot
284
+ b. Tablet (768px): set viewport → navigate → screenshot
285
+ c. Mobile (375px): set viewport → navigate → screenshot
286
+
287
+ 3. Analyze each screenshot against:
288
+ - Brandbook token compliance (colors, spacing, typography)
289
+ - Component state completeness (hover, focus, error, loading)
290
+ - No visual regressions (broken layout, overflow, misalignment)
291
+ - Responsive correctness at all 3 viewports
292
+
293
+ 4. Classify findings:
294
+ - VISUAL-ERROR (blocks APPROVED): wrong brand colors, broken layout, missing states
295
+ - VISUAL-WARNING (noted, does not block): minor spacing, animation timing
296
+
297
+ 5. If VISUAL-ERROR found: send correction to Dev agent (Silent Correction Loop).
298
+
299
+ 6. Document in report:
300
+ ## Visual Review
301
+ | Route | Desktop | Tablet | Mobile | Tokens | Responsive | Status |
302
+ |-------|---------|--------|--------|--------|------------|--------|
303
+ | /{route} | {screenshot} | {screenshot} | {screenshot} | {PASS/FAIL} | {PASS/FAIL} | {OK/ERROR/WARNING} |
304
+
305
+ FALLBACK: If browser MCP unavailable:
306
+ - Check Dev handoff for screenshot evidence (Step 9.7)
307
+ - If Dev provided screenshots: use those as visual evidence
308
+ - If no screenshots anywhere: flag VISUAL-WARNING "No visual evidence" and note in report
309
+ ```
310
+
271
311
  ### Phase 6: Score & Decide
272
312
  ```
273
313
  Calculate overall quality score:
274
- Tests: weight 0.25
314
+ Tests: weight 0.20
275
315
  Coverage: weight 0.10
276
- Security: weight 0.25
316
+ Security: weight 0.20
277
317
  Code Quality: weight 0.15
278
318
  Acceptance Criteria: weight 0.10
279
319
  Adversarial Review: weight 0.15
320
+ Visual Review: weight 0.10 (0.00 when no UI files — redistribute to Tests + Security)
280
321
 
281
322
  Result:
282
323
  - All checks pass AND adversarial review complete -> APPROVED -> proceed to DevOps
@@ -1,8 +1,8 @@
1
1
  # chati.dev Configuration
2
- version: "4.1.4"
2
+ version: "4.1.6"
3
3
  installed_at: "2026-02-07T10:00:00Z"
4
4
  updated_at: "2026-04-06T00:00:00Z"
5
- installer_version: "4.1.4"
5
+ installer_version: "4.1.6"
6
6
  project_type: greenfield
7
7
  language: en
8
8
  ides: [claude-code]
@@ -489,5 +489,5 @@ When multiple CLI providers are enabled, the system SHALL coordinate agent execu
489
489
 
490
490
  ---
491
491
 
492
- *Chati.dev Constitution v4.1.4 — 19 Articles + Preamble*
492
+ *Chati.dev Constitution v4.1.6 — 19 Articles + Preamble*
493
493
  *All agents are bound by this Constitution. Violations are enforced per article.*
@@ -1,7 +1,7 @@
1
1
  # Chati.dev System Context
2
2
 
3
3
  ## Framework
4
- - **Version**: 4.1.4
4
+ - **Version**: 4.1.6
5
5
  - **Agents**: Specialized agents across DISCOVER, PLAN, BUILD, DEPLOY phases
6
6
  - **Constitution**: 19 Articles + Preamble
7
7
  - **Quality**: 5 pipeline gates + 3-tier verdicts (APPROVED / NEEDS_REVISION / BLOCKED)
@@ -3,7 +3,7 @@
3
3
  # and by the Health Check for system integrity validation.
4
4
 
5
5
  metadata:
6
- version: "4.1.4"
6
+ version: "4.1.6"
7
7
  last_updated: "2026-04-06T00:00:00Z"
8
8
  entity_count: 62
9
9
  checksum_algorithm: sha256
@@ -112,9 +112,10 @@ async function main() {
112
112
  }
113
113
 
114
114
  if (result.violations.length > 0) {
115
+ // Block all style violations — agent must rewrite
115
116
  process.stdout.write(JSON.stringify({
116
- decision: 'allow',
117
- reason: `[Style Guard] ${result.violations.join(' ')}`,
117
+ decision: 'block',
118
+ reason: `[Style Guard] ${result.violations.join(' ')} Rewrite without em-dashes and emojis.`,
118
119
  }));
119
120
  return;
120
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chati-dev",
3
- "version": "4.1.4",
3
+ "version": "4.1.6",
4
4
  "description": "AI-Powered Multi-Agent Orchestration System — Structured vibe coding for Full Stack Development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -533,14 +533,14 @@ main();
533
533
 
534
534
  /**
535
535
  * Generate the style guard hook for Gemini CLI.
536
- * BeforeTool event -- advisory: enforces em-dash and emoji standards.
536
+ * BeforeTool event -- blocking: rejects em-dashes and emojis in generated content.
537
537
  */
538
538
  function generateStyleGuard() {
539
539
  return `${HOOK_HEADER}
540
540
  /**
541
541
  * Style Guard -- BeforeTool
542
- * Advisory: enforces Constitution Article V writing standards
543
- * (no em-dashes, no emojis in generated content).
542
+ * Blocking: rejects em-dashes and emojis in generated content.
543
+ * Constitution Article V enforcement.
544
544
  */
545
545
  async function main() {
546
546
  let input = '';
@@ -584,8 +584,8 @@ async function main() {
584
584
 
585
585
  if (result.violations && result.violations.length > 0) {
586
586
  console.log(JSON.stringify({
587
- decision: 'allow',
588
- reason: \`[Style Guard] \${result.violations.join(' ')}\`,
587
+ decision: 'block',
588
+ reason: \`[Style Guard] \${result.violations.join(' ')} Rewrite without em-dashes and emojis.\`,
589
589
  }));
590
590
  return;
591
591
  }
@@ -148,7 +148,7 @@ export async function installFramework(config) {
148
148
  /**
149
149
  * Copy framework files from the Chati.dev source directory
150
150
  */
151
- function copyFrameworkFiles(destDir, provider = 'claude') {
151
+ export function copyFrameworkFiles(destDir, provider = 'claude') {
152
152
  if (!existsSync(FRAMEWORK_SOURCE)) return;
153
153
 
154
154
  const filesToCopy = [
@@ -914,8 +914,15 @@ async function handleScan(projectDir, args) {
914
914
  const { scanProjectSecurity } = await import('../scanning/security-scanner.js');
915
915
  return scanProjectSecurity(projectDir, { language: args.language, dir: args.dir });
916
916
  }
917
+ case 'ui': {
918
+ const file = args.file;
919
+ if (!file) return errorResult('Missing --file flag for UI scan', 'MISSING_FILE');
920
+ const { scanUIQuality } = await import('../scanning/ui-scanner.js');
921
+ const { join: joinPath } = await import('path');
922
+ return scanUIQuality(joinPath(projectDir, file));
923
+ }
917
924
  default:
918
- return errorResult(`Unknown scan type: ${type}. Valid: placeholders, leakage, density, env, security`, 'UNKNOWN_SCAN_TYPE');
925
+ return errorResult(`Unknown scan type: ${type}. Valid: placeholders, leakage, density, env, security, ui`, 'UNKNOWN_SCAN_TYPE');
919
926
  }
920
927
  } catch (err) {
921
928
  return errorResult(`Scan failed: ${err.message}`, 'SCAN_ERROR');
@@ -30,7 +30,7 @@ export function executeHandoff(projectDir, params) {
30
30
  const errors = [];
31
31
 
32
32
  // Validate preconditions
33
- const preconditions = validateHandoffPreconditions(params);
33
+ const preconditions = validateHandoffPreconditions(params, projectDir);
34
34
  if (!preconditions.valid) {
35
35
  return {
36
36
  success: false,
@@ -113,7 +113,7 @@ export function executeHandoff(projectDir, params) {
113
113
  * @param {object} params
114
114
  * @returns {{ valid: boolean, issues: string[] }}
115
115
  */
116
- export function validateHandoffPreconditions(params) {
116
+ export function validateHandoffPreconditions(params, projectDir = '.') {
117
117
  const issues = [];
118
118
 
119
119
  // Check validation exists and passed
@@ -148,6 +148,19 @@ export function validateHandoffPreconditions(params) {
148
148
  issues.push('Handoff summary is required');
149
149
  }
150
150
 
151
+ // Deterministic artifact checks for UX agent handoffs
152
+ const agent = params.fromTask?.agent || params.fromTask?.id?.split('-')[0] || '';
153
+ if (agent === 'ux') {
154
+ const brandbookPath = join(projectDir, 'chati.dev', 'artifacts', '4-UX', 'brandbook.html');
155
+ if (!existsSync(brandbookPath)) {
156
+ issues.push('brandbook.html is missing — UX handoff requires the visual brandbook file on disk');
157
+ }
158
+ const discoveryLogPath = join(projectDir, 'chati.dev', 'artifacts', '4-UX', 'component-discovery-log.md');
159
+ if (!existsSync(discoveryLogPath)) {
160
+ issues.push('component-discovery-log.md is missing — UX handoff requires component discovery documentation');
161
+ }
162
+ }
163
+
151
164
  return {
152
165
  valid: issues.length === 0,
153
166
  issues,
@@ -319,6 +332,20 @@ export function validateHandoffIntegrity(handoff, receivingAgent) {
319
332
  }
320
333
  }
321
334
 
335
+ // Check for screenshot evidence from Dev agent (UI tasks)
336
+ if (handoff.from_agent === 'dev' || handoff.agent === 'dev') {
337
+ const hasUIOutputs = handoff.outputs && handoff.outputs.some(o =>
338
+ /\.(tsx|jsx|vue|svelte|css)$/i.test(o)
339
+ );
340
+ if (hasUIOutputs) {
341
+ const hasVisualEvidence = handoff.summary &&
342
+ (handoff.summary.includes('Visual review PASSED') || handoff.summary.includes('screenshot'));
343
+ if (!hasVisualEvidence) {
344
+ warnings.push('Dev agent produced UI files but no visual review evidence found in handoff — manual visual review recommended');
345
+ }
346
+ }
347
+ }
348
+
322
349
  return {
323
350
  valid: missing.length === 0,
324
351
  missing,
@@ -5,3 +5,4 @@ export { scanImplementationLeakage, extractFRSection, TECH_NAMES } from './leaka
5
5
  export { scanInformationDensity, FILLER_PATTERNS } from './density-scanner.js';
6
6
  export { scanEnvSync, parseEnvFile, grepEnvUsage } from './env-scanner.js';
7
7
  export { scanProjectSecurity, detectLanguage } from './security-scanner.js';
8
+ export { scanUIQuality, UI_QUALITY_CHECKS } from './ui-scanner.js';
@@ -0,0 +1,120 @@
1
+ /**
2
+ * UI Quality Scanner -- Detects hardcoded visual values in UI files.
3
+ * Deterministic regex scan. Zero LLM dependency.
4
+ *
5
+ * Catches common "vibecoded" patterns:
6
+ * - Hardcoded hex colors (should use design tokens)
7
+ * - Inline styles with px values (should use spacing tokens)
8
+ * - !important in CSS (code smell)
9
+ * - Images without alt attributes (accessibility)
10
+ * - Inline style= attributes (should use classes/tokens)
11
+ */
12
+
13
+ import { readFileSync } from 'fs';
14
+
15
+ /**
16
+ * UI quality patterns. Each has an id, pattern, severity, and description.
17
+ */
18
+ export const UI_QUALITY_CHECKS = [
19
+ {
20
+ id: 'HARDCODED_HEX_COLOR',
21
+ pattern: /(?<![\\w-])(?:color|background|border|fill|stroke)\s*:\s*#[0-9a-fA-F]{3,8}\b/g,
22
+ severity: 'high',
23
+ description: 'Hardcoded hex color in CSS property — use design tokens instead',
24
+ },
25
+ {
26
+ id: 'INLINE_STYLE',
27
+ pattern: /\bstyle\s*=\s*["'{]/g,
28
+ severity: 'medium',
29
+ description: 'Inline style attribute — use CSS classes or design tokens',
30
+ },
31
+ {
32
+ id: 'CSS_IMPORTANT',
33
+ pattern: /!\s*important/gi,
34
+ severity: 'medium',
35
+ description: '!important in CSS — indicates specificity problem',
36
+ },
37
+ {
38
+ id: 'IMG_MISSING_ALT',
39
+ pattern: /<img(?![^>]*\balt\s*=)[^>]*>/gi,
40
+ severity: 'high',
41
+ description: 'Image tag without alt attribute — accessibility violation',
42
+ },
43
+ {
44
+ id: 'HARDCODED_PX_INLINE',
45
+ pattern: /style\s*=\s*["'][^"']*\d+px[^"']*["']/g,
46
+ severity: 'medium',
47
+ description: 'Hardcoded px values in inline style — use spacing tokens',
48
+ },
49
+ {
50
+ id: 'HARDCODED_RGBA',
51
+ pattern: /(?<![\\w-])(?:color|background|border|fill|stroke)\s*:\s*rgba?\([^)]+\)/g,
52
+ severity: 'high',
53
+ description: 'Hardcoded rgba color — use design tokens instead',
54
+ },
55
+ ];
56
+
57
+ /**
58
+ * File extensions this scanner targets.
59
+ */
60
+ const UI_FILE_EXTENSIONS = ['.tsx', '.jsx', '.vue', '.svelte', '.css', '.scss'];
61
+
62
+ /**
63
+ * Paths to skip (config files, design token definitions, tailwind config).
64
+ */
65
+ const SKIP_PATTERNS = [
66
+ /tailwind\.config/,
67
+ /globals\.css$/,
68
+ /tokens?\./,
69
+ /theme\./,
70
+ /\.config\./,
71
+ /node_modules/,
72
+ /brandbook/,
73
+ ];
74
+
75
+ /**
76
+ * Check if a file should be scanned.
77
+ * @param {string} filePath
78
+ * @returns {boolean}
79
+ */
80
+ function shouldScan(filePath) {
81
+ const hasUIExtension = UI_FILE_EXTENSIONS.some(ext => filePath.endsWith(ext));
82
+ const isSkipped = SKIP_PATTERNS.some(p => p.test(filePath));
83
+ return hasUIExtension && !isSkipped;
84
+ }
85
+
86
+ /**
87
+ * Scan a file for UI quality violations.
88
+ * @param {string} filePath - Path to file to scan
89
+ * @returns {{ clean: boolean, findings: Array<{id: string, match: string, line: number, severity: string, description: string}>, count: number, skipped: boolean }}
90
+ */
91
+ export function scanUIQuality(filePath) {
92
+ if (!shouldScan(filePath)) {
93
+ return { clean: true, findings: [], count: 0, skipped: true };
94
+ }
95
+
96
+ const content = readFileSync(filePath, 'utf-8');
97
+ const findings = [];
98
+
99
+ for (const check of UI_QUALITY_CHECKS) {
100
+ const re = new RegExp(check.pattern.source, check.pattern.flags);
101
+ let match;
102
+ while ((match = re.exec(content)) !== null) {
103
+ const line = content.slice(0, match.index).split('\n').length;
104
+ findings.push({
105
+ id: check.id,
106
+ match: match[0].slice(0, 80),
107
+ line,
108
+ severity: check.severity,
109
+ description: check.description,
110
+ });
111
+ }
112
+ }
113
+
114
+ return {
115
+ clean: findings.length === 0,
116
+ findings,
117
+ count: findings.length,
118
+ skipped: false,
119
+ };
120
+ }