qualia-framework-v2 2.10.0 → 3.0.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.
- package/CLAUDE.md +1 -3
- package/README.md +11 -6
- package/agents/planner.md +52 -0
- package/agents/verifier.md +120 -19
- package/bin/cli.js +149 -0
- package/bin/install.js +30 -25
- package/bin/qualia-ui.js +1 -1
- package/bin/state.js +106 -6
- package/hooks/auto-update.js +27 -2
- package/hooks/block-env-edit.js +22 -0
- package/hooks/branch-guard.js +23 -2
- package/hooks/migration-guard.js +23 -0
- package/hooks/pre-compact.js +20 -0
- package/hooks/pre-deploy-gate.js +39 -0
- package/hooks/pre-push.js +20 -0
- package/hooks/session-start.js +19 -0
- package/package.json +1 -1
- package/skills/qualia/SKILL.md +1 -0
- package/skills/qualia-build/SKILL.md +18 -0
- package/skills/qualia-learn/SKILL.md +27 -4
- package/skills/qualia-polish/SKILL.md +154 -117
- package/skills/qualia-report/SKILL.md +17 -8
- package/skills/qualia-review/SKILL.md +126 -41
- package/skills/qualia-test/SKILL.md +134 -0
- package/skills/qualia-verify/SKILL.md +1 -1
- package/templates/plan.md +14 -0
- package/tests/bin.test.sh +15 -1
- package/tests/hooks.test.sh +53 -7
- package/tests/state.test.sh +189 -11
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-polish
|
|
3
|
-
description: "Design and UX pass —
|
|
3
|
+
description: "Design and UX pass — anti-AI-slop, genuine craft, responsive, accessible. Run after all phases are verified."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /qualia-polish — Design Pass
|
|
7
7
|
|
|
8
|
-
Run after all feature phases are verified.
|
|
8
|
+
Makes it look like a human designer built it. Kills AI slop. Run after all feature phases are verified.
|
|
9
|
+
|
|
10
|
+
## The Standard
|
|
11
|
+
|
|
12
|
+
Every site Qualia ships must feel **designed, not generated.** AI-generated sites have tells:
|
|
13
|
+
- Identical card grids with rounded corners and soft shadows
|
|
14
|
+
- Blue-purple gradients on everything
|
|
15
|
+
- Inter/system-ui font with no hierarchy
|
|
16
|
+
- Generic hero with centered text and a stock gradient background
|
|
17
|
+
- Fixed-width containers leaving dead space on wide screens
|
|
18
|
+
- No motion, no personality, no opinion
|
|
19
|
+
- Perfect symmetry everywhere (real design has tension)
|
|
20
|
+
|
|
21
|
+
**Kill all of these.** A Qualia site should make someone ask "who designed this?" — not "which template is this?"
|
|
9
22
|
|
|
10
23
|
## Process
|
|
11
24
|
|
|
@@ -19,139 +32,163 @@ node ~/.claude/bin/qualia-ui.js banner polish
|
|
|
19
32
|
cat .planning/DESIGN.md 2>/dev/null || echo "NO_DESIGN"
|
|
20
33
|
```
|
|
21
34
|
|
|
22
|
-
If DESIGN.md exists →
|
|
35
|
+
If DESIGN.md exists → it's the standard. If not → use `rules/frontend.md` defaults.
|
|
23
36
|
|
|
24
37
|
Read EVERY frontend file before modifying. No blind edits.
|
|
25
38
|
|
|
26
|
-
### 1.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
**Typography:**
|
|
31
|
-
- [ ] No generic fonts (Inter, Roboto, Arial, system-ui, Space Grotesk)
|
|
32
|
-
- [ ] Proper type scale with clear hierarchy (display → heading → body → caption)
|
|
33
|
-
- [ ] Body text: 16px+, line-height 1.5–1.7
|
|
34
|
-
- [ ] Headings: tighter line-height (1.1–1.3), negative letter-spacing for large sizes
|
|
35
|
-
- [ ] Prose max-width: 65ch
|
|
36
|
-
- [ ] Font weights used for hierarchy (Regular, Medium, Semibold, Bold)
|
|
37
|
-
|
|
38
|
-
**Color & Contrast:**
|
|
39
|
-
- [ ] Cohesive palette via CSS variables (not scattered hex values)
|
|
40
|
-
- [ ] All text passes WCAG AA contrast (4.5:1 normal, 3:1 large)
|
|
41
|
-
- [ ] CTA buttons use accent color — stand out from the page
|
|
42
|
-
- [ ] No blue-purple gradients, no rainbow palettes
|
|
43
|
-
- [ ] Dark mode: rethought surfaces (not just inverted)
|
|
44
|
-
- [ ] Semantic colors used consistently (success=green, error=red, warning=amber)
|
|
45
|
-
|
|
46
|
-
**Layout & Spacing:**
|
|
47
|
-
- [ ] Full-width fluid layouts — no hardcoded max-width caps
|
|
48
|
-
- [ ] 8px spacing grid followed consistently
|
|
49
|
-
- [ ] Tight spacing within groups, generous between sections
|
|
50
|
-
- [ ] Fluid padding: `clamp(1rem, 5vw, 4rem)` horizontal
|
|
51
|
-
- [ ] No identical card grids — varied visual hierarchy
|
|
52
|
-
- [ ] No generic heroes — purposeful, distinctive
|
|
53
|
-
|
|
54
|
-
**Interactive States:**
|
|
55
|
-
- [ ] Every button/link: hover (color shift, 150ms), focus (visible ring), active (press feedback), disabled
|
|
56
|
-
- [ ] Loading: skeleton or spinner on all async operations
|
|
57
|
-
- [ ] Empty: helpful message + action on empty lists/data
|
|
58
|
-
- [ ] Error: user-friendly message + recovery action on failed fetches
|
|
59
|
-
- [ ] Form validation: inline errors with `aria-describedby`
|
|
60
|
-
|
|
61
|
-
**Motion:**
|
|
62
|
-
- [ ] Hover/focus: 150–200ms transitions
|
|
63
|
-
- [ ] Page load: staggered entrance animations (50–80ms delay)
|
|
64
|
-
- [ ] Expand/collapse: 250ms ease-in-out
|
|
65
|
-
- [ ] `prefers-reduced-motion` respected (no animation for users who opt out)
|
|
66
|
-
- [ ] No jank: transforms and opacity only for animated properties
|
|
67
|
-
|
|
68
|
-
**Accessibility:**
|
|
69
|
-
- [ ] Semantic HTML: `nav`, `main`, `section`, `article`, `header`, `footer`
|
|
70
|
-
- [ ] One `h1` per page, sequential heading hierarchy
|
|
71
|
-
- [ ] All images: descriptive `alt` (or `alt=""` + `aria-hidden` if decorative)
|
|
72
|
-
- [ ] All form inputs: visible `<label>` with `htmlFor` — not placeholder-only
|
|
73
|
-
- [ ] All interactive elements: keyboard accessible (Tab/Enter/Escape)
|
|
74
|
-
- [ ] Touch targets: 44px minimum
|
|
75
|
-
- [ ] Skip link: `<a href="#main">` as first focusable element
|
|
76
|
-
- [ ] No `outline: none` without focus replacement
|
|
77
|
-
- [ ] `<html lang="en">` set
|
|
78
|
-
- [ ] Color not sole information carrier — icons/text as supplements
|
|
79
|
-
|
|
80
|
-
**Responsive:**
|
|
81
|
-
- [ ] Mobile-first approach (base styles for mobile, min-width breakpoints for larger)
|
|
82
|
-
- [ ] No horizontal scroll at 320px
|
|
83
|
-
- [ ] Navigation: hamburger on mobile, expanded on desktop
|
|
84
|
-
- [ ] Touch targets adequate on mobile (44px min)
|
|
85
|
-
- [ ] Fluid typography with `clamp()`
|
|
86
|
-
- [ ] Images: `max-width: 100%`, responsive srcset where needed
|
|
87
|
-
- [ ] Tables: card view or horizontal scroll on mobile
|
|
88
|
-
|
|
89
|
-
**Performance:**
|
|
90
|
-
- [ ] Server Components by default — `'use client'` only when needed
|
|
91
|
-
- [ ] Images via `next/image` with width/height
|
|
92
|
-
- [ ] No barrel file imports — direct imports from source
|
|
93
|
-
- [ ] Heavy components lazy-loaded with `next/dynamic`
|
|
94
|
-
- [ ] Data fetched in parallel, not sequentially
|
|
95
|
-
|
|
96
|
-
### 2. Fix Everything
|
|
97
|
-
|
|
98
|
-
Work through violations from the critique. Fix each category:
|
|
99
|
-
|
|
100
|
-
1. Typography first (sets the visual foundation)
|
|
101
|
-
2. Color & contrast (palette coherence)
|
|
102
|
-
3. Layout & spacing (structural fixes)
|
|
103
|
-
4. Interactive states (loading, empty, error, hover, focus)
|
|
104
|
-
5. Motion (transitions, entrance animations, reduced-motion)
|
|
105
|
-
6. Accessibility (semantic HTML, ARIA, keyboard, labels)
|
|
106
|
-
7. Responsive (mobile breakpoints, fluid sizing)
|
|
107
|
-
8. Performance (quick wins — image optimization, dynamic imports)
|
|
108
|
-
|
|
109
|
-
### 3. Harden
|
|
110
|
-
|
|
111
|
-
After polish, stress-test edge cases:
|
|
112
|
-
- Long text content (overflow, truncation, word-break)
|
|
113
|
-
- Extremely long usernames or email addresses
|
|
114
|
-
- Empty data everywhere simultaneously
|
|
115
|
-
- Error state on every fetch simultaneously
|
|
116
|
-
- 320px viewport width
|
|
117
|
-
- Keyboard-only navigation through entire flow
|
|
118
|
-
- Screen reader landmarks check (semantic HTML)
|
|
119
|
-
- Right-to-left text (if i18n is planned)
|
|
120
|
-
- Slow network (loading states visible, no flash of empty content)
|
|
121
|
-
|
|
122
|
-
### 4. Verify
|
|
39
|
+
### 1. AI Slop Detector
|
|
40
|
+
|
|
41
|
+
Run these checks first. Any hits = mandatory fixes.
|
|
123
42
|
|
|
124
43
|
```bash
|
|
125
|
-
|
|
44
|
+
# Generic fonts (the #1 AI tell)
|
|
45
|
+
grep -rn "Inter\|Roboto\|Arial\|Helvetica\|system-ui\|Space.Grotesk" --include="*.tsx" --include="*.css" --include="*.scss" --include="tailwind*" app/ components/ src/ 2>/dev/null | grep -v node_modules
|
|
46
|
+
|
|
47
|
+
# Hardcoded max-width containers (screams template)
|
|
48
|
+
grep -rn "max-w-7xl\|max-w-\[1200\|max-w-\[1280\|max-width.*1200\|max-width.*1280" --include="*.tsx" --include="*.css" app/ components/ src/ 2>/dev/null
|
|
49
|
+
|
|
50
|
+
# Blue-purple gradients
|
|
51
|
+
grep -rn "from-blue.*to-purple\|from-purple.*to-blue\|linear-gradient.*blue.*purple\|linear-gradient.*purple.*blue\|from-indigo.*to-violet" --include="*.tsx" --include="*.css" app/ components/ src/ 2>/dev/null
|
|
52
|
+
|
|
53
|
+
# Card grid monotony (same card component repeated in a grid)
|
|
54
|
+
grep -rn "grid-cols-3\|grid-cols-4" --include="*.tsx" app/ components/ src/ 2>/dev/null | head -5
|
|
55
|
+
|
|
56
|
+
# Generic hero patterns
|
|
57
|
+
grep -rn "text-center.*mx-auto\|Hero\|hero" --include="*.tsx" app/ components/ src/ 2>/dev/null | head -5
|
|
58
|
+
|
|
59
|
+
# Scattered hardcoded colors (no design system)
|
|
60
|
+
grep -rn "text-\[#\|bg-\[#\|border-\[#\|color:.*#\|background:.*#" --include="*.tsx" app/ components/ src/ 2>/dev/null | wc -l
|
|
126
61
|
```
|
|
127
62
|
|
|
128
|
-
|
|
63
|
+
**Every hit gets fixed.** Not flagged — fixed.
|
|
64
|
+
|
|
65
|
+
### 2. Typography Pass
|
|
66
|
+
|
|
67
|
+
**Goal:** A reader should feel the type was chosen, not defaulted.
|
|
68
|
+
|
|
69
|
+
- Pick a distinctive display font. Not Inter, not Roboto, not system. Something with character: Clash Display, Cabinet Grotesk, General Sans, Satoshi, Plus Jakarta Sans, Outfit, Sora, Manrope. Pair with a clean body font.
|
|
70
|
+
- Establish clear hierarchy: display (hero text) → h1 → h2 → h3 → body → caption
|
|
71
|
+
- Body: 16px minimum, line-height 1.5-1.7
|
|
72
|
+
- Headings: tighter line-height (1.1-1.3), negative letter-spacing (-0.02em) for display sizes
|
|
73
|
+
- Weight hierarchy: Regular (400) for body, Medium (500) for labels, Semibold (600) for headings, Bold (700) for display
|
|
74
|
+
- Prose content: `max-width: 65ch`. Everything else: fluid full-width.
|
|
75
|
+
- Use `clamp()` for fluid sizing: `clamp(2rem, 1rem + 3vw, 3.75rem)` for h1
|
|
76
|
+
|
|
77
|
+
### 3. Color & Surfaces
|
|
78
|
+
|
|
79
|
+
**Goal:** A palette that looks intentional, not random.
|
|
80
|
+
|
|
81
|
+
- Define all colors as CSS variables or Tailwind config — zero scattered hex values
|
|
82
|
+
- One dominant brand color. One sharp accent for CTAs that pops against the page.
|
|
83
|
+
- Surfaces: layer them. Background → card → elevated card. Use subtle shade differences, not just white-on-white.
|
|
84
|
+
- Dark mode (if present): rethink surfaces, don't just invert. Slightly reduce contrast. Use darker brand colors, not just white→black swap.
|
|
85
|
+
- Semantic colors with non-color indicators: success (green + checkmark), error (red + icon), warning (amber + triangle)
|
|
86
|
+
- Verify WCAG AA: 4.5:1 for normal text, 3:1 for large text (18px+ bold or 24px+)
|
|
87
|
+
|
|
88
|
+
### 4. Layout & Spacing
|
|
89
|
+
|
|
90
|
+
**Goal:** Full-width, fluid, generous. No dead space gutters.
|
|
91
|
+
|
|
92
|
+
- Full-width layouts with fluid padding: `clamp(1rem, 5vw, 4rem)` horizontal
|
|
93
|
+
- 8px spacing grid: 4, 8, 12, 16, 24, 32, 48, 64, 96
|
|
94
|
+
- Tight spacing within groups (related items). Generous spacing between sections.
|
|
95
|
+
- Break symmetry where it serves the design — offset grids, overlapping elements, diagonal flow
|
|
96
|
+
- Varied layouts: not every section should be a centered-text-with-cards-below. Use side-by-side, staggered, asymmetric, full-bleed.
|
|
97
|
+
- Section spacing: `clamp(2rem, 8vw, 6rem)` vertical padding
|
|
98
|
+
|
|
99
|
+
### 5. Interactive States
|
|
129
100
|
|
|
130
|
-
|
|
101
|
+
**Goal:** Every clickable thing responds. Every async operation shows progress.
|
|
102
|
+
|
|
103
|
+
- **Hover:** color shift or underline within 150ms ease-out. `cursor: pointer` on ALL clickables.
|
|
104
|
+
- **Focus:** visible ring (2px+ offset, contrasting color). Never `outline: none` without replacement.
|
|
105
|
+
- **Active/pressed:** subtle scale down (`transform: scale(0.98)`) or color shift.
|
|
106
|
+
- **Disabled:** opacity 0.5 + `cursor: not-allowed` + `aria-disabled="true"`
|
|
107
|
+
- **Loading:** skeleton shimmer or spinner on every async operation. Never a blank void.
|
|
108
|
+
- **Empty:** helpful message + CTA on empty lists/tables. Not just "No results."
|
|
109
|
+
- **Error:** user-friendly message + recovery action. Not raw error text. Use `aria-live="assertive"`.
|
|
110
|
+
|
|
111
|
+
### 6. Motion & Personality
|
|
112
|
+
|
|
113
|
+
**Goal:** The site feels alive, not static. But tasteful — not a circus.
|
|
114
|
+
|
|
115
|
+
- Page load: stagger children entrance (50-80ms delay between items, `fadeUp` animation, 300ms)
|
|
116
|
+
- Hover transitions: 150-200ms ease-out
|
|
117
|
+
- Section transitions: 300-500ms with `cubic-bezier(0.4, 0, 0.2, 1)`
|
|
118
|
+
- One signature motion that gives the site personality (parallax, scroll-triggered reveal, magnetic buttons, morphing shapes)
|
|
119
|
+
- **Always** `prefers-reduced-motion: reduce` — disable non-essential animation
|
|
120
|
+
- CSS-only for static sites, `motion/react` (formerly Framer Motion) for React
|
|
121
|
+
|
|
122
|
+
### 7. Accessibility (Non-Negotiable)
|
|
123
|
+
|
|
124
|
+
- Semantic HTML: `nav`, `main`, `section`, `article`, `header`, `footer` — not div soup
|
|
125
|
+
- One `h1` per page, sequential heading order (no h1 → h3 skip)
|
|
126
|
+
- All images: descriptive `alt` (or `alt=""` + `aria-hidden` if decorative)
|
|
127
|
+
- All form inputs: visible `<label>` with `htmlFor` — not placeholder-only
|
|
128
|
+
- All interactive elements: keyboard accessible (Tab, Enter, Escape, Arrow keys)
|
|
129
|
+
- Touch targets: 44x44px minimum
|
|
130
|
+
- Skip link: `<a href="#main" class="sr-only focus:not-sr-only">` as first focusable element
|
|
131
|
+
- `<html lang="en">` set
|
|
132
|
+
- Color never the sole information carrier — icons, text, patterns as supplements
|
|
133
|
+
- `aria-live="polite"` for toast notifications and dynamic content updates
|
|
134
|
+
|
|
135
|
+
### 8. Responsive (Mobile-First)
|
|
136
|
+
|
|
137
|
+
- Base styles for mobile (320px), scale up with `min-width` breakpoints
|
|
138
|
+
- Test at: 320px (small phone), 375px (iPhone), 768px (iPad), 1024px (laptop), 1440px (desktop)
|
|
139
|
+
- No horizontal scroll at any viewport
|
|
140
|
+
- Navigation: hamburger/drawer on mobile, full horizontal on desktop
|
|
141
|
+
- Stack on mobile, expand on desktop
|
|
142
|
+
- Fluid typography with `clamp()`
|
|
143
|
+
- Images: `max-width: 100%`, responsive `srcset`, `next/image` with width/height
|
|
144
|
+
- Tables: card layout or horizontal scroll on mobile
|
|
145
|
+
|
|
146
|
+
### 9. Harden (Edge Cases)
|
|
147
|
+
|
|
148
|
+
After all visual work, stress-test:
|
|
149
|
+
- Long text: does a 200-character username break the layout?
|
|
150
|
+
- Empty everywhere: all lists empty, all data missing — does it still make sense?
|
|
151
|
+
- Error everywhere: every fetch fails — are error states visible and helpful?
|
|
152
|
+
- 320px viewport: nothing overflows, nothing clips, nothing overlaps
|
|
153
|
+
- Keyboard only: Tab through the entire app — can you reach everything? Is focus visible?
|
|
154
|
+
- Slow network: are loading states visible? Does content stream in or flash?
|
|
155
|
+
|
|
156
|
+
### 10. Verify & Ship
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx tsc --noEmit 2>&1 | head -20
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Fix any TypeScript errors.
|
|
131
163
|
|
|
132
164
|
```bash
|
|
133
165
|
git add {changed files}
|
|
134
|
-
git commit -m "polish: design
|
|
166
|
+
git commit -m "polish: design pass — typography, color, states, motion, responsive, a11y"
|
|
135
167
|
```
|
|
136
168
|
|
|
137
169
|
```bash
|
|
138
170
|
node ~/.claude/bin/state.js transition --to polished
|
|
139
171
|
```
|
|
140
|
-
Do NOT manually edit STATE.md or tracking.json — state.js handles both.
|
|
141
|
-
|
|
142
|
-
### 6. Report
|
|
143
172
|
|
|
144
173
|
```bash
|
|
145
174
|
node ~/.claude/bin/qualia-ui.js divider
|
|
146
|
-
node ~/.claude/bin/qualia-ui.js
|
|
147
|
-
node ~/.claude/bin/qualia-ui.js ok "Typography
|
|
148
|
-
node ~/.claude/bin/qualia-ui.js ok "Color
|
|
149
|
-
node ~/.claude/bin/qualia-ui.js ok "Layout
|
|
150
|
-
node ~/.claude/bin/qualia-ui.js ok "States
|
|
151
|
-
node ~/.claude/bin/qualia-ui.js ok "Motion
|
|
152
|
-
node ~/.claude/bin/qualia-ui.js ok "Accessibility
|
|
153
|
-
node ~/.claude/bin/qualia-ui.js ok "Responsive
|
|
154
|
-
node ~/.claude/bin/qualia-ui.js ok "
|
|
155
|
-
node ~/.claude/bin/qualia-ui.js ok "Hardening — {brief}"
|
|
175
|
+
node ~/.claude/bin/qualia-ui.js ok "AI slop: killed"
|
|
176
|
+
node ~/.claude/bin/qualia-ui.js ok "Typography: {brief}"
|
|
177
|
+
node ~/.claude/bin/qualia-ui.js ok "Color: {brief}"
|
|
178
|
+
node ~/.claude/bin/qualia-ui.js ok "Layout: {brief}"
|
|
179
|
+
node ~/.claude/bin/qualia-ui.js ok "States: {brief}"
|
|
180
|
+
node ~/.claude/bin/qualia-ui.js ok "Motion: {brief}"
|
|
181
|
+
node ~/.claude/bin/qualia-ui.js ok "Accessibility: {brief}"
|
|
182
|
+
node ~/.claude/bin/qualia-ui.js ok "Responsive: {brief}"
|
|
183
|
+
node ~/.claude/bin/qualia-ui.js ok "Hardened: {brief}"
|
|
156
184
|
node ~/.claude/bin/qualia-ui.js end "POLISHED" "/qualia-ship"
|
|
157
185
|
```
|
|
186
|
+
|
|
187
|
+
## Rules
|
|
188
|
+
|
|
189
|
+
1. **Read before write.** Understand every file before changing it.
|
|
190
|
+
2. **DESIGN.md is law.** If it exists, follow it. Don't override client decisions.
|
|
191
|
+
3. **Don't break functionality.** Only change styling, never logic.
|
|
192
|
+
4. **AI slop is a bug.** Generic fonts, card grids, blue-purple gradients, centered-everything — treat these as defects, not preferences.
|
|
193
|
+
5. **TypeScript must pass** after every change.
|
|
194
|
+
6. **One commit** at the end — not per category.
|
|
@@ -73,26 +73,35 @@ git commit -m "report: session {YYYY-MM-DD}"
|
|
|
73
73
|
git push
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
### 5. Upload to ERP
|
|
76
|
+
### 5. Upload to ERP (if enabled)
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Read `~/.claude/.qualia-config.json` and check the `erp` object:
|
|
79
|
+
- If `erp.enabled` is `false`, skip this step and print: "ERP upload skipped (disabled in config)."
|
|
80
|
+
- If `erp.enabled` is `true` (default) or the `erp` field is missing (backward compatibility), proceed with the upload.
|
|
79
81
|
|
|
80
82
|
```bash
|
|
81
|
-
|
|
83
|
+
# Read ERP config
|
|
84
|
+
ERP_URL=$(node -e "try{const c=JSON.parse(require('fs').readFileSync(require('os').homedir()+'/.claude/.qualia-config.json','utf8'));console.log(c.erp?.url||'https://portal.qualiasolutions.net')}catch{console.log('https://portal.qualiasolutions.net')}")
|
|
85
|
+
ERP_ENABLED=$(node -e "try{const c=JSON.parse(require('fs').readFileSync(require('os').homedir()+'/.claude/.qualia-config.json','utf8'));console.log(c.erp?.enabled!==false)}catch{console.log('true')}")
|
|
86
|
+
|
|
82
87
|
API_KEY=$(cat ~/.claude/.erp-api-key 2>/dev/null)
|
|
83
88
|
REPORT_FILE=".planning/reports/report-{date}.md"
|
|
84
89
|
EMAIL=$(git config user.email)
|
|
85
90
|
PROJECT=$(basename $(pwd))
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
# Only upload if ERP is enabled
|
|
93
|
+
if [ "$ERP_ENABLED" = "true" ]; then
|
|
94
|
+
curl -s -X POST "$ERP_URL/api/claude/report-upload" \
|
|
95
|
+
-H "X-API-Key: $API_KEY" \
|
|
96
|
+
-F "file=@$REPORT_FILE" \
|
|
97
|
+
-F "employee_email=$EMAIL" \
|
|
98
|
+
-F "project_name=$PROJECT"
|
|
99
|
+
fi
|
|
92
100
|
```
|
|
93
101
|
|
|
94
102
|
If the upload succeeds, print: "Report uploaded to ERP. You can now clock out."
|
|
95
103
|
If it fails (no API key, network error), print the error and tell the employee to ask Fawzi.
|
|
104
|
+
If ERP is disabled, print: "ERP upload skipped (disabled in config)."
|
|
96
105
|
|
|
97
106
|
### 6. Update State
|
|
98
107
|
|
|
@@ -1,76 +1,161 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qualia-review
|
|
3
|
-
description: "Production audit
|
|
3
|
+
description: "Production audit with scored diagnostics. Runs real commands, scores findings by severity. Trigger on 'review', 'audit', 'code review', 'security check', 'production check'."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /qualia-review — Production Audit
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Runs real diagnostic commands and scores every finding. Not a checklist — an executable audit.
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
12
|
-
- `/qualia-review` —
|
|
13
|
-
- `/qualia-review --web` —
|
|
14
|
-
- `/qualia-review --ai` — AI/voice agent
|
|
12
|
+
- `/qualia-review` — Full audit (security + quality + performance)
|
|
13
|
+
- `/qualia-review --web` — Adds web-specific checks (headers, CORS, vitals)
|
|
14
|
+
- `/qualia-review --ai` — Adds AI/voice agent checks (prompt safety, latency)
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Process
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
node ~/.claude/bin/qualia-ui.js banner review
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
### 0. Load Context
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
5. **Test Coverage** — Gaps, edge cases, test quality
|
|
24
|
+
```bash
|
|
25
|
+
cat ~/.claude/knowledge/common-fixes.md 2>/dev/null
|
|
26
|
+
cat ~/.claude/knowledge/learned-patterns.md 2>/dev/null
|
|
27
|
+
```
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
Detect project shape:
|
|
30
|
+
```bash
|
|
31
|
+
ls package.json next.config.* tsconfig.json supabase/ app/ src/ 2>/dev/null
|
|
32
|
+
```
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
### 1. Security Scan
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
Run every command. Record each finding with severity.
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
```bash
|
|
39
|
+
# CRITICAL: service_role in client code
|
|
40
|
+
grep -rn "service_role" --include="*.ts" --include="*.tsx" --include="*.js" app/ components/ src/ lib/ 2>/dev/null | grep -v node_modules | grep -v "\.server\.\|[\\/]server[\\/]\|[\\/]app[\\/]api[\\/]\|route\.\|middleware\."
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
# CRITICAL: hardcoded secrets
|
|
43
|
+
grep -rn "sk_live\|sk_test\|SUPABASE_SERVICE_ROLE\|eyJhbGciOi" --include="*.ts" --include="*.tsx" --include="*.js" app/ components/ src/ lib/ 2>/dev/null | grep -v node_modules | grep -v "\.env"
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
# CRITICAL: dangerous patterns
|
|
46
|
+
grep -rn "dangerouslySetInnerHTML\|eval(" --include="*.ts" --include="*.tsx" --include="*.js" app/ components/ src/ 2>/dev/null | grep -v node_modules
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
# CRITICAL: .env files tracked in git
|
|
49
|
+
git ls-files | grep -i "\.env" | grep -v "\.example\|\.template\|\.sample"
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
# HIGH: API routes without auth
|
|
52
|
+
for f in $(find app/api -name "route.ts" -o -name "route.js" 2>/dev/null); do
|
|
53
|
+
grep -qL "getUser\|getSession\|auth()\|createClient" "$f" && echo "UNPROTECTED: $f"
|
|
54
|
+
done
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
# HIGH: API routes without input validation
|
|
57
|
+
for f in $(find app/api -name "route.ts" -o -name "route.js" 2>/dev/null); do
|
|
58
|
+
grep -L "z\.\|zod\|Zod\|parse\|safeParse" "$f" 2>/dev/null
|
|
59
|
+
done
|
|
47
60
|
|
|
48
|
-
|
|
61
|
+
# HIGH: client-side database mutations
|
|
62
|
+
grep -rn "\.insert\|\.update\|\.delete\|\.upsert" --include="*.tsx" --include="*.jsx" app/ components/ 2>/dev/null | grep -v "use server" | grep -v "\.server\."
|
|
49
63
|
|
|
50
|
-
|
|
64
|
+
# MEDIUM: npm vulnerabilities
|
|
65
|
+
npm audit --json 2>/dev/null | node -e "try{const d=JSON.parse(require('fs').readFileSync(0,'utf8'));const v=d.metadata?.vulnerabilities||{};console.log('critical:',v.critical||0,'high:',v.high||0,'moderate:',v.moderate||0)}catch{console.log('audit unavailable')}"
|
|
66
|
+
```
|
|
51
67
|
|
|
52
|
-
|
|
68
|
+
### 2. Code Quality Scan
|
|
53
69
|
|
|
54
|
-
|
|
70
|
+
```bash
|
|
71
|
+
# TypeScript errors (HIGH if >0)
|
|
72
|
+
npx tsc --noEmit 2>&1 | grep -c "error TS"
|
|
55
73
|
|
|
56
|
-
|
|
74
|
+
# 'any' type usage (MEDIUM — count)
|
|
75
|
+
grep -rn ": any\| as any" --include="*.ts" --include="*.tsx" app/ components/ src/ lib/ 2>/dev/null | grep -v node_modules | wc -l
|
|
57
76
|
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
- **Where** — `file:line`
|
|
61
|
-
- **Fix** — concrete suggestion
|
|
62
|
-
- **Severity** — CRITICAL / HIGH / MEDIUM / LOW
|
|
77
|
+
# Empty catch blocks (HIGH)
|
|
78
|
+
grep -rn "catch\s*{}\|catch\s*(.*)\s*{\s*}" --include="*.ts" --include="*.tsx" app/ components/ src/ lib/ 2>/dev/null | grep -v node_modules | head -10
|
|
63
79
|
|
|
64
|
-
|
|
80
|
+
# TODO/FIXME left in code (LOW — count)
|
|
81
|
+
grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.ts" --include="*.tsx" app/ components/ src/ lib/ 2>/dev/null | grep -v node_modules | wc -l
|
|
65
82
|
|
|
83
|
+
# console.log in production code (LOW — count)
|
|
84
|
+
grep -rn "console\.log" --include="*.ts" --include="*.tsx" app/ components/ src/ 2>/dev/null | grep -v node_modules | wc -l
|
|
66
85
|
```
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
|
|
87
|
+
### 3. Performance Scan
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Build output — route sizes and first load JS
|
|
91
|
+
npx next build 2>&1 | grep -E "Route|First Load|shared by all|○|●|ƒ|λ" | tail -25
|
|
92
|
+
|
|
93
|
+
# Heavy files (>300 lines often means split needed)
|
|
94
|
+
find app/ components/ src/ -name "*.tsx" -o -name "*.ts" 2>/dev/null | xargs wc -l 2>/dev/null | sort -rn | head -10
|
|
95
|
+
|
|
96
|
+
# Missing next/image (MEDIUM)
|
|
97
|
+
grep -rn "<img " --include="*.tsx" --include="*.jsx" app/ components/ src/ 2>/dev/null | grep -v "next/image" | wc -l
|
|
98
|
+
|
|
99
|
+
# Client component ratio
|
|
100
|
+
echo "use client: $(grep -rl "'use client'" --include="*.tsx" app/ components/ src/ 2>/dev/null | wc -l)"
|
|
101
|
+
echo "total tsx: $(find app/ components/ src/ -name '*.tsx' 2>/dev/null | wc -l)"
|
|
102
|
+
|
|
103
|
+
# Sequential data fetching (HIGH)
|
|
104
|
+
grep -rn "const.*=.*await" --include="*.tsx" --include="*.ts" app/ src/ 2>/dev/null | grep -v "Promise.all\|Promise.allSettled" | head -10
|
|
76
105
|
```
|
|
106
|
+
|
|
107
|
+
### 4. Score and Report
|
|
108
|
+
|
|
109
|
+
Write to `.planning/REVIEW.md`:
|
|
110
|
+
|
|
111
|
+
```markdown
|
|
112
|
+
# Production Review — {YYYY-MM-DD}
|
|
113
|
+
|
|
114
|
+
## Summary
|
|
115
|
+
| Category | Critical | High | Medium | Low | Score |
|
|
116
|
+
|----------|----------|------|--------|-----|-------|
|
|
117
|
+
| Security | {n} | {n} | {n} | {n} | {1-5} |
|
|
118
|
+
| Quality | {n} | {n} | {n} | {n} | {1-5} |
|
|
119
|
+
| Perf | {n} | {n} | {n} | {n} | {1-5} |
|
|
120
|
+
| **Total** | {n} | {n} | {n} | {n} | **{avg}/5** |
|
|
121
|
+
|
|
122
|
+
## Findings
|
|
123
|
+
|
|
124
|
+
### CRITICAL
|
|
125
|
+
- **{title}** — `{file}:{line}` — {what's wrong} — Fix: {how}
|
|
126
|
+
|
|
127
|
+
### HIGH
|
|
128
|
+
- ...
|
|
129
|
+
|
|
130
|
+
### MEDIUM
|
|
131
|
+
- ...
|
|
132
|
+
|
|
133
|
+
### LOW
|
|
134
|
+
- ...
|
|
135
|
+
|
|
136
|
+
## Verdict
|
|
137
|
+
{PASS: no critical/high | FAIL: N blockers — fix before /qualia-ship}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Scoring:**
|
|
141
|
+
- 5 = zero high/critical, fewer than 3 medium
|
|
142
|
+
- 4 = zero critical, 1 high or fewer than 5 medium
|
|
143
|
+
- 3 = zero critical, 2-3 high
|
|
144
|
+
- 2 = 1 critical or 4+ high
|
|
145
|
+
- 1 = multiple critical
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node ~/.claude/bin/qualia-ui.js divider
|
|
149
|
+
node ~/.claude/bin/qualia-ui.js info "Security: {score}/5 ({n} findings)"
|
|
150
|
+
node ~/.claude/bin/qualia-ui.js info "Quality: {score}/5 ({n} findings)"
|
|
151
|
+
node ~/.claude/bin/qualia-ui.js info "Perf: {score}/5 ({n} findings)"
|
|
152
|
+
node ~/.claude/bin/qualia-ui.js end "REVIEW: {PASS|FAIL}" "{next command}"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Rules
|
|
156
|
+
|
|
157
|
+
1. **Run every command.** Don't skip scans because "the code looks clean."
|
|
158
|
+
2. **Every finding gets a severity.** No prose — CRITICAL/HIGH/MEDIUM/LOW.
|
|
159
|
+
3. **Every finding gets a fix suggestion.** Not just "this is bad" — say what to do.
|
|
160
|
+
4. **Review detects. It does NOT fix.** This is an audit, not a refactor. Tell the user what to fix.
|
|
161
|
+
5. **CRITICAL or HIGH = deploy blocker.** `/qualia-ship` checks for these.
|