Add drag-and-drop reordering to categories page

- Replace up/down chevron buttons with @dnd-kit drag handles
- Update pack generation and question review commands
- Fix MCP bridge and add supabase package.json for Docker

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Opus 4.5 committed 2026-01-10 21:19:36 +00:00
1 parent 2e05105bb0
commit 3009e80dd4
13 files changed
+1125 -1281

No files matched your search

+271
View File
@@ -0,0 +1,271 @@
---
allowed-tools: Read, mcp__sauci-prod__execute_sql, mcp__sauci-prod__list_tables
argument-hint: <category name> [--all] [--explicit] [--apply] [--limit=100]
description: Review and automatically fix issues with questions (text, targeting, intensity, props, duplicates)
---
# Question Fix Agent
Fix questions in category: $ARGUMENTS
## Task Overview
Analyze questions across all packs in a category and apply fixes for issues across 5 dimensions:
1. **Text Phrasing** - Improve wording, remove wishy-washy language
2. **Audience Targeting** - Fix couple/initiator gender targeting
3. **Intensity Levels** - Correct misassigned intensity ratings
4. **Required Props** - Identify missing physical props/accessories
5. **Deletions** - Remove true duplicates, unsafe, or broken questions
## Step 1: Parse Arguments
Extract from $ARGUMENTS:
- **Category identifier**: Category name (partial match) OR category ID (UUID)
- **--all**: Flag indicating the agent should work through all categories sequentially
- **--explicit**: Flag indicating this is an explicit/adult category (auto-detected from category.is_explicit if not specified)
- **--apply**: If set, apply fixes automatically after review. If not set, just show suggestions.
- **--limit**: Max questions to analyze per category (default: 100)
## Step 2: Fetch Data
Query the database to get the category and its questions:
```sql
-- Get category info
SELECT id, name
FROM categories
WHERE id::text = '<category_id>' OR name ILIKE '%<category_name>%'
LIMIT 1;
```
```sql
-- Get all packs in the category
SELECT p.id, p.name, p.is_explicit
FROM question_packs p
WHERE p.category_id = '<category_id>'
ORDER BY p.name;
```
```sql
-- Get questions for all packs in the category
SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props, p.name as pack_name
FROM questions q
JOIN question_packs p ON q.pack_id = p.id
WHERE p.category_id = '<category_id>' AND q.deleted_at IS NULL
ORDER BY p.name, q.created_at
LIMIT <limit>;
```
```sql
-- Get existing props catalog for consistency
SELECT DISTINCT unnest(required_props) as prop
FROM questions
WHERE required_props IS NOT NULL
ORDER BY prop;
```
If **--all** flag is set, first list all categories:
```sql
SELECT id, name, is_explicit FROM categories ORDER BY name;
```
Then process each category sequentially.
## Step 3: Analyze Each Question
For each question, evaluate against these criteria:
### 3.1 Text Analysis
**Language Rules:**
- ALWAYS use "your partner" - NEVER use "me", "I", "you" (as receiver), "him", "her", or gendered pronouns
- Cards are PROPOSALS, NOT interview questions - "Give your partner a massage" not "Would you like to give a massage?"
- Avoid wishy-washy language - NO "Would you...", "Have you ever...", "Do you think...", "Maybe we could..."
- No time-specific words - NO "tonight", "now", "today", "right now"
- Keep concise - ideal length is 5-12 words
**Tone Rules (based on is_explicit):**
For EXPLICIT packs:
- Use tasteful phrasing for common acts: "Have sex in X" (not "fuck in X"), "Perform oral" or "go down on" (not crude oral terms)
- BUT keep crude/specific terms when they ARE the activity: "Cum on your partner's tits" (cum is the act), "Use a cock ring" (that's the name)
- NEVER sanitize "cum" to "come" - they have different meanings
For NON-EXPLICIT packs:
- CRITICAL: No sexual acts, crude language, or NSFW content
- Keep language romantic, sensual, or playful without graphic terms
- Flag explicit content for removal
**Partner Text Rules:**
- NULL for symmetric activities (both partners do the same thing together)
- FILLED for asymmetric activities (one does something to/for the other)
- text = what the INITIATOR does
- partner_text = what the RECEIVER experiences
- Make partner_text APPEALING - frame receiver's experience positively
- When initiator causes a response (moan, cum, beg), frame as ALLOWING:
- text: "Make your partner moan" -> partner_text: "Let your partner make you moan"
- NOT: "Moan for your partner" (sounds forced)
### 3.2 Intensity Analysis
Check if current intensity matches the activity:
| Level | Name | Description | Examples |
|-------|------|-------------|----------|
| 1 | Gentle | Emotional bonding, non-sexual | cooking, cuddling, foot massage |
| 2 | Warm | Romantic, affectionate touch | slow dance, sensual massage, kissing |
| 3 | Playful | Light sexual exploration | oral, mutual masturbation, light roleplay |
| 4 | Steamy | Explicit sex, moderate adventure | intercourse, light bondage, anal play |
| 5 | Intense | Advanced/BDSM/extreme | impact play, power dynamics, taboo kinks |
### 3.3 Targeting Analysis
**Couple Targeting (allowed_couple_genders):**
- DEFAULT: null (ALL couples) unless explicit anatomical requirement
- Restrict to `['male+male', 'female+male']` ONLY if activity requires penis
- Restrict to `['female+male', 'female+female']` ONLY if activity requires vagina
- Sex toys are GENDER-NEUTRAL (vibrators, dildos, plugs work for anyone)
**Initiator Targeting (target_user_genders):**
- DEFAULT: null (anyone can initiate)
- Only for asymmetric questions (those with partner_text)
- Set based on who does the action in "text" field:
- "Swallow your partner's cum" -> partner has penis -> initiator: ['female'] in M+F
- "Deep throat your partner" -> partner has penis -> ['female'] in M+F
- "Give your partner a massage" -> gender-neutral -> null
### 3.4 Props Analysis
**Rules:**
- Props are physical items/accessories, not body parts or acts
- Use lowercase, short, singular names (e.g., "blindfold", "remote vibrator")
- Reuse existing prop names from the catalog when possible
- If no props are required, set to null
### 3.5 Deletion Analysis
**Deletion Categories:**
- `duplicate`: Same `text` AND same initiator as another question
- `redundant`: Minor variation with no added value
- `off-tone`: Violates explicit vs non-explicit rules
- `unsafe`: Coercion, minors, illegal acts, or consent violations
- `too-vague`: Not actionable or not a clear proposal
- `broken`: Grammar too broken to repair without inventing content
- `off-topic`: Not relevant to a couples intimacy app
**CRITICAL: Inverse Questions are NOT Duplicates**
Asymmetric questions MUST exist in pairs (original + inverse). These are NOT duplicates:
```
Question A:
text: "Spank your partner"
partner_text: "Be spanked by your partner"
Question B (INVERSE - NOT A DUPLICATE):
text: "Be spanked by your partner"
partner_text: "Spank your partner"
```
These are intentionally paired so both partners get asked about both roles. When reviewing:
- If you see two questions where one's `text` matches the other's `partner_text`, they are a VALID INVERSE PAIR
- Do NOT flag inverse pairs as duplicates or redundant
- Only flag as duplicate if TWO questions have the SAME `text` (or nearly identical `text`) with the SAME initiator role
**Rules:**
- Prefer edits over deletions when possible
- For true duplicates: delete the weaker/less specific version
- Do NOT delete for minor wording issues
## Step 4: Output Results
Present findings grouped by fix type:
### Summary
| Category | Issues Found | Will Fix |
|----------|--------------|----------|
| Text fixes | X | X |
| Intensity fixes | X | X |
| Targeting fixes | X | X |
| Props fixes | X | X |
| Deletions | X | X |
| No changes needed | X | - |
### Detailed Changes
For each question needing changes, show:
```
**Question ID:** <id>
**Current:** <text> / <partner_text or null>
**Issues:**
- Text: <issue description>
- Intensity: <current> -> <suggested> (<reason>)
- Targeting: <issue description>
- Props: <current> -> <suggested>
- DELETE: <category> - <reason>
**Fix:**
- Text: "<new text>"
- Partner Text: "<new partner_text>" or null
- Intensity: <new intensity>
- Couple Targets: <new allowed_couple_genders or null>
- Initiator Targets: <new target_user_genders or null>
- Required Props: <new required_props or null>
```
## Step 5: Apply Fixes (if --apply flag)
If --apply flag is set, execute the SQL updates:
```sql
-- Text/Partner Text/Intensity fixes
UPDATE questions SET
text = '<new_text>',
partner_text = '<new_partner_text>',
intensity = <new_intensity>
WHERE id = '<question_id>';
-- Targeting fixes
UPDATE questions SET
allowed_couple_genders = ARRAY['...'] or NULL,
target_user_genders = ARRAY['...'] or NULL
WHERE id = '<question_id>';
-- Props fixes
UPDATE questions SET
required_props = ARRAY['...'] or NULL
WHERE id = '<question_id>';
-- Deletions (soft delete)
UPDATE questions SET deleted_at = NOW() WHERE id IN ('<id1>', '<id2>', ...);
```
After applying, report:
- X questions updated
- X questions deleted
- Any errors encountered
If --apply is NOT set, output the SQL statements for manual review.
## Example Usage
```
/fix-questions "Kink" # Review all questions in "Kink" category
/fix-questions "Romantic Classics" --apply # Review and apply fixes to category
/fix-questions --all # Process all categories sequentially
/fix-questions --all --apply # Process and fix all categories
/fix-questions 123e4567-e89b-12d3-a456-426614174000 --limit=50
```
## Guidelines
- DO ask for clarification if category name matches multiple categories
- DO group similar issues together for batch fixes
- DO prioritize safety issues (delete unsafe content first)
- DO preserve the core intent of questions when suggesting text fixes
- DO verify inverse pairs exist before flagging duplicates
- DON'T change questions that are already well-formed
- DON'T suggest overly restrictive targeting (prefer inclusive by default)
- DON'T create new prop names when existing ones match
- DON'T flag inverse pairs as duplicates (one's text = other's partner_text)
+124 -27
View File
@@ -15,8 +15,9 @@ First, read the pack generation guidelines:
## Current State
- Existing categories: !`mcp__sauci-prod__execute_sql` with query: SELECT name, id FROM categories ORDER BY sort_order
- Existing categories: can be retrieved from supabase with query: SELECT name, id FROM categories ORDER BY sort_order
- Existing packs in target category: Will query once category is identified
- Existing props: can be retrieved with: SELECT DISTINCT unnest(required_props) as prop FROM questions WHERE required_props IS NOT NULL ORDER BY prop
## Task
@@ -38,7 +39,12 @@ Extract from $ARGUMENTS:
- What intensity levels are covered?
- Where does this new pack fit in the progression?
2. If new category, establish the progression plan:
2. Check if category is explicit:
```sql
SELECT id, name, is_explicit FROM categories WHERE name ILIKE '%<category>%';
```
3. If new category, establish the progression plan:
- What will the beginner pack cover?
- What will intermediate cover?
- What will advanced cover?
@@ -47,19 +53,46 @@ Extract from $ARGUMENTS:
Follow these rules strictly:
#### Format Rules
- Questions are PROPOSALS, not interview questions
- Use "your partner" - never gendered pronouns
- No time-specific language (tonight, now, today)
- Keep concise: 5-15 words ideal
#### 3.1 Core Language Rules
- ALWAYS use "your partner" - NEVER use "me", "I", "you" (as receiver), "him", "her", or gendered pronouns
- Questions are PROPOSALS, NOT interview questions - "Give your partner a massage" not "Would you like to give a massage?"
- Avoid wishy-washy language - NO "Would you...", "Have you ever...", "Do you think...", "Maybe we could..."
- No time-specific words - NO "tonight", "now", "today", "right now"
- Keep concise: 5-12 words ideal
- Modern language for 25-40 year olds
- Avoid cheesy/cliche phrasing
- Avoid cheesy/cliche phrasing (candlelit dinner, rose petals, bubble bath, Netflix and chill)
#### Partner Text Rules
- **NULL** for symmetric activities (both do the same thing)
- **Filled** for asymmetric activities (different roles)
#### 3.2 Tone Rules (based on category's is_explicit flag)
**For EXPLICIT categories (is_explicit = true):**
- Use tasteful phrasing for common acts: "Have sex in X" (not "fuck in X"), "Perform oral" or "go down on" (not crude oral terms)
- BUT keep crude/specific terms when they ARE the activity: "Cum on your partner's tits" (cum is the act), "Use a cock ring" (that's the name), "Edge until they beg"
- Rule: crude terms for specific acts/objects, tasteful terms for general sex/oral
- NEVER sanitize "cum" to "come" - they have different meanings
**For NON-EXPLICIT categories (is_explicit = false):**
- CRITICAL: No sexual acts, crude language, or NSFW content
- Keep language romantic, sensual, or playful without graphic terms
- If intensity 3+ activities are needed, use suggestive but not explicit language
#### 3.3 Partner Text Rules
- **NULL** for symmetric activities (both partners do the same thing together)
- **Filled** for asymmetric activities (one does something to/for the other)
- `text` = what the INITIATOR does
- `partner_text` = what the RECEIVER experiences
**Partner Text Quality:**
- Make partner_text APPEALING - don't just grammatically flip, make receiver feel excited
- When initiator causes a response (moan, cum, beg), frame as ALLOWING:
- text: "Make your partner moan" -> partner_text: "Let your partner make you moan"
- NOT: "Moan for your partner" (sounds forced, not natural)
- BAD: "Receive oral from your partner" (clinical)
- GOOD: "Let your partner pleasure you with their mouth" (enticing)
#### 3.4 Critical: Inverse Questions
#### Critical: Inverse Questions
For EVERY asymmetric question, create its inverse:
```
@@ -74,24 +107,77 @@ Inverse (MUST CREATE):
This ensures both partners are asked about both roles.
#### Intensity Guidelines
- **1**: Non-sexual bonding (cooking, gaming, quality time)
- **2**: Romantic, flirty (kisses, cuddles, sweet messages)
- **3**: Light sexual (oral, mutual masturbation, basic toys)
- **4**: Explicit sexual (sex, light bondage, anal play)
- **5**: Intense/BDSM (impact play, power exchange, edge play)
#### 3.5 Intensity Guidelines
| Level | Name | Description | Examples |
|-------|------|-------------|----------|
| 1 | Gentle | Emotional bonding, non-sexual | cooking, cuddling, foot massage |
| 2 | Warm | Romantic, affectionate touch | slow dance, sensual massage, kissing |
| 3 | Playful | Light sexual exploration | oral, mutual masturbation, light roleplay |
| 4 | Steamy | Explicit sex, moderate adventure | intercourse, light bondage, anal play |
| 5 | Intense | Advanced/BDSM/extreme | impact play, power dynamics, taboo kinks |
#### 3.6 Targeting Rules
**Couple Targeting (allowed_couple_genders):**
- DEFAULT: null (ALL couples) unless explicit anatomical requirement
- Options: `['male+male', 'female+male', 'female+female']` or subset
- Restrict to `['male+male', 'female+male']` ONLY if activity requires penis (blowjob, handjob)
- Restrict to `['female+male', 'female+female']` ONLY if activity requires vagina (cunnilingus)
- Sex toys are GENDER-NEUTRAL (vibrators, dildos, plugs work for anyone) -> null
**Initiator Targeting (target_user_genders):**
- DEFAULT: null (anyone can initiate)
- Only for asymmetric questions (those with partner_text)
- Set based on who does the action in "text" field:
- "Swallow your partner's cum" -> partner has penis -> initiator: `['female']` in M+F couples
- "Deep throat your partner" -> partner has penis -> `['female']` in M+F couples
- "Give your partner a massage" -> gender-neutral -> null
- When in doubt, use null
**Anatomical Consistency:**
- NEVER mix male-specific and female-specific acts as alternatives
- BAD: "Finger or give your partner a handjob" (mixed anatomy)
- GOOD: Pick one activity per question
#### 3.7 Required Props
Identify physical items/accessories needed:
- Props are items like: blindfold, remote vibrator, handcuffs, massage oil, ice cubes
- Props are NOT body parts or acts
- Use lowercase, short, singular names
- Reuse existing prop names from catalog when possible
- Set to null if no props are required
### Step 4: Review & Validate
Before outputting, verify:
- [ ] All asymmetric questions have inverses
- [ ] No gendered pronouns
- [ ] No time-specific language
- [ ] Questions are proposals, not questions
- [ ] Intensity levels are accurate
- [ ] No cheesy language
Before outputting, verify each question against this checklist:
**Language & Format:**
- [ ] Uses "your partner" (no gendered pronouns)
- [ ] Is a proposal, not an interview question
- [ ] No wishy-washy language
- [ ] No time-specific words
- [ ] 5-12 words (concise)
- [ ] No cheesy/cliche language
- [ ] Varied sentence structures
**Structure:**
- [ ] All asymmetric questions have inverses (these are NOT duplicates - they're required pairs)
- [ ] Partner text is appealing (not clinical)
- [ ] Partner text frames responses as "allowing" not forced
- [ ] No actual duplicates (same `text` appearing twice)
**Accuracy:**
- [ ] Intensity level matches the activity
- [ ] Couple targeting is correct (or null for universal)
- [ ] Initiator targeting is correct for asymmetric questions
- [ ] Required props are identified
- [ ] Considers same-sex couples
**Consistency:**
- [ ] No mixed anatomy in alternatives
- [ ] Tone matches explicit/non-explicit category
- [ ] Fits progression within category
### Step 5: Output
@@ -110,11 +196,18 @@ VALUES (
RETURNING id;
-- Then insert questions (use returned pack_id)
INSERT INTO questions (pack_id, text, partner_text, intensity) VALUES
('[pack_id]', 'Question text', 'Partner text or NULL', intensity),
INSERT INTO questions (pack_id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props) VALUES
('[pack_id]', 'Question text', 'Partner text or NULL', intensity, NULL, NULL, NULL),
('[pack_id]', 'Blowjob question', 'Partner text', 3, ARRAY['male+male', 'female+male'], ARRAY['female'], NULL),
('[pack_id]', 'Toy question', 'Partner text', 4, NULL, NULL, ARRAY['remote vibrator']),
-- ... all questions
```
**Field notes:**
- `allowed_couple_genders`: NULL for all couples, or array like `ARRAY['male+male', 'female+male']`
- `target_user_genders`: NULL for any initiator, or array like `ARRAY['female']`
- `required_props`: NULL if no props needed, or array like `ARRAY['blindfold', 'ice cubes']`
Also provide a summary table:
| Metric | Value |
@@ -127,6 +220,9 @@ Also provide a summary table:
| Intensity 5 | X |
| Symmetric (null partner_text) | X |
| Asymmetric (with inverses) | X pairs |
| With couple targeting | X |
| With initiator targeting | X |
| With required props | X |
## Example Usage
@@ -146,3 +242,4 @@ Also provide a summary table:
- DON'T assume heterosexual couples
- DON'T skip the inverse question requirement
- DON'T use interview-style questions ("Would you like to...")
- DON'T treat inverse pairs as duplicates - they are REQUIRED (one's `text` = other's `partner_text`)
+259
View File
@@ -0,0 +1,259 @@
---
allowed-tools: Read, mcp__sauci-prod__execute_sql, mcp__sauci-prod__list_tables
argument-hint: <pack-name-or-id> [--explicit] [--limit=100]
description: Review and fix questions for text, targeting, intensity, props, and duplicates
---
# Question Review Command
Review and validate questions: $ARGUMENTS
## Task Overview
Analyze questions for issues and suggest fixes across 5 dimensions:
1. **Text Phrasing** - Improve wording, remove wishy-washy language
2. **Audience Targeting** - Fix couple/initiator gender targeting
3. **Intensity Levels** - Correct misassigned intensity ratings
4. **Required Props** - Identify missing physical props/accessories
5. **Deletions** - Flag duplicates, redundant, or broken questions
## Step 1: Parse Arguments
Extract from $ARGUMENTS:
- **Pack identifier**: Pack name (partial match) OR pack ID (UUID)
- **--explicit**: Flag indicating this is an explicit/adult pack (default: false if not specified)
- **--limit**: Max questions to analyze (default: 100)
## Step 2: Fetch Questions
Query the database:
```sql
-- If pack name provided:
SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props
FROM questions q
JOIN question_packs p ON q.pack_id = p.id
WHERE p.name ILIKE '%<pack_name>%'
ORDER BY q.created_at
LIMIT <limit>;
-- If pack ID provided:
SELECT id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props
FROM questions
WHERE pack_id = '<pack_id>'
ORDER BY created_at
LIMIT <limit>;
```
Also fetch existing props for consistency:
```sql
SELECT DISTINCT unnest(required_props) as prop
FROM questions
WHERE required_props IS NOT NULL
ORDER BY prop;
```
## Step 3: Determine Content Type
Check if the pack is explicit:
```sql
SELECT p.name, c.is_explicit
FROM question_packs p
JOIN categories c ON p.category_id = c.id
WHERE p.id = '<pack_id>' OR p.name ILIKE '%<pack_name>%'
LIMIT 1;
```
Use this to set the content type rules below.
## Step 4: Analyze Questions
For each question, evaluate against these criteria:
### 4.1 Text Analysis
**Language Rules:**
- ALWAYS use "your partner" - NEVER use "me", "I", "you" (as receiver), "him", "her", or gendered pronouns
- Cards are PROPOSALS, NOT interview questions - "Give your partner a massage" not "Would you like to give a massage?"
- Avoid wishy-washy language - NO "Would you...", "Have you ever...", "Do you think...", "Maybe we could..."
- No time-specific words - NO "tonight", "now", "today", "right now"
- Keep concise - ideal length is 5-12 words
**Tone Rules (based on explicit flag):**
For EXPLICIT packs:
- Use tasteful phrasing for common acts: "Have sex in X" (not "fuck in X"), "Perform oral" or "go down on" (not crude oral terms)
- BUT keep crude/specific terms when they ARE the activity: "Cum on your partner's tits" (cum is the act), "Use a cock ring" (that's the name)
- Rule: crude terms for specific acts/objects, tasteful terms for general sex/oral
- NEVER sanitize "cum" to "come" - they have different meanings
For NON-EXPLICIT packs:
- CRITICAL: No sexual acts, crude language, or NSFW content
- Keep language romantic, sensual, or playful without graphic terms
- Flag explicit content for removal
**Partner Text Rules:**
- NULL for symmetric activities (both partners do the same thing together)
- FILLED for asymmetric activities (one does something to/for the other)
- text = what the INITIATOR does
- partner_text = what the RECEIVER experiences
- Make partner_text APPEALING - frame receiver's experience positively
- When initiator causes a response (moan, cum, beg), frame as ALLOWING:
- text: "Make your partner moan" -> partner_text: "Let your partner make you moan"
- NOT: "Moan for your partner" (sounds forced)
### 4.2 Intensity Analysis
Check if current intensity matches the activity:
| Level | Name | Description | Examples |
|-------|------|-------------|----------|
| 1 | Gentle | Emotional bonding, non-sexual | cooking, cuddling, foot massage |
| 2 | Warm | Romantic, affectionate touch | slow dance, sensual massage, kissing |
| 3 | Playful | Light sexual exploration | oral, mutual masturbation, light roleplay |
| 4 | Steamy | Explicit sex, moderate adventure | intercourse, light bondage, anal play |
| 5 | Intense | Advanced/BDSM/extreme | impact play, power dynamics, taboo kinks |
### 4.3 Targeting Analysis
**Couple Targeting (allowed_couple_genders):**
- DEFAULT: null (ALL couples) unless explicit anatomical requirement
- Restrict to `['male+male', 'female+male']` ONLY if activity requires penis
- Restrict to `['female+male', 'female+female']` ONLY if activity requires vagina
- Sex toys are GENDER-NEUTRAL (vibrators, dildos, plugs work for anyone)
**Initiator Targeting (target_user_genders):**
- DEFAULT: null (anyone can initiate)
- Only for asymmetric questions (those with partner_text)
- Set based on who does the action in "text" field:
- "Swallow your partner's cum" -> partner has penis -> initiator: ['female'] in M+F
- "Deep throat your partner" -> partner has penis -> ['female'] in M+F
- "Give your partner a massage" -> gender-neutral -> null
### 4.4 Props Analysis
**Rules:**
- Props are physical items/accessories, not body parts or acts
- Use lowercase, short, singular names (e.g., "blindfold", "remote vibrator")
- Reuse existing prop names from the catalog when possible
- If no props are required, set to null
### 4.5 Deletion Analysis
**Deletion Categories:**
- `duplicate`: Same core action as another question
- `redundant`: Minor variation with no added value
- `off-tone`: Violates explicit vs non-explicit rules
- `unsafe`: Coercion, minors, illegal acts, or consent violations
- `too-vague`: Not actionable or not a clear proposal
- `broken`: Grammar too broken to repair without inventing content
- `off-topic`: Not relevant to a couples intimacy app
**Rules:**
- Prefer edits over deletions when possible
- For duplicates: delete the weaker/less specific version
- Do NOT delete for minor wording issues
**CRITICAL: Inverse Questions are NOT Duplicates**
Asymmetric questions MUST exist in pairs (original + inverse). These are NOT duplicates:
```
Question A:
text: "Spank your partner"
partner_text: "Be spanked by your partner"
Question B (INVERSE - NOT A DUPLICATE):
text: "Be spanked by your partner"
partner_text: "Spank your partner"
```
These are intentionally paired so both partners get asked about both roles. When reviewing:
- If you see two questions where one's `text` matches the other's `partner_text`, they are a valid inverse pair
- Do NOT flag inverse pairs as duplicates or redundant
- Only flag as duplicate if TWO questions have the SAME `text` (or nearly identical `text`)
## Step 5: Output Results
Present findings in a structured format:
### Summary
| Category | Count |
|----------|-------|
| Text fixes | X |
| Intensity fixes | X |
| Targeting fixes | X |
| Props fixes | X |
| Deletions suggested | X |
| No changes needed | X |
### Suggested Changes
For each question needing changes:
```
**Question ID:** <id>
**Current Text:** <text>
**Current Partner Text:** <partner_text or null>
**Current Intensity:** <intensity>
Issues Found:
- [ ] Text: <description of issue>
- [ ] Intensity: <current> -> <suggested> (<reason>)
- [ ] Targeting: <description>
- [ ] Props: <current> -> <suggested>
- [ ] DELETE: <category> - <reason>
Suggested Fix:
- Text: "<new text>"
- Partner Text: "<new partner_text>" or null
- Intensity: <new intensity>
- Couple Targets: <new allowed_couple_genders or null>
- Initiator Targets: <new target_user_genders or null>
- Required Props: <new required_props or null>
```
### SQL to Apply Fixes
After user approval, provide UPDATE statements:
```sql
-- Text/Partner Text/Intensity fixes
UPDATE questions SET
text = '<new_text>',
partner_text = '<new_partner_text>',
intensity = <new_intensity>
WHERE id = '<question_id>';
-- Targeting fixes
UPDATE questions SET
allowed_couple_genders = '<new_targets>',
target_user_genders = '<new_initiator>'
WHERE id = '<question_id>';
-- Props fixes
UPDATE questions SET
required_props = ARRAY[<new_props>]
WHERE id = '<question_id>';
-- Deletions
DELETE FROM questions WHERE id IN ('<id1>', '<id2>', ...);
```
## Example Usage
```
/review-questions "Foreplay Favorites"
/review-questions "Kink Discovery" --explicit
/review-questions 123e4567-e89b-12d3-a456-426614174000 --limit=50
```
## Guidelines
- DO ask for clarification if pack name matches multiple packs
- DO group similar issues together for batch fixes
- DO prioritize safety issues (delete unsafe content first)
- DO preserve the core intent of questions when suggesting text fixes
- DON'T change questions that are already well-formed
- DON'T suggest overly restrictive targeting (prefer inclusive by default)
- DON'T create new prop names when existing ones match