qualia-framework 4.3.0 → 4.5.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 (42) hide show
  1. package/CLAUDE.md +13 -1
  2. package/README.md +16 -13
  3. package/agents/builder.md +12 -20
  4. package/agents/plan-checker.md +18 -0
  5. package/agents/planner.md +9 -0
  6. package/agents/verifier.md +62 -0
  7. package/bin/agent-runs.js +233 -0
  8. package/bin/cli.js +225 -21
  9. package/bin/install.js +25 -5
  10. package/bin/plan-contract.js +220 -0
  11. package/bin/slop-detect.mjs +357 -0
  12. package/bin/state.js +199 -10
  13. package/docs/agent-runs.md +273 -0
  14. package/docs/erp-contract.md +5 -0
  15. package/docs/plan-contract.md +321 -0
  16. package/hooks/auto-update.js +3 -7
  17. package/hooks/pre-compact.js +22 -11
  18. package/hooks/pre-deploy-gate.js +16 -2
  19. package/hooks/pre-push.js +22 -2
  20. package/hooks/stop-session-log.js +1 -1
  21. package/package.json +8 -2
  22. package/rules/design-brand.md +110 -0
  23. package/rules/design-laws.md +144 -0
  24. package/rules/design-product.md +110 -0
  25. package/rules/design-rubric.md +153 -0
  26. package/skills/qualia-build/SKILL.md +5 -5
  27. package/skills/qualia-flush/SKILL.md +1 -1
  28. package/skills/qualia-new/SKILL.md +40 -3
  29. package/skills/qualia-polish/SKILL.md +180 -136
  30. package/skills/qualia-quick/SKILL.md +1 -1
  31. package/skills/qualia-report/SKILL.md +25 -5
  32. package/skills/qualia-ship/SKILL.md +12 -10
  33. package/skills/zoho-workflow/SKILL.md +64 -0
  34. package/templates/DESIGN.md +229 -435
  35. package/templates/PRODUCT.md +95 -0
  36. package/templates/help.html +13 -7
  37. package/tests/bin.test.sh +6 -3
  38. package/tests/hooks.test.sh +9 -20
  39. package/tests/lib.test.sh +217 -0
  40. package/tests/runner.js +96 -75
  41. package/tests/state.test.sh +4 -3
  42. package/skills/qualia-design/SKILL.md +0 -169
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: qualia-polish
3
- description: "Design and UX pass — anti-AI-slop, genuine craft, responsive, accessible. Run after all phases are verified."
3
+ description: "Scope-adaptive design pass — works on a single component, a route, the whole app, or a ground-up redesign. Trigger on 'polish', 'design pass', 'fix the design', 'redesign', 'critique', 'audit design', 'looks ugly', 'make it look better'. Replaces both /qualia-polish and /qualia-design from earlier versions."
4
4
  allowed-tools:
5
5
  - Bash
6
6
  - Read
@@ -9,207 +9,251 @@ allowed-tools:
9
9
  - Grep
10
10
  - Glob
11
11
  - Agent
12
+ argument-hint: "[file|route|--redesign|--critique|--quick] [--register=brand|product]"
12
13
  ---
13
14
 
14
- # /qualia-polish — Design Pass
15
+ # /qualia-polish — Scope-Adaptive Design Pass
15
16
 
16
- Makes it look like a human designer built it. Kills AI slop. Run after all feature phases are verified.
17
+ One command. Six scopes. Use it whenever you need design work from a 30-second component touch-up to a 30-minute ground-up redesign.
17
18
 
18
- ## The Standard
19
+ ## Scopes
19
20
 
20
- Every site Qualia ships must feel **designed, not generated.** AI-generated sites have tells:
21
- - Identical card grids with rounded corners and soft shadows
22
- - Blue-purple gradients on everything
23
- - Inter/system-ui font with no hierarchy
24
- - Generic hero with centered text and a stock gradient background
25
- - Fixed-width containers leaving dead space on wide screens
26
- - No motion, no personality, no opinion
27
- - Perfect symmetry everywhere (real design has tension)
21
+ The first argument selects the scope. Stage selection follows from scope.
28
22
 
29
- **Kill all of these.** A Qualia site should make someone ask "who designed this?" — not "which template is this?"
23
+ | Invocation | Scope | Runtime | Stages |
24
+ |---|---|---|---|
25
+ | `/qualia-polish src/components/Button.tsx` | **Component** | ~30s | 0, 2, 3, 7 |
26
+ | `/qualia-polish app/dashboard` | **Section** | ~3m | 0, 1 (light), 2, 3, 4, 7 |
27
+ | `/qualia-polish` | **App** | ~12m | all 7 (fan-out batches of 5) |
28
+ | `/qualia-polish --redesign` | **Redesign** | ~30m | all + Stage 1 mandatory + 2 vision iterations |
29
+ | `/qualia-polish --critique` | **Critique** | read-only | 0, 4, 5 (no edits) |
30
+ | `/qualia-polish --quick` | **Quick** | ~1m | 0, 2, 7 (gates only, no vision loop) |
30
31
 
31
- ## Process
32
+ Other flags: `--register=brand|product` to override register inference.
32
33
 
33
- ```bash
34
- node ~/.claude/bin/qualia-ui.js banner polish
34
+ ## Setup gates (non-optional, every scope)
35
+
36
+ Before any work — design or otherwise — pass these gates. Skipping them produces generic output that ignores the project.
37
+
38
+ | Gate | Required check | If fail |
39
+ |---|---|---|
40
+ | Substrate | `rules/design-laws.md`, `design-brand.md`, `design-product.md`, `design-rubric.md` exist and have been read | Read them. |
41
+ | PRODUCT | `PRODUCT.md` exists at project root, has `register:` field, and is not placeholder (`[TODO]` markers, < 200 chars) | Run setup: ask 5 questions and generate it. Never synthesize from prompt alone. |
42
+ | DESIGN | `DESIGN.md` exists. If missing on App / Redesign scope, BLOCK and run setup. If missing on Component / Section / Critique / Quick scope, NUDGE and proceed. | Generate from PRODUCT.md + 3 questions. |
43
+ | Slop-detect | `bin/slop-detect.mjs` is callable | Install or pull from framework. |
44
+ | Mutation | All gates above pass | Do not edit project files yet. |
45
+
46
+ State this preflight explicitly before editing:
47
+
48
+ ```
49
+ POLISH_PREFLIGHT: substrate=pass product=pass design=pass|nudged slop_detect=pass mutation=open
35
50
  ```
36
51
 
37
- ### 0. Load Design Context
52
+ ## Process
38
53
 
39
54
  ```bash
40
- cat .planning/DESIGN.md 2>/dev/null || echo "NO_DESIGN"
55
+ node ~/.claude/bin/qualia-ui.js banner polish
41
56
  ```
42
57
 
43
- If DESIGN.md exists it's the standard. Read ALL 12 sections. Key sections for polish:
44
- - §1 Visual Theme — the feel and signature details
45
- - §2 Color Palette — exact hex values, CSS variables, contrast ratios
46
- - §3 Typography — hierarchy table with exact sizes/weights/spacing
47
- - §4 Components — exact button/card/input/badge specs
48
- - §5 Layout — spacing scale, grid strategy
49
- - §6 Depth & Elevation — shadow levels with exact rgba values
50
- - §7 Do's/Don'ts — brand-specific guardrails
51
- - §9 Agent Prompt Guide — quick reference for common patterns
52
- - §10 Accessibility — WCAG checklist
53
- - §11 Hardening — stress-test criteria
54
- - §12 Anti-Slop Detection — grep patterns for automated checks
58
+ ### Stage 0 Direction commit (mandatory; light on small scopes)
55
59
 
56
- If no DESIGN.md use `rules/frontend.md` defaults.
60
+ Read PRODUCT.md and DESIGN.md. Identify the **register** — Brand (marketing/landing/portfolio) or Product (app/admin/dashboard). Priority for register inference:
57
61
 
58
- Read EVERY frontend file before modifying. No blind edits.
62
+ 1. The path of the target file/route (e.g., `/marketing/*` brand; `/app/*` → product)
63
+ 2. `register:` field in PRODUCT.md
64
+ 3. `--register` flag override
59
65
 
60
- ### 1. AI Slop Detector
66
+ For App / Redesign scope, write the brief BEFORE any code (use `ultrathink`):
61
67
 
62
- Run these checks first. Any hits = mandatory fixes.
68
+ ```
69
+ Aesthetic direction: {editorial · brutalist · luxury · maximalist · retro-futuristic · organic · terminal-native · ...}
70
+ Color strategy: {Restrained · Committed · Full palette · Drenched}
71
+ Scene sentence: {who uses this, where, ambient light, mood — concrete}
72
+ Differentiation: {what someone remembers 24 hours later}
73
+ ```
63
74
 
64
- ```bash
65
- # Generic fonts (the #1 AI tell)
66
- 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
75
+ For Component / Section / Quick scope, the brief is implicit (loaded from DESIGN.md). Skip the ultrathink step but cite the relevant DESIGN.md tokens you'll touch.
67
76
 
68
- # Hardcoded max-width containers (screams template)
69
- 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
77
+ For Critique scope, no commit needed — you're scoring, not editing.
70
78
 
71
- # Blue-purple gradients
72
- 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
79
+ ### Stage 1 — Static gates (no browser, deterministic)
73
80
 
74
- # Card grid monotony (same card component repeated in a grid)
75
- grep -rn "grid-cols-3\|grid-cols-4" --include="*.tsx" app/ components/ src/ 2>/dev/null | head -5
81
+ ```bash
82
+ # Anti-pattern scan (the slop-detect script)
83
+ node bin/slop-detect.mjs {target_paths}
76
84
 
77
- # Generic hero patterns
78
- grep -rn "text-center.*mx-auto\|Hero\|hero" --include="*.tsx" app/ components/ src/ 2>/dev/null | head -5
85
+ # TypeScript still compiles?
86
+ npx tsc --noEmit 2>&1 | head -20
79
87
 
80
- # Scattered hardcoded colors (no design system)
81
- grep -rn "text-\[#\|bg-\[#\|border-\[#\|color:.*#\|background:.*#" --include="*.tsx" app/ components/ src/ 2>/dev/null | wc -l
88
+ # Token enforcement (if Stylelint configured)
89
+ npx stylelint "src/**/*.css" 2>&1 | head -20 # optional, skip silently if not configured
82
90
  ```
83
91
 
84
- **Every hit gets fixed.** Not flagged fixed.
92
+ ANY critical-severity slop hit = mandatory fix before proceeding. Don't stage anything until critical findings are zero.
85
93
 
86
- ### 2. Typography Pass
94
+ ### Stage 2 Generation (forked subagents, batches of 5 files)
87
95
 
88
- **Goal:** A reader should feel the type was chosen, not defaulted.
96
+ For App / Redesign / Section scope on multi-file work:
89
97
 
90
- - 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.
91
- - Establish clear hierarchy: display (hero text) h1 h2 h3 body caption
92
- - Body: 16px minimum, line-height 1.5-1.7
93
- - Headings: tighter line-height (1.1-1.3), negative letter-spacing (-0.02em) for display sizes
94
- - Weight hierarchy: Regular (400) for body, Medium (500) for labels, Semibold (600) for headings, Bold (700) for display
95
- - Prose content: `max-width: 65ch`. Everything else: fluid full-width.
96
- - Use `clamp()` for fluid sizing: `clamp(2rem, 1rem + 3vw, 3.75rem)` for h1
98
+ - Count target files. If 5, process in main context.
99
+ - If > 5, fan out: spawn one Agent per batch of 5 files IN THE SAME RESPONSE TURN (parallel execution).
100
+ - If the conversation already contains design-taste discussion (font/color/motion preferences threaded across multiple turns), prefer **forked subagents** (`--fork-session`) so they inherit the taste context. Otherwise, blank-context fan-out is fine for mechanical fixes.
97
101
 
98
- ### 3. Color & Surfaces
102
+ Each agent receives:
103
+ - `rules/design-laws.md` + the matching register file
104
+ - `PRODUCT.md` + `DESIGN.md` (inlined)
105
+ - Its 5 files (paths + contents)
106
+ - Instruction: apply the Design Quality Rubric per file. Fix every dimension scoring < 3. Make literal edits. Do NOT change logic — only styling.
99
107
 
100
- **Goal:** A palette that looks intentional, not random.
108
+ For Component scope: do the work in main context. Read, fix, verify.
101
109
 
102
- - Define all colors as CSS variables or Tailwind config — zero scattered hex values
103
- - One dominant brand color. One sharp accent for CTAs that pops against the page.
104
- - Surfaces: layer them. Background → card → elevated card. Use subtle shade differences, not just white-on-white.
105
- - Dark mode (if present): rethink surfaces, don't just invert. Slightly reduce contrast. Use darker brand colors, not just white→black swap.
106
- - Semantic colors with non-color indicators: success (green + checkmark), error (red + icon), warning (amber + triangle)
107
- - Verify WCAG AA: 4.5:1 for normal text, 3:1 for large text (18px+ bold or 24px+)
110
+ **Apply fixes scoped by what's missing:**
108
111
 
109
- ### 4. Layout & Spacing
112
+ | If the file scores low on… | Apply fix from… |
113
+ |---|---|
114
+ | Typography | DESIGN.md §3 — font tokens, scale, weight hierarchy, tabular numerals |
115
+ | Color cohesion | DESIGN.md §2 — OKLCH tokens, replace hex, verify contrast |
116
+ | Spatial rhythm | DESIGN.md §4 — fluid spacing scale, vary by content |
117
+ | Layout originality | register file (brand or product) — kill three-column grids, vary layout |
118
+ | Shadow hierarchy | DESIGN.md §6 — elevation tokens, brand-tinted shadows |
119
+ | Motion intent | DESIGN.md §7 — easing, stagger, signature motion |
120
+ | Microcopy | rename "Get Started" / "Learn More" to action-named CTAs |
121
+ | Container depth | flatten card-on-card; remove side-stripe borders |
110
122
 
111
- **Goal:** Full-width, fluid, generous. No dead space gutters.
123
+ ### Stage 3 Runtime gates (App / Section / Redesign only — needs dev server)
112
124
 
113
- - Full-width layouts with fluid padding: `clamp(1rem, 5vw, 4rem)` horizontal
114
- - 8px spacing grid: 4, 8, 12, 16, 24, 32, 48, 64, 96
115
- - Tight spacing within groups (related items). Generous spacing between sections.
116
- - Break symmetry where it serves the design — offset grids, overlapping elements, diagonal flow
117
- - Varied layouts: not every section should be a centered-text-with-cards-below. Use side-by-side, staggered, asymmetric, full-bleed.
118
- - Section spacing: `clamp(2rem, 8vw, 6rem)` vertical padding
125
+ If a dev server is up at `http://localhost:3000` (or detected via `lsof` on common ports):
119
126
 
120
- ### 5. Interactive States
127
+ ```bash
128
+ # Lighthouse-CI — numeric a11y/perf/best-practices
129
+ npx lhci autorun \
130
+ --collect.url=http://localhost:3000{route} \
131
+ --collect.numberOfRuns=1 \
132
+ --assert.assertions='{
133
+ "categories:accessibility": ["error", {"minScore": 0.9}],
134
+ "categories:performance": ["warn", {"minScore": 0.7}],
135
+ "categories:best-practices": ["warn", {"minScore": 0.8}]
136
+ }' 2>&1 | tail -30
137
+
138
+ # axe-core direct (catches what Lighthouse misses)
139
+ npx @axe-core/cli http://localhost:3000{route} --exit 2>&1 | tail -30
140
+ ```
141
+
142
+ If Lighthouse/axe not installed, skip silently and note in the report. Don't fail the polish over missing tooling.
121
143
 
122
- **Goal:** Every clickable thing responds. Every async operation shows progress.
144
+ If a11y < 90 OR axe critical/serious violations: **fix programmatically** (these are deterministic no vision needed). Re-run to confirm.
123
145
 
124
- - **Hover:** color shift or underline within 150ms ease-out. `cursor: pointer` on ALL clickables.
125
- - **Focus:** visible ring (2px+ offset, contrasting color). Never `outline: none` without replacement.
126
- - **Active/pressed:** subtle scale down (`transform: scale(0.98)`) or color shift.
127
- - **Disabled:** opacity 0.5 + `cursor: not-allowed` + `aria-disabled="true"`
128
- - **Loading:** skeleton shimmer or spinner on every async operation. Never a blank void.
129
- - **Empty:** helpful message + CTA on empty lists/tables. Not just "No results."
130
- - **Error:** user-friendly message + recovery action. Not raw error text. Use `aria-live="assertive"`.
146
+ ### Stage 4 Vision loop (Redesign scope only max 2 iterations)
131
147
 
132
- ### 6. Motion & Personality
148
+ Use Anthropic's `webapp-testing` skill (Playwright). Capture screenshots at 3 viewports: 375 / 768 / 1280. Single browser (Chromium) is fine in 2026 — cross-browser CSS rendering differences are vanishingly rare.
133
149
 
134
- **Goal:** The site feels alive, not static. But tasteful — not a circus.
150
+ ```
151
+ viewports: [
152
+ { width: 375, height: 812, name: 'mobile' },
153
+ { width: 768, height: 1024, name: 'tablet' },
154
+ { width: 1280, height: 800, name: 'desktop' }
155
+ ]
156
+ ```
135
157
 
136
- - Page load: stagger children entrance (50-80ms delay between items, `fadeUp` animation, 300ms)
137
- - Hover transitions: 150-200ms ease-out
138
- - Section transitions: 300-500ms with `cubic-bezier(0.4, 0, 0.2, 1)`
139
- - One signature motion that gives the site personality (parallax, scroll-triggered reveal, magnetic buttons, morphing shapes)
140
- - **Always** `prefers-reduced-motion: reduce` — disable non-essential animation
141
- - CSS-only for static sites, `motion/react` (formerly Framer Motion) for React
158
+ For each iteration:
142
159
 
143
- ### 7. Accessibility (Non-Negotiable)
160
+ 1. Capture all 3 viewports
161
+ 2. Pass to a vision-model agent with `rules/design-rubric.md` as the prompt anchor + DESIGN.md as the spec
162
+ 3. The agent scores 8 dimensions, anchored 1-5, with evidence per dimension
163
+ 4. Apply fixes ONLY to dimensions scored 1 or 2 (don't nitpick 3s; prevents oscillation)
164
+ 5. STOP if: all dimensions ≥ 3 (success), OR any dimension regressed from previous iteration (regression-stop), OR 2 iterations reached (hard cap)
144
165
 
145
- - Semantic HTML: `nav`, `main`, `section`, `article`, `header`, `footer` not div soup
146
- - One `h1` per page, sequential heading order (no h1 → h3 skip)
147
- - All images: descriptive `alt` (or `alt=""` + `aria-hidden` if decorative)
148
- - All form inputs: visible `<label>` with `htmlFor` — not placeholder-only
149
- - All interactive elements: keyboard accessible (Tab, Enter, Escape, Arrow keys)
150
- - Touch targets: 44x44px minimum
151
- - Skip link: `<a href="#main" class="sr-only focus:not-sr-only">` as first focusable element
152
- - `<html lang="en">` set
153
- - Color never the sole information carrier — icons, text, patterns as supplements
154
- - `aria-live="polite"` for toast notifications and dynamic content updates
166
+ **The anchored rubric is mandatory.** Without it, vision models say "looks great!" to everything. Quote from the rubric prompt: "Score 3 = acceptable. Only 1-2 = must fix. 4-5 = exceeds. Default to 3 unless you can cite specific evidence."
155
167
 
156
- ### 8. Responsive (Mobile-First)
168
+ ### Stage 5 — Drift audit (App / Redesign / Critique scope)
157
169
 
158
- - Base styles for mobile (320px), scale up with `min-width` breakpoints
159
- - Test at: 320px (small phone), 375px (iPhone), 768px (iPad), 1024px (laptop), 1440px (desktop)
160
- - No horizontal scroll at any viewport
161
- - Navigation: hamburger/drawer on mobile, full horizontal on desktop
162
- - Stack on mobile, expand on desktop
163
- - Fluid typography with `clamp()`
164
- - Images: `max-width: 100%`, responsive `srcset`, `next/image` with width/height
165
- - Tables: card layout or horizontal scroll on mobile
170
+ Compare the implementation against DESIGN.md as source of truth. Flag deltas.
166
171
 
167
- ### 9. Harden (Edge Cases)
172
+ ```bash
173
+ # Find tokens used in code that don't exist in DESIGN.md
174
+ grep -rE "var\(--[a-z-]+\)" src/ app/ components/ 2>/dev/null | \
175
+ awk -F'var\\(--' '{print $2}' | awk -F'\\)' '{print $1}' | sort -u > /tmp/used-tokens
176
+ grep -E "^\s*--[a-z-]+:" templates/DESIGN.md | sed -E 's/.*--([a-z-]+):.*/\1/' | sort -u > /tmp/declared-tokens
177
+ comm -23 /tmp/used-tokens /tmp/declared-tokens > /tmp/orphan-tokens
178
+ [ -s /tmp/orphan-tokens ] && echo "── orphan tokens (used in code, missing from DESIGN.md) ──" && cat /tmp/orphan-tokens
179
+
180
+ # Find raw hex still appearing
181
+ grep -rnE "#[0-9a-fA-F]{6}\b" src/ app/ components/ --include="*.tsx" --include="*.css" 2>/dev/null | \
182
+ grep -v "shadow\|outline\|currentColor" | head -20
183
+ ```
168
184
 
169
- After all visual work, stress-test:
170
- - Long text: does a 200-character username break the layout?
171
- - Empty everywhere: all lists empty, all data missing — does it still make sense?
172
- - Error everywhere: every fetch fails — are error states visible and helpful?
173
- - 320px viewport: nothing overflows, nothing clips, nothing overlaps
174
- - Keyboard only: Tab through the entire app — can you reach everything? Is focus visible?
175
- - Slow network: are loading states visible? Does content stream in or flash?
185
+ Drift findings get reported, not auto-fixed (drift may be intentional). For Critique scope, this IS the deliverable.
176
186
 
177
- ### 10. Verify & Ship
187
+ ### Stage 6 — Verify (every scope)
178
188
 
179
189
  ```bash
180
- npx tsc --noEmit 2>&1 | head -20
190
+ # Re-run Stage 1 all critical findings must be zero
191
+ node bin/slop-detect.mjs {target_paths}
192
+ [ $? -eq 0 ] || { echo "polish failed — slop-detect still finds critical issues"; exit 1; }
193
+
194
+ # TypeScript clean
195
+ npx tsc --noEmit 2>&1 | tail -10
181
196
  ```
182
197
 
183
- Fix any TypeScript errors.
198
+ ### Stage 7 — Commit & state
199
+
200
+ For Critique scope: write the report to `.planning/polish-critique-{timestamp}.md` and STOP. Do not edit, do not commit.
201
+
202
+ For all other scopes:
184
203
 
185
204
  ```bash
186
- git add {changed files}
187
- git commit -m "polish: design pass typography, color, states, motion, responsive, a11y"
205
+ git add {modified files}
206
+ git -c user.name="Qualia Solutions" -c user.email="info@qualiasolutions.net" commit -m "$(cat <<'EOF'
207
+ polish({scope}): {brief summary}
208
+
209
+ - {key change 1}
210
+ - {key change 2}
211
+ - rubric scores: typography {N}, color {N}, ..., aggregate {N}/40
212
+
213
+ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
214
+ EOF
215
+ )"
188
216
  ```
189
217
 
218
+ If this is the FINAL polish before deploy (Handoff milestone, last phase):
219
+
190
220
  ```bash
191
221
  node ~/.claude/bin/state.js transition --to polished
192
222
  ```
193
223
 
224
+ Otherwise, no state transition (polish is now a normal verb, not a phase boundary).
225
+
226
+ ### Output
227
+
194
228
  ```bash
195
229
  node ~/.claude/bin/qualia-ui.js divider
196
- node ~/.claude/bin/qualia-ui.js ok "AI slop: killed"
197
- node ~/.claude/bin/qualia-ui.js ok "Typography: {brief}"
198
- node ~/.claude/bin/qualia-ui.js ok "Color: {brief}"
199
- node ~/.claude/bin/qualia-ui.js ok "Layout: {brief}"
200
- node ~/.claude/bin/qualia-ui.js ok "States: {brief}"
201
- node ~/.claude/bin/qualia-ui.js ok "Motion: {brief}"
202
- node ~/.claude/bin/qualia-ui.js ok "Accessibility: {brief}"
203
- node ~/.claude/bin/qualia-ui.js ok "Responsive: {brief}"
204
- node ~/.claude/bin/qualia-ui.js ok "Hardened: {brief}"
205
- node ~/.claude/bin/qualia-ui.js end "POLISHED" "/qualia-ship"
230
+ node ~/.claude/bin/qualia-ui.js ok "Scope: {component|section|app|redesign|critique|quick}"
231
+ node ~/.claude/bin/qualia-ui.js ok "Files: {N}"
232
+ node ~/.claude/bin/qualia-ui.js ok "Slop-detect: {N critical, M high, K medium}"
233
+ node ~/.claude/bin/qualia-ui.js ok "Rubric aggregate: {N}/40 (avg {N})"
234
+ node ~/.claude/bin/qualia-ui.js ok "Vision iterations: {0|1|2}"
235
+ node ~/.claude/bin/qualia-ui.js ok "Drift findings: {N}"
236
+ node ~/.claude/bin/qualia-ui.js end "POLISHED" "{next command — depends on context}"
206
237
  ```
207
238
 
208
239
  ## Rules
209
240
 
210
241
  1. **Read before write.** Understand every file before changing it.
211
- 2. **DESIGN.md is law.** If it exists, follow it. Don't override client decisions.
242
+ 2. **DESIGN.md is law.** If a project decision exists, follow it. Don't override.
212
243
  3. **Don't break functionality.** Only change styling, never logic.
213
- 4. **AI slop is a bug.** Generic fonts, card grids, blue-purple gradients, centered-everything treat these as defects, not preferences.
244
+ 4. **Slop is a bug.** Critical-severity findings block commit. No overriding the script.
214
245
  5. **TypeScript must pass** after every change.
215
- 6. **One commit** at the end — not per category.
246
+ 6. **One commit per polish run** — not per category, not per file.
247
+ 7. **Forked subagents inherit taste.** When the conversation has design discussion, fork. Otherwise blank-context.
248
+ 8. **The rubric is anchored.** Score = 3 means ships. Default there. Justify deviations.
249
+
250
+ ## Failure modes (handle gracefully)
251
+
252
+ | Symptom | Likely cause | Action |
253
+ |---|---|---|
254
+ | `bin/slop-detect.mjs not found` | Framework not installed in project | Run `npx qualia install` or pull script from framework repo |
255
+ | `PRODUCT.md missing` | Pre-v4.5.0 project | Run setup (ask 5 questions, generate). For Component scope, can proceed with nudge. |
256
+ | `Lighthouse not installed` | Optional tool | Skip Stage 3 numeric gates, note in report. Don't fail. |
257
+ | `webapp-testing skill not present` | Optional Anthropic skill | Skip Stage 4 vision loop on Redesign scope. Note in report. Recommend installing. |
258
+ | Vision loop oscillates between iterations | Rubric not anchored properly | Verify rubric prompt instructs "default to 3, only 1-2 = fix". Hard cap at 2 iterations. |
259
+ | User says "you missed X" after polish completes | Scope was too narrow | Re-run with wider scope (`/qualia-polish` whole-app). Don't argue scope. |
@@ -24,7 +24,7 @@ node ~/.claude/bin/qualia-ui.js banner quick
24
24
  2. **Build:** Do it directly — read before write, MVP only
25
25
  3. **Verify:** Run `npx tsc --noEmit`, test locally
26
26
  4. **Commit:** Atomic commit with clear message
27
- 5. **Update:** Update tracking.json notes field
27
+ 5. **Update:** Record the work through `state.js`
28
28
 
29
29
  End with:
30
30
  ```bash
@@ -134,7 +134,7 @@ SUBMITTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
134
134
  # returns a generic 401 that is hard to diagnose.
135
135
  if [ "$ERP_ENABLED" = "true" ] && [ -z "$API_KEY" ] && [ "$DRY_RUN" != "true" ]; then
136
136
  node ~/.claude/bin/qualia-ui.js warn "ERP API key missing (~/.claude/.erp-api-key is empty or unreadable). Skipping upload."
137
- node ~/.claude/bin/qualia-ui.js info "Ask Fawzi for the ERP key, save to ~/.claude/.erp-api-key, then re-run /qualia-report --upload-only."
137
+ node ~/.claude/bin/qualia-ui.js info "Ask Fawzi for the ERP key, run 'qualia-framework set-erp-key <key>', then run 'qualia-framework erp-ping'."
138
138
  ERP_ENABLED="false"
139
139
  fi
140
140
 
@@ -154,21 +154,41 @@ PAYLOAD=$(
154
154
  REPORT_FILE="$REPORT_FILE" \
155
155
  node -e "
156
156
  const fs = require('fs');
157
+ const path = require('path');
158
+ const os = require('os');
159
+ const { spawnSync } = require('child_process');
160
+ const git = (args) => {
161
+ const r = spawnSync('git', args, { encoding: 'utf8', timeout: 3000 });
162
+ return r.status === 0 ? r.stdout.trim() : '';
163
+ };
164
+ const repoSlug = (remote) => (remote || '')
165
+ .replace(/^git@github\\.com:/, 'github.com/')
166
+ .replace(/^https?:\\/\\//, '')
167
+ .replace(/\\.git$/, '')
168
+ .split('/')
169
+ .filter(Boolean)
170
+ .pop();
171
+ let config = {};
172
+ try {
173
+ config = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude/.qualia-config.json'), 'utf8'));
174
+ } catch {}
157
175
  const t = JSON.parse(fs.readFileSync('.planning/tracking.json', 'utf8'));
158
176
  const notes = fs.readFileSync(process.env.REPORT_FILE, 'utf8').substring(0, 60000);
159
177
  const commits = [];
160
178
  try {
161
- const { spawnSync } = require('child_process');
162
179
  const r = spawnSync('git', ['log', '--oneline', '--since=8 hours ago', '--format=%h'], { encoding: 'utf8', timeout: 3000 });
163
180
  if (r.stdout) commits.push(...r.stdout.trim().split('\n').filter(Boolean));
164
181
  } catch {}
182
+ const gitRemote = t.git_remote || git(['config', '--get', 'remote.origin.url']);
183
+ const projectKey = t.project_id || repoSlug(gitRemote) || require('path').basename(process.cwd());
165
184
  console.log(JSON.stringify({
166
185
  project: t.project || require('path').basename(process.cwd()),
167
- project_id: t.project_id || '',
168
- team_id: t.team_id || '',
169
- git_remote: t.git_remote || '',
186
+ project_id: projectKey,
187
+ team_id: t.team_id || 'qualia-solutions',
188
+ git_remote: gitRemote,
170
189
  client: t.client || '',
171
190
  client_report_id: process.env.CLIENT_REPORT_ID,
191
+ framework_version: config.version || '',
172
192
  milestone: t.milestone || 1,
173
193
  milestone_name: t.milestone_name || '',
174
194
  milestones: Array.isArray(t.milestones) ? t.milestones : [],
@@ -37,13 +37,14 @@ VERIFICATION=$(echo "$STATE" | node -e "try{const d=JSON.parse(require('fs').rea
37
37
  # verified+pass — final phase verified; skipping polish is allowed for hotfixes
38
38
  # Anything else (setup, planned, built, shipped, handed_off, verified+fail) is refused.
39
39
  if [ "$STATUS" != "polished" ] && ! { [ "$STATUS" = "verified" ] && [ "$VERIFICATION" = "pass" ]; }; then
40
+ if [ "${QUALIA_SHIP_FORCE:-0}" = "1" ]; then
41
+ node ~/.claude/bin/qualia-ui.js warn "Forced ship from state '$STATUS' (verification: ${VERIFICATION:-none}). Record the reason in the final report."
42
+ else
40
43
  node ~/.claude/bin/qualia-ui.js fail "Cannot ship from state '$STATUS' (verification: ${VERIFICATION:-none})."
41
44
  node ~/.claude/bin/qualia-ui.js info "Run /qualia-polish first, or /qualia-verify {phase} if verification is still pending."
42
- node ~/.claude/bin/qualia-ui.js info "Override: add --force to the skill invocation (hotfix escape hatch, use with care)."
43
- # The --force escape hatch exists for production hotfixes where the polished
44
- # state was never reached. The operator is expected to have read and
45
- # understood the pending verification findings.
45
+ node ~/.claude/bin/qualia-ui.js info "Hotfix override: set QUALIA_SHIP_FORCE=1 only when the user explicitly approved it."
46
46
  exit 1
47
+ fi
47
48
  fi
48
49
  ```
49
50
 
@@ -53,7 +54,8 @@ Run in sequence. Auto-fix failures (up to 2 attempts).
53
54
 
54
55
  ```bash
55
56
  npx tsc --noEmit # TypeScript — must pass
56
- npx eslint . --max-warnings 0 # Lint auto-fix first
57
+ if node -e "const p=require('./package.json');process.exit(p.scripts&&p.scripts.lint?0:1)"; then npm run lint; fi
58
+ if node -e "const p=require('./package.json');process.exit(p.scripts&&p.scripts.test?0:1)"; then npm test; fi
57
59
  npm run build # Build — must succeed
58
60
  ```
59
61
 
@@ -130,15 +132,15 @@ wrangler deploy # Cloudflare Workers
130
132
 
131
133
  ### 5. Post-Deploy Verification
132
134
 
133
- Read the deployed URL from `tracking.json.deployed_url` — set by the deploy tool's output parser, or passed via `--url` to this skill. Do NOT use a `{domain}` placeholder — that expects the LLM to hallucinate the URL, which is exactly the kind of silent fail the state guard above prevents.
135
+ Read the deployed URL from `tracking.json.deployed_url` or from an explicit user-provided URL. Do NOT use a `{domain}` placeholder — that expects the LLM to hallucinate the URL, which is exactly the kind of silent fail the state guard above prevents.
134
136
 
135
137
  ```bash
136
- # Read URL from tracking.json (set by /qualia-handoff or previous ship), or
137
- # let the operator pass it as an argument. Never assume a placeholder.
138
- URL=$(node -e "try{const t=JSON.parse(require('fs').readFileSync('.planning/tracking.json','utf8'));process.stdout.write(t.deployed_url||'')}catch{}")
138
+ # If the user invoked `/qualia-ship --url ...`, set QUALIA_SHIP_URL to that
139
+ # exact value before running this block. Otherwise use tracking.json.
140
+ URL="${QUALIA_SHIP_URL:-$(node -e 'try{const t=JSON.parse(require("fs").readFileSync(".planning/tracking.json","utf8"));process.stdout.write(t.deployed_url||"")}catch{}')}"
139
141
  if [ -z "$URL" ]; then
140
142
  node ~/.claude/bin/qualia-ui.js warn "No deployed_url in tracking.json — parse it from the deploy command output (vercel/supabase/wrangler all print the URL on success)."
141
- node ~/.claude/bin/qualia-ui.js info "Re-run with: /qualia-ship --url https://your-site.com"
143
+ node ~/.claude/bin/qualia-ui.js info "Re-run with: /qualia-ship --url https://your-site.com, then export that value as QUALIA_SHIP_URL for this check."
142
144
  exit 1
143
145
  fi
144
146
 
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: zoho-workflow
3
+ description: Zoho Invoice and Mail operations - create invoices, send emails, manage contacts. Use when Fawzi mentions invoicing, email, or Zoho.
4
+ tags: [zoho, invoice, email, billing, crm]
5
+ ---
6
+
7
+ # Zoho Workflow
8
+
9
+ ## Route invoicing through the ERP first
10
+
11
+ The Qualia ERP at `https://portal.qualiasolutions.net` owns the canonical invoice workflow. **Use it before reaching for raw Zoho MCP tools.**
12
+
13
+ For any "invoice X" request, the order of preference is:
14
+
15
+ 1. **ERP MCP** (preferred): `mcp__qualia-erp__create_invoice_draft` + `mcp__qualia-erp__create_invoice_cover_email_draft`. These wrap Zoho with the right templates, terms, VAT treatment, and matching cover email. Templates: `monthly_retainer` (Underdog pattern — retainer + SEO + usage credit), `simple_service` (Maison Maud / Armenius pattern — single line), `project_deposit` (50% upfront, requires proposal ref), `project_balance` (final 50%).
16
+ 2. **ERP UI** (when Fawzi is at his desk): `https://portal.qualiasolutions.net/admin?tab=finance` → "New invoice from template" button.
17
+ 3. **Raw Zoho Books MCP** (`mcp__claude_ai_Zoho_Books__*`): only for things the ERP doesn't expose yet — voiding, editing existing invoices, recording payments, expenses, contact management.
18
+
19
+ The full handoff runbook lives in the qualia-erp repo: `docs/finance-runbook.md`. It covers once-per-client setup, the 4 templates, the bulk monthly batch, and common errors.
20
+
21
+ **Why this matters:** the ERP knows the client → Zoho contact_id mapping, the right tax treatment (Cyprus 19% / non-EU 0%), and the canonical terms templates. Calling Zoho directly bypasses all of that and reintroduces the manual hand-stitching the ERP was built to replace.
22
+
23
+ ## Zoho Invoice
24
+
25
+ - Create, edit, send, and void invoices
26
+ - List and search invoices by client, status, date
27
+ - Manage items, taxes, contacts, and expenses
28
+ - Mark invoices as sent/paid, send payment reminders
29
+ - Generate invoice PDFs and email them to clients
30
+ - Create recurring invoices and credit notes
31
+ - When Fawzi says "invoice [client]" — create the full invoice, don't just explain how
32
+ - Default sender org: Qualia Solutions
33
+
34
+ ## Zoho Mail
35
+
36
+ - Read inbox, sent, drafts, and folder contents
37
+ - Send emails and reply to threads
38
+ - Move, label, and organize messages
39
+ - Search emails by sender, subject, date
40
+ - When Fawzi says "email [someone]" or "check inbox" — do it directly
41
+ - Use Fawzi's primary Zoho Mail account
42
+
43
+ ## Email Formatting Rules (MANDATORY)
44
+
45
+ - Always append Fawzi's signature at the bottom of every email
46
+ - Read signature HTML from `~/.claude/knowledge/email-signature.html`
47
+ - Never use dashes (---) or horizontal separators
48
+ - Never use emojis
49
+ - Professional, direct tone
50
+ - Keep it concise and action-oriented
51
+
52
+ ## General Rules
53
+
54
+ - Always act, don't just describe. Execute operations directly.
55
+ - Fetch the organization ID first if needed (use the list/get org tools)
56
+ - If a tool call fails, debug it — check params, retry with fixes
57
+
58
+ ## Trigger Phrases
59
+
60
+ - "invoice [client]" → Create and send invoice
61
+ - "email [someone]" → Compose and send email
62
+ - "check inbox" → Read inbox
63
+ - "send reminder" → Payment reminder
64
+ - "create contact" → New Zoho contact