home / skills / krishagel / geoffrey / writer
This skill helps you generate authentic, voice-consistent emails, blogs, posts, and reports by learning your patterns from samples and adaptive interviews.
npx playbooks add skill krishagel/geoffrey --skill writerReview the files below or copy the command above to add this skill to your agents.
---
name: writer
description: Generate content in your authentic voice across emails, blogs, social media, and reports
triggers:
- "write"
- "draft"
- "compose"
- "help me write"
- "in my voice"
- "write blog"
- "write email"
- "write post"
allowed-tools: Read, Write, Bash, Skill, AskUserQuestion
version: 0.1.0
---
# Writer Skill
Generate content in your authentic voice by analyzing writing samples and applying learned voice patterns through adaptive interviewing and voice-matched generation.
## Philosophy
**Key Innovation**: This isn't a generic AI writer - it's YOUR voice, learned from YOUR writing.
Unlike generic AI content generation, this skill:
- Learns from 6 distinct voice profiles (work email, personal email, blog, LinkedIn, Twitter, reports)
- Interviews you like a Pulitzer-winning journalist to deeply understand your message
- Validates output against your authentic patterns (not generic "good writing")
- Saves versioned drafts to Obsidian for iteration and history
**Simplicity**: Geoffrey's native LLM capabilities analyze your voice. Scripts just fetch samples. No complex NLP libraries - clean, maintainable architecture.
---
## When to Activate
Use this skill when you need to:
- Write emails that sound like you (not generic AI)
- Draft blog posts in your established voice
- Create social media content consistent with your style
- Generate reports matching your professional tone
- Ensure content authentically represents your perspective and voice
**DON'T use for**:
- Quick factual responses (use native Claude)
- Content in someone else's voice
- Generic templates
---
## Voice Profiles
Six distinct profiles capture different contexts:
| Profile | Source | Sample Target | Status |
|---------|--------|---------------|--------|
| `email_work` | PSD Gmail (sent emails, 6mo) | 50+ emails | Check writing-voice.json |
| `email_personal` | Personal/HRG Gmail (sent, 6mo) | 50+ emails | Check writing-voice.json |
| `blog_technical` | psd401.ai + blog.krishagel.com | 10+ posts | Check writing-voice.json |
| `social_linkedin` | LinkedIn profile posts | 30+ posts | Check writing-voice.json |
| `social_twitter` | X/Twitter posts | 30+ posts | Check writing-voice.json |
| `report_formal` | User-provided samples | 5-10 reports | Check writing-voice.json |
**Confidence Levels**:
- **High (0.85+)**: 50+ samples - voice well established
- **Moderate (0.70-0.84)**: 20-49 samples - patterns emerging
- **Low (<0.70)**: <20 samples - insufficient data, warn user
---
## 6-Phase Workflow
### Phase 1: Voice Profile Selection & Validation
**Goal**: Load the right voice profile and verify it's ready
**Process**:
1. Detect content type from user request:
- "write email" → `email_work` or `email_personal` (ask which)
- "blog post" → `blog_technical`
- "LinkedIn post" → `social_linkedin`
- "tweet" → `social_twitter`
- "report" → `report_formal`
2. Load voice profile:
```bash
cat ~/Library/Mobile\ Documents/com~apple~CloudDocs/Geoffrey/knowledge/writing-voice.json
```
3. Check confidence and sample count:
- If confidence ≥ 0.85: Proceed with confidence
- If confidence 0.70-0.84: Proceed with note
- If confidence < 0.70: Warn user, offer options
**Low Confidence Response**:
```
Only {sample_count} {content_type} samples (confidence: {confidence}).
Target: {target_samples}+ samples for 0.85+ confidence.
Options:
1. Gather more samples (recommended)
2. Use {alternative_profile} voice as proxy (confidence: {alt_confidence})
3. Proceed anyway (may not fully match your voice)
Preference?
```
**Checkpoint 1**: User confirms profile or gathers more samples
---
### Phase 2: Deep Interview (Adaptive)
**Goal**: Deeply understand what you want to communicate
**Process**:
1. Load interview questions from `templates/interview-questions.json`
2. Adapt depth to content type:
- **Tweet/Social**: 3-5 questions (~2-3 min)
- **Email Quick**: 4 questions (~3 min)
- **Email Complex**: 8 questions (~5 min)
- **Blog**: 8-12 questions (~5-8 min)
- **Report**: 15+ questions (~10-15 min)
3. Interview like a journalist:
- Ask follow-up questions for vague answers
- Probe for examples, data, evidence
- Clarify ambiguities
- Build structured understanding
**Email Interview Example** (complex):
```
1. Who are the recipient(s) and what's the context?
→ [User answers]
2. What's the primary goal?
→ [User answers]
[If vague: "Can you be more specific about the outcome you want?"]
3. What background do they need?
→ [User answers]
4. What are the key points? (Max 3-5)
→ [User answers]
[If >5: "Let's focus on the most critical 3-5 points."]
... continue through 8 questions
```
**Blog Interview Example**:
```
1. What's your central message?
2. Why does this matter right now?
3. What should readers do/think/feel after?
4. What evidence supports this?
5. Any stories or case studies?
6. Main counterarguments?
7. How should it open?
8. Key sections (3-5)?
9. How should it close?
10. Primary audience and technical level?
11. Desired tone?
```
**Checkpoint 2**: Review interview summary, add missing details
---
### Phase 3: Content Planning & Outline
**Goal**: Create a clear structure before writing
**Process**:
1. Create outline based on:
- Interview responses
- Content type structure (email: intro + points + ask, blog: hook + body + conclusion)
- Voice profile preferences (e.g., blog uses chronological flow, staff quotes)
2. Apply voice-specific patterns:
- Email: Opening pattern, bullet points for key points, clear ask
- Blog: Subheadings for scannability, narrative flow, reflective close
- Social: Hook first line, call-to-action
3. Suggest length from profile:
- Email avg: Check profile examples
- Blog avg: Check profile examples
- Social: Platform limits (LinkedIn 1300 chars, Twitter 280 chars)
**Email Outline Example**:
```
## Email Plan
**Subject**: [Based on purpose]
**Opening**: [Use opening pattern from profile, e.g., "I've reviewed {topic}..."]
**Body**:
- Point 1: [From interview]
- Point 2: [From interview]
- Point 3: [From interview]
**Ask**: [Specific call-to-action from interview]
**Closing**: [Use closing pattern from profile, e.g., "Let me know if..."]
**Tone**: Direct, professional, action-oriented
**Length**: ~200 words (typical for email_work)
```
**Blog Outline Example**:
```
## Blog Plan
**Title**: [Derived from central message]
**Structure**:
1. Opening: [Hook strategy - context-setting or reflective quote]
2. Background & Context
3. [Main section 1]
4. [Main section 2]
5. [Main section 3]
6. Impact & Results
7. Closing: [Forward-looking reflection]
**Voice elements**:
- Staff quotes (use throughout)
- Chronological narrative flow
- Subheadings for scannability
- Accessible but authoritative tone
**Length**: ~850 words (typical for blog_technical)
```
**Checkpoint 3**: User approves outline
---
### Phase 4: Draft Generation
**Goal**: Write content that authentically sounds like you
**Process**:
1. Load identity context:
```bash
cat ~/Library/Mobile\ Documents/com~apple~CloudDocs/Geoffrey/knowledge/identity-core.json
```
Use for personality alignment:
- `communication_preferences.style` → Direct, no-nonsense, concise
- `decision_framework` → Analytical, evidence-based
- `telos_summary.top_3_values` → Equity, excellence, empathy
2. Apply voice characteristics as generation guidelines:
- **Tone**: From `voice_characteristics.tone`
- **Sentence structure**: From `voice_characteristics.sentence_structure`
- **Vocabulary**: Common terms, avoided terms
- **Formatting**: Bullet points, markdown, emphasis patterns
- **Opening**: Use patterns from `opening_patterns`
- **Closing**: Use patterns from `closing_patterns`
- **Argument structure**: Lead with conclusion, use evidence
3. Write content naturally:
- Use Geoffrey's native writing capabilities
- Guided by voice profile (not rigid pattern matching)
- Weave in interview content (examples, data, stories)
- Match typical length from profile examples
4. Reference example emails/posts from profile for inspiration
**Generation Approach** (Hybrid):
- Write naturally with rich voice context
- Don't mechanically apply patterns
- Let voice characteristics inform style, not constrain creativity
---
### Phase 5: Validation & Refinement
**Goal**: Ensure draft authentically matches your voice
**Native LLM Validation** (no script needed):
1. **Geoffrey self-validates** by comparing draft to voice profile:
- Read the draft I just generated
- Read the voice profile (`voice_characteristics` + `examples`)
- Compare tone, structure, vocabulary, formatting
- Score alignment 0-100
2. **Scoring criteria** (weighted):
- Tone match (20%)
- Sentence structure - length, complexity (15%)
- Vocabulary - common/avoided terms (15%)
- Formatting - bullets, markdown, emphasis (10%)
- Opening pattern match (10%)
- Closing pattern match (10%)
- Argument structure - conclusion placement, evidence use (10%)
- Stylistic markers - contractions, punctuation, framing (10%)
3. **Identify specific deviations**:
- "Sentences 15% longer than typical (avg 16 words vs typical 14)"
- "Missing characteristic bullet points (you use them 67% of time)"
- "Tone slightly more formal than usual"
4. **Scoring thresholds**:
- 85-100: Excellent match, present draft
- 70-84: Good match with minor deviations, note them
- <70: Significant deviations, propose refinements
**Refinement** (if score < 85):
```
Voice Validation: {score}/100
Issues:
- {Issue 1 with specific evidence}
- {Issue 2 with specific evidence}
- {Issue 3 with specific evidence}
Should I refine to better match your voice?
```
If user approves refinement:
- Apply specific fixes identified
- Re-validate
- Present refined draft
**Checkpoint 4**: User accepts, requests refinement, or manually edits
---
### Phase 6: Storage & Versioning
**Goal**: Save draft to Obsidian with proper versioning
**Process**:
1. Determine file path:
```
~/Library/Mobile Documents/iCloud~md~obsidian/Documents/Personal_Notes/Geoffrey/Writing/{ContentType}/{date}-{slug}.md
```
Content types:
- Blog → `Geoffrey/Writing/Blog/`
- Email → `Geoffrey/Writing/Email/`
- Social → `Geoffrey/Writing/Social/`
- Reports → `Geoffrey/Writing/Reports/`
2. Generate frontmatter:
```yaml
---
created: 2025-12-06
type: blog
profile: blog_technical
topic: AI implementation
status: draft
version: 1.0
validation_score: 87
word_count: 847
tags: [geoffrey, writing, ai]
source: geoffrey-writer
interview_date: 2025-12-06
---
```
3. Save file using Obsidian MCP tools:
```
mcp__obsidian-vault__create_vault_file
```
4. Also return text directly (copy-paste ready)
5. Offer to open in Obsidian:
```
mcp__obsidian-vault__show_file_in_obsidian
```
**Versioning**:
- Initial: `version: 1.0`
- Refinements: `version: 1.1`, `1.2`
- Major rewrites: `version: 2.0`
**Output to user**:
```
## Draft Complete
**Validation Score**: 87/100 ✓
**Word Count**: 847
**Profile**: blog_technical
**Saved to**: Geoffrey/Writing/Blog/2025-12-06-ai-implementation.md
[Full draft text here]
Actions:
1. Open in Obsidian
2. Request refinements
3. Mark as final
```
---
## Voice Learning Process
### Initial Setup (Before First Use)
**Required**: Gather writing samples for each profile you'll use
**Step 1: Email Samples** (3 accounts)
```bash
# PSD work emails
bun scripts/extract-email-samples.js \
--account psd \
--date-range "2024-06-01:2025-12-06" \
--max-samples 100 \
--output "/tmp/email-samples-psd.json"
# Personal emails
bun scripts/extract-email-samples.js \
--account kh \
--date-range "2024-06-01:2025-12-06" \
--max-samples 50 \
--output "/tmp/email-samples-kh.json"
# Business/consulting emails
bun scripts/extract-email-samples.js \
--account hrg \
--date-range "2024-06-01:2025-12-06" \
--max-samples 50 \
--output "/tmp/email-samples-hrg.json"
```
**Step 2: Blog Samples**
```bash
# Launch Geoffrey Chrome (for personal blog access)
cd ~/non-ic-code/geoffrey/skills/browser-control
./scripts/launch-chrome.sh
# Extract blog posts
cd ~/non-ic-code/geoffrey/skills/writer
bun scripts/extract-blog-samples.js \
--urls "https://psd401.ai/blog/stanford-ai-tinkery-2025,https://psd401.ai/blog/why-this-website,https://blog.krishagel.com/post1,https://blog.krishagel.com/post2" \
--output "/tmp/blog-samples.json"
```
**Step 3: Social Media Samples** (requires login)
```bash
# IMPORTANT: Manually log into LinkedIn and X in Geoffrey Chrome first
# LinkedIn
bun scripts/extract-social-samples.js \
--platform linkedin \
--profile-url "https://linkedin.com/in/krishagel" \
--max-posts 50 \
--output "/tmp/social-linkedin.json"
# Twitter/X
bun scripts/extract-social-samples.js \
--platform twitter \
--profile-url "https://x.com/KrisHagel" \
--max-posts 50 \
--output "/tmp/social-twitter.json"
```
**Step 4: Geoffrey Analyzes**
Tell Geoffrey:
```
Analyze my writing samples and create voice profiles.
Email samples: /tmp/email-samples-psd.json, /tmp/email-samples-kh.json, /tmp/email-samples-hrg.json
Blog samples: /tmp/blog-samples.json
Social samples: /tmp/social-linkedin.json, /tmp/social-twitter.json
```
Geoffrey will:
1. Read all sample files
2. Analyze each profile's:
- Tone and voice
- Sentence structure patterns
- Vocabulary (common/avoided terms)
- Formatting preferences
- Opening/closing patterns
- Argument structure
- Distinctive markers
3. Select representative examples
4. Calculate confidence scores
5. Write to `writing-voice.json`:
```
~/Library/Mobile Documents/com~apple~CloudDocs/Geoffrey/knowledge/writing-voice.json
```
**Estimated time**: 30-45 min initial setup
---
## Integration Points
### knowledge-manager → Identity Context
**When**: Phase 4 (Draft Generation)
**Purpose**: Ensure voice reflects personality (analytical, direct, impact-driven)
**How**:
```bash
cat ~/Library/Mobile\ Documents/com~apple~CloudDocs/Geoffrey/knowledge/identity-core.json
```
**Use**:
- `communication_preferences` → Tone, style, dislikes
- `decision_framework` → Analytical, evidence-based approach
- `telos_summary` → Values (equity, excellence, empathy), mission
- `strengths_essence` → Input (collector), Significance (impact), Analytical
---
### google-workspace → Email Samples
**When**: Initial voice creation + quarterly refresh
**Scripts**: `search_messages.js`, `read_message.js`
**Process**:
1. Search sent emails
2. Filter automated, short, quoted
3. Clean signatures, HTML
4. Extract plain text + metadata
5. Save to JSON for Geoffrey to analyze
---
### browser-control → Blog/Social Samples
**When**: Initial voice creation + ad-hoc updates
**Prerequisites**: Geoffrey Chrome running with logins
**Process**:
1. Navigate to blog/profile URL
2. Extract article/post content
3. Handle scrolling/lazy-loading (social)
4. Parse and clean content
5. Save to JSON
---
### obsidian-manager → Draft Storage
**When**: Phase 6 (Storage)
**Folder structure**:
```
Geoffrey/Writing/
├── Blog/
├── Email/
├── Social/
└── Reports/
```
**File format**: `YYYY-MM-DD-{slug}.md` with frontmatter
---
## Edge Cases & Handling
### Insufficient Samples
**Scenario**: User requests email, but only 12 samples (confidence 0.62)
**Response**:
```
Only 12 email_work samples (0.62 confidence). Target: 50+ for 0.85+ confidence.
Options:
1. Gather ~40 more email samples from recent sent (recommended)
2. Use email_personal as proxy (if similar, confidence: 0.88)
3. Proceed with low confidence (may not fully match your voice)
Note: Voice profiles improve with more samples. I can still write, but authenticity may vary.
Preference?
```
---
### Voice Drift (Outdated Profile)
**Scenario**: Profile last updated 18 months ago
**Detection**: Check `last_updated` in writing-voice.json
**Response**:
```
Your email_work profile is 18 months old (last updated: 2024-06-15).
Writing styles evolve over time.
Recommendation: Refresh with recent samples (past 6 months)
Run:
bun scripts/extract-email-samples.js --account psd --date-range "2024-06-01:2025-12-06" --max-samples 100 --output "/tmp/email-refresh.json"
Then ask me to analyze and update the profile.
Proceed with current profile or refresh first?
```
---
### Contradictory Samples
**Scenario**: Email samples vary significantly (45% formal, 35% casual, 20% mixed)
**Geoffrey analyzes** and responds:
```
Your email_work voice varies significantly across samples:
- 45% formal (board, superintendent, external)
- 35% casual (staff, peers, project teams)
- 20% mixed
Options:
1. Split into email_work_formal and email_work_casual profiles
2. Choose dominant style (formal) as default
3. Let me dynamically adjust based on recipient (you specify each time)
Preference?
```
---
### Low Validation Score
**Scenario**: Draft scores 62/100
**Response**:
```
Voice Validation: 62/100 ⚠️
Issues:
- Tone too formal (target: direct, action-oriented)
Example: "It would be appreciated if you could consider..." vs typical "Please review and confirm."
- Sentences 35% longer (avg 19 words vs typical 14)
Example: Draft paragraph 2 avg 21 words, your emails avg 14 words.
- Missing bullet points (you use bullets 67% of the time for key points)
Draft uses prose for 5 key points, you typically bullet them.
Should I refine to better match your voice?
```
---
### New Content Type
**Scenario**: User requests "write a press release"
**Response**:
```
I don't have a press_release voice profile yet.
Options:
1. Use report_formal profile as starting point (confidence: 0.83)
2. Provide 5-10 past press releases for me to analyze
3. Write without voice matching (generic professional press release style)
Note: If you provide samples, I can create a new press_release profile.
Preference?
```
---
## Usage Examples
### Example 1: Quick Work Email
**User**: "Write email to my team about next week's leadership meeting"
**Geoffrey**:
```
I'll use your email_work profile (confidence: 0.92, 156 samples).
Quick interview (4 questions):
1. Who's the team and what's your relationship?
→ [User: Direct reports - IT leadership team]
2. Purpose? (inform, request, respond, update)
→ [User: Inform and request prep]
3. Key information?
→ [User: Meeting Tuesday 2pm, agenda items: budget, staffing, AI roadmap. Need their input on AI priorities.]
4. Tone?
→ [User: Professional but casual - these are my directs]
```
[Generates outline] → User approves
[Generates draft, validates (score: 89), presents to user]
---
### Example 2: Blog Post
**User**: "Write blog post about our AI implementation journey"
**Geoffrey**:
```
I'll use your blog_technical profile (confidence: 0.78, 4 samples).
Note: Only 4 blog samples. Recommend 6+ more for 0.85+ confidence.
Proceed or gather more samples first?
→ [User: Proceed]
Standard interview (11 questions):
1. Central message?
→ [User: ...]
2. Why now?
→ [User: ...]
[... continues through 11 questions ...]
```
[Creates detailed outline] → User approves
[Generates ~850 word draft with staff quotes, chronological flow, reflective close]
[Validates (score: 87), presents draft]
[Saves to Geoffrey/Writing/Blog/2025-12-06-ai-implementation-journey.md]
---
### Example 3: LinkedIn Post
**User**: "LinkedIn post about Stanford AI Tinkery visit"
**Geoffrey**:
```
I'll use your social_linkedin profile (confidence: 0.91, 48 posts).
Quick interview (5 questions):
1. Main point (1-2 sentences)?
→ [User: Visited Stanford's AI Tinkery with district team. Collaborative learning happening everywhere, not just at elite institutions.]
2. Target audience?
→ [User: Ed tech leaders, superintendents]
3. Tone?
→ [User: Inspirational, reflective]
4. Call-to-action?
→ [User: Encourage others to build their own learning networks]
5. Link or visual?
→ [User: No link, maybe photo but I'll add later]
```
[Generates ~180 word LinkedIn post]
[Validates (score: 92), presents]
---
## Limitations
### Known Constraints
1. **Sample Dependency**: Voice quality directly tied to sample quantity and quality
- Low samples (<20) = low accuracy
- Poor quality samples = poor voice matching
2. **Voice Drift**: Styles evolve over time
- Recommend quarterly refresh (every 3 months)
- Auto-warn if profile >12 months old
3. **New Contexts**: No profile for content types you haven't written before
- Press releases, technical docs, grant proposals require new samples
- Can adapt existing profiles but may not match perfectly
4. **Platform Nuances**: Social media scraping limitations
- LinkedIn/X may change selectors
- Requires manual login
- May violate ToS (user accepted risk)
5. **Multilingual**: Currently English only
- No Spanish email profile (even though you write Spanish emails)
- Could add with Spanish samples
6. **Collaborative Writing**: Single voice only
- Can't blend your voice + co-author
- Future enhancement
---
## Validation & Quality
### Success Metrics
**Quantitative**:
- Voice alignment: 85+ for established profiles (0.85+ confidence)
- Sample size: 50+ emails, 10+ blogs, 30+ social per profile
- User acceptance: >80% drafts accepted with minor/no edits
- Refinement iterations: <2 average
**Qualitative**:
- User feedback: "This sounds like me"
- Consistent voice across topics within profile
- Authentic, not mechanical or formulaic
### Quality Checklist
Before presenting draft:
- ✓ Matches tone from voice profile
- ✓ Sentence structure within 20% of typical length
- ✓ Uses common vocabulary, avoids avoided terms
- ✓ Follows formatting preferences (bullets, markdown)
- ✓ Opening/closing match typical patterns
- ✓ Argument structure aligned (conclusion placement, evidence use)
- ✓ Validation score ≥85 (or explained deviations if <85)
- ✓ Word count within typical range for content type
---
## Future Enhancements (Out of Scope)
1. **Real-time Learning**: Auto-analyze new sent emails monthly
2. **A/B Testing**: Generate 2 versions, learn from user choices
3. **Collaborative Voice**: Blend user + co-author voices
4. **Multi-language**: Spanish voice profiles
5. **Performance Tracking**: Voice drift detection over time
6. **Audience-Aware**: Sub-profiles (email_work_superintendent vs email_work_staff)
7. **Feedback Loop**: User rates content, Geoffrey learns from scores
---
## Troubleshooting
### "No voice profile found"
**Cause**: writing-voice.json doesn't exist or profile type missing
**Solution**:
1. Check if file exists:
```bash
cat ~/Library/Mobile\ Documents/com~apple~CloudDocs/Geoffrey/knowledge/writing-voice.json
```
2. If missing: Run initial setup (extract samples + ask Geoffrey to analyze)
3. If file exists but profile missing: Add samples for that content type
---
### "Low confidence warning"
**Cause**: Fewer than 20 samples for profile
**Solution**:
1. Gather more samples using extraction scripts
2. Ask Geoffrey to re-analyze with new samples
3. Or: Proceed with caveat that voice may not fully match
---
### "Geoffrey Chrome not running"
**Cause**: Social/blog extraction requires Geoffrey Chrome with remote debugging
**Solution**:
```bash
cd ~/non-ic-code/geoffrey/skills/browser-control
./scripts/launch-chrome.sh
```
---
### "Failed to extract social posts"
**Cause**: Not logged into LinkedIn/X, or selectors changed
**Solution**:
1. Open Geoffrey Chrome manually
2. Navigate to LinkedIn/X and log in
3. Verify posts load
4. Re-run extraction script
5. If still fails: Platform may have changed selectors (update script)
---
## Notes
- **Progressive Disclosure**: Load only the profile needed for current task (not all 6)
- **Identity Alignment**: Always cross-reference identity-core.json for personality consistency
- **Checkpoints**: 4 user approval points prevent wasted effort
- **Versioning**: All drafts saved to Obsidian with version numbers
- **Native Analysis**: Geoffrey's LLM capabilities analyze voice - no complex libraries needed
This skill generates content in your authentic voice across emails, blogs, social media, and reports. It learns from your writing samples, runs an adaptive interview to clarify intent, and produces drafts validated against your voice profile. Drafts are versioned and saved for iteration.
The skill loads a voice profile built from your past samples (six context-specific profiles) and checks confidence based on sample counts. It conducts a short, adaptive interview to surface goals and facts, then builds an outline and generates a draft that follows your voice characteristics. A built-in validation step scores alignment with your profile and offers iterative refinements before saving versions to your notes vault.
What if I don’t have many samples yet?
If a profile has <20 samples the skill warns you and offers options: gather more samples, use a proxy profile, or proceed with lower confidence.
How does validation work?
The draft is compared against profile characteristics (tone, sentence structure, vocabulary, formatting, openings/closings) and scored 0–100. Scores <85 prompt targeted refinement suggestions.