
Sensitivity Check
- 354 installs
- 133 repo stars
- Updated February 24, 2026
- jwynia/agent-skills
sensitivity-check is a jwynia agent skill that reviews product copy, UX flows, and agent outputs for harmful stereotypes, privacy pitfalls, and brand-risk language before public release.
About
sensitivity-check is an evaluative agent skill in jwynia/agent-skills that reviews copy, UX flows, and AI-generated outputs for representation harm, biased stereotypes, privacy pitfalls, regulatory wording issues, and brand-risk language before shipping. It provides context for safer deployment of user-facing strings, onboarding flows, marketing pages, and agent responses that could expose teams to reputational or compliance problems. The skill sits alongside claim-investigation and fact-check in the repository and explicitly supports evaluating representation claims during investigative workflows. Product engineers, frontend developers, and technical writers reach for sensitivity-check during pre-release content review, UX audits, or agent output QA—not when implementing backend logic or writing unit tests. It complements design review by focusing on harm flagging and inclusive language rather than visual layout or accessibility mechanics.
- Scans for stereotyping, slurs, and exclusionary framing
- Flags privacy-sensitive claims and consent gaps
- Checks regulatory phrases in finance, health, and kids contexts
- Suggests inclusive alternatives with rationale
- Supports marketing, support macros, and AI-generated copy
Sensitivity Check by the numbers
- 354 all-time installs (skills.sh)
- +6 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #576 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jwynia/agent-skills --skill sensitivity-checkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 354 |
|---|---|
| repo stars | ★ 133 |
| Last updated | February 24, 2026 |
| Repository | jwynia/agent-skills ↗ |
How do you review UX copy for harmful bias?
Review copy, UX flows, or agent outputs for harmful stereotypes, privacy pitfalls, regulatory wording issues, and brand-risk language before public release.
Who is it for?
Developers and technical writers reviewing user-facing copy, UX flows, or agent outputs before launch.
Skip if: Backend API implementation, automated accessibility WCAG audits, or legal contract drafting requiring licensed counsel.
When should I use this skill?
User asks to review copy, UX text, or agent outputs for sensitivity, bias, privacy pitfalls, or brand-risk language before release.
What you get
Harm and representation flags, privacy-risk notes, and revised copy guidance for safer public release.
Files
Sensitivity Check: Evaluative Skill
You evaluate representation accuracy and flag potential harm concerns in fiction. Your role is to identify issues that might cause harm to readers from affected communities, while respecting authorial intent and providing constructive alternatives.
Core Principle
Good intentions don't prevent harmful impact.
The goal is not to police creativity but to help writers create more accurate, respectful, and authentic representation. A sensitivity check asks: "How might members of represented communities experience this work?"
This is not about what a writer can or cannot write—it's about writing with awareness and intentionality.
---
Fundamental Distinction
What This Skill IS
- Evaluative: Flags concerns, explains reasoning, suggests alternatives
- Informative: Provides context the writer may not have
- Collaborative: Works with the author's intent, not against it
- Specific: Points to concrete passages with concrete concerns
- Constructive: Offers paths forward, not just criticism
What This Skill Is NOT
- Prescriptive: Does not dictate what can or cannot be written
- Censorious: Does not demand removal of difficult content
- Absolutist: Recognizes that representation is complex and contested
- Authoritative: One perspective, not the final word
- Restrictive: Does not prohibit exploration of challenging themes
---
The Evaluation States
State S1: Cultural Appropriation Concerns
Focus: Work extracts cultural elements without proper context, respect, or attribution.
Key Questions:
- Does the work take elements from a marginalized culture without proper context?
- Is the author positioned as discovering/explaining/improving upon traditions not their own?
- Is sacred or restricted cultural knowledge presented inappropriately?
- Are cultural elements reduced to exotic decoration?
Red Flags:
- Cultural practices taken out of context
- Outsider character "discovering" or explaining a culture to readers
- Spiritual/ceremonial practices treated as aesthetic elements
- Mixed cultural elements without acknowledgment of distinctness
- Colonial or outdated anthropological framing
Evaluation Checklist:
- [ ] Cultural elements presented with accurate context
- [ ] Characters from the culture have agency and voice
- [ ] Diversity within the culture acknowledged
- [ ] Sacred/restricted knowledge handled appropriately
- [ ] Language used accurately, not for "exotic flavor"
Constructive Approaches:
- Center voices from within the culture
- Provide cultural context without exoticizing
- Acknowledge sources and inspirations
- Consult primary sources and community members
- Consider whether this story is yours to tell
---
State S2: Gender and Misogyny Issues
Focus: Work contains problematic gender dynamics, objectification, or normalized misogyny.
Key Questions:
- Are gender power imbalances portrayed uncritically?
- Are female/non-binary characters defined primarily through relationships to men?
- Is physical description disproportionately focused on women's bodies?
- Is gendered violence used as convenient plot device?
Red Flags:
- Women existing only to further male character development
- Disproportionate physical description of women's bodies
- Sexual violence as character development shortcut
- Feminine traits described dismissively vs. valorized masculine traits
- "Not like other girls" framing
- Male gaze as default narrative perspective
Evaluation Checklist:
- [ ] Characters of all genders given complex motivations
- [ ] Physical descriptions balanced across genders
- [ ] Violence serves narrative purpose, not gratuitous
- [ ] Agency distributed equitably
- [ ] Diverse gender presentations allowed full humanity
Constructive Approaches:
- Give all characters inner lives and agency
- Examine whose gaze narrates physical descriptions
- Question whether violence is necessary or lazy
- Allow feminine traits and interests equal dignity
- Ensure women exist for their own stories, not men's
---
State S3: Disability, Mental Health, and Body Size Representation
Focus: Work reduces disabled, neurodivergent, or fat characters to their conditions or employs harmful tropes.
Key Questions:
- Are these characters fully realized or reduced to their conditions?
- Does the narrative require "overcoming" disability for a happy ending?
- Are mental health conditions portrayed as making someone dangerous?
- Are fat characters allowed storylines not about their weight?
Red Flags:
- Inspiration porn (disabled person exists to inspire able-bodied people)
- Magical disability (impairment grants special powers as compensation)
- Mental illness = violence or unpredictability
- Fat = moral failing or comedy
- "Overcoming" as only positive narrative
- Cure as required happy ending
Evaluation Checklist:
- [ ] Characters have identities beyond their conditions
- [ ] Barriers framed as environmental/social, not just medical
- [ ] Adaptive tools/accommodations presented naturally
- [ ] Full emotional range allowed
- [ ] Community-preferred terminology used
- [ ] Diverse experiences within conditions shown
Constructive Approaches:
- Allow characters lives not centered on their conditions
- Frame accessibility as normal, not special accommodation
- Consult own-voices resources and sensitivity readers
- Avoid metaphors using disability for negative traits
- Show characters thriving without requiring cure
---
State S4: Stereotyping Patterns
Focus: Work relies on stereotypes rather than individualized characterization.
Key Questions:
- Are marginalized characters defined by stereotypes?
- Does characterization rely on assumed group traits?
- Are characters tokens representing "their" group?
- Is there diversity within identity groups?
Common Stereotype Patterns:
| Identity | Stereotype to Avoid | Fuller Characterization |
|---|---|---|
| Asian | Model minority, martial arts, exotic | Individual skills, varied interests |
| Black | Athletic, criminal, magical | Full profession/interest range |
| Latino | Fiery, criminal, domestic worker | Individual temperament, varied roles |
| Indigenous | Mystical, vanishing, primitive | Contemporary, diverse, modern |
| LGBTQ+ | Tragic, predatory, comic relief | Full life not centered on identity |
| Jewish | Wealthy, neurotic, controlling mother | Diverse economic/personality range |
| Muslim | Terrorist, oppressed woman | Varied practice levels, individuality |
Evaluation Checklist:
- [ ] Characters have individual traits, not just group traits
- [ ] Diversity exists within identity groups
- [ ] Characters exist for their own stories, not as tokens
- [ ] Stereotypes subverted or examined when present
- [ ] Background characters have same complexity as leads
Constructive Approaches:
- Characterize individuals, not representatives
- Include multiple characters from same identity (showing diversity)
- Give marginalized characters the same range of traits as default characters
- Subvert or examine stereotypes rather than playing them straight
---
State S5: Agency and Voice Imbalance
Focus: Marginalized characters lack agency, voice, or narrative centrality.
Key Questions:
- Who drives the plot?
- Whose perspective is centered?
- Who makes meaningful choices?
- Who gets to speak for themselves?
Red Flags:
- White savior narratives
- Marginalized characters as sidekicks/supporters only
- Stories about marginalized experiences told through outsider eyes
- Characters who are acted upon rather than acting
- Marginalized wisdom existing to educate/save the protagonist
Evaluation Checklist:
- [ ] Marginalized characters make meaningful choices
- [ ] Their perspectives are centered in their own stories
- [ ] They are not defined by their utility to default characters
- [ ] They are not reduced to wisdom-dispensers or helpers
- [ ] Their stories have resolution independent of default characters
Constructive Approaches:
- Center marginalized perspectives in their own stories
- Give supporting characters their own goals and arcs
- Examine who benefits from the story's resolution
- Let characters speak for themselves, not be explained
- Question whose story this actually is
---
State S6: Harmful Tropes
Focus: Work employs recognized harmful tropes that damage representation.
Key Questions:
- Does the work use tropes known to cause harm?
- Are harmful patterns reproduced uncritically?
- Is the work aware of the tropes it's engaging?
- If subverting tropes, is the subversion clear?
Trope Categories:
Death and Suffering Tropes
- Bury Your Gays: LGBTQ+ characters die at higher rates than straight characters
- Dead Disabled Person: Disability storyline ends only in death
- Fridging: Women killed to motivate male characters
- Tragic Mulatto: Mixed-race character's tragedy centers on racial identity
Utility Tropes
- Magical Negro: Black character exists to help white protagonist with folk wisdom
- Mystical Native: Indigenous character provides spiritual guidance to white character
- Gay Best Friend: LGBTQ+ character exists only to support straight protagonist
- Manic Pixie Dream Girl: Woman exists only to enliven male protagonist's life
Danger Tropes
- Depraved Bisexual: Bisexuality as indicator of moral corruption
- Psycho Lesbian: Lesbian coded as dangerous or predatory
- Trans Deceiver: Trans character as deceptive about "true" identity
- Mentally Ill = Violent: Mental illness as explanation for violence
Evaluation Checklist:
- [ ] LGBTQ+ characters survive at comparable rates to straight characters
- [ ] Marginalized characters exist for their own stories
- [ ] Mental illness not used as explanation for violence
- [ ] Marginalized suffering not used as other characters' development
- [ ] If engaging tropes, doing so critically
Constructive Approaches:
- Research tropes affecting the identities you're writing
- If using recognized tropes, subvert or examine them explicitly
- Let marginalized characters survive and thrive
- Give marginalized characters stories not about their suffering
- Remember: you don't have to kill them
---
Evaluation Process
1. Identify Represented Identities
List all marginalized identities present in the work:
- Race/ethnicity
- Gender identity
- Sexual orientation
- Disability/neurodivergence
- Mental health conditions
- Body size
- Religion
- Socioeconomic class
- Age
- National origin
2. Apply Relevant State Analyses
For each represented identity, consider:
- S1: Cultural appropriation concerns
- S2: Gender/misogyny concerns
- S3: Disability/mental health/body concerns
- S4: Stereotyping patterns
- S5: Agency/voice imbalance
- S6: Harmful tropes
3. Distinguish Severity Levels
| Level | Definition | Action |
|---|---|---|
| Critical | Likely to cause significant harm | Must address before publication |
| Significant | Pattern of concern | Strongly recommend addressing |
| Minor | Isolated issues | Consider addressing |
| Note | Awareness item | Inform author |
4. Provide Constructive Feedback
For each concern: 1. Identify the specific passage/pattern 2. Explain why it may be problematic 3. Provide context (history, community perspective) 4. Suggest alternative approaches 5. Acknowledge what the author is doing well
5. Acknowledge Limitations
- This is one perspective, not the final word
- Communities are not monolithic; views differ
- Perfect representation is impossible
- The goal is awareness and intentionality
---
Anti-Patterns in Sensitivity Work
The Word Police
Pattern: Flagging individual words without context. Problem: Ignores that language is contextual; same word can be fine or harmful. Fix: Evaluate language in context, not as isolated tokens.
The Representation Quota
Pattern: Demanding specific demographic representation. Problem: Tokenism isn't better than absence; forced diversity rings false. Fix: Focus on quality of representation, not quantity.
The Purity Test
Pattern: Requiring perfect representation with no missteps. Problem: Impossible standard; discourages attempts at representation. Fix: Distinguish between harmful patterns and imperfect efforts.
The Outsider Prohibition
Pattern: Claiming writers can only write their own identities. Problem: Limits representation; ignores research and sensitivity reading. Fix: Focus on how it's written, not who writes it.
The Context Ignorance
Pattern: Flagging historical accuracy as problematic. Problem: Historical fiction must show historical attitudes. Fix: Distinguish between depicting and endorsing; check authorial stance.
The Single Voice
Pattern: Treating one sensitivity reader as definitive. Problem: Communities have diverse views; one person can't represent all. Fix: Acknowledge limitations; consider multiple perspectives.
---
When to Use This Skill
Use When:
- Writing characters from identities different from your own
- Depicting experiences of marginalized communities
- Handling sensitive subject matter (trauma, violence, discrimination)
- Before final publication (ideally during drafting)
- Supplementing (not replacing) own-voices sensitivity readers
Do Not Use As:
- Replacement for actual sensitivity readers
- Authorization to write any identity you want
- Shield against all criticism
- Excuse to not do research
- Final word on representation
---
Available Tools
sensitivity-audit.ts
Scans text for common pattern concerns.
deno run --allow-read scripts/sensitivity-audit.ts manuscript.txt
deno run --allow-read scripts/sensitivity-audit.ts --text "Sample passage..."Detects:
- Potential stereotyping language patterns
- Uneven physical description patterns
- Common harmful trope markers
- Agency/voice distribution indicators
Note: This is a pattern matcher, not a replacement for human evaluation. It flags possible concerns for human review.
representation-map.ts
Maps characters and their representation.
deno run --allow-read scripts/representation-map.ts characters.jsonReports:
- Character identity distribution
- Agency/centrality analysis
- Trope risk assessment
- Diversity within identity groups
---
Integration with Other Skills
| Skill | Integration Point |
|---|---|
| story-sense | Sensitivity concerns affect story coherence |
| character-arc | Marginalized characters need full arcs too |
| dialogue | Voice distinctiveness includes authentic speech patterns |
| worldbuilding | Worlds reflect real power dynamics; examine them |
| genre-conventions | Some genre conventions are problematic tropes |
When to Hand Off
- To character-arc: When representation issues stem from underdeveloped character
- To worldbuilding: When issues are systemic to the world's construction
- To dialogue: When voice authenticity is the core concern
---
Example Interactions
Example 1: White Savior Pattern
Writer: "My protagonist travels to Africa and helps a village build a well."
Your approach: 1. Identify concern: S5 (Agency Imbalance) + potential S1 (Cultural Appropriation) 2. Ask: "Who has agency in this story? Who drives the solution?" 3. Flag: White savior pattern—outsider solving problems for passive community 4. Suggest: Center village members as protagonists; outsider as supporter at most 5. Alternative: Story about the village's own efforts, with or without outside support
Example 2: Mental Illness Villain
Writer: "My villain has schizophrenia, which explains their violence."
Your approach: 1. Identify concern: S3 (Mental Health) + S6 (Harmful Trope: Mentally Ill = Violent) 2. Explain: This trope increases stigma; people with schizophrenia are more likely to be victims than perpetrators 3. Flag: Using mental illness to explain villainy perpetuates harmful stereotypes 4. Suggest: Separate the mental illness from the villainy, or don't diagnose the character
Example 3: Diverse Cast Concern
Writer: "I'm worried I don't have enough diversity in my cast."
Your approach: 1. Reframe: Quality matters more than quantity 2. Ask: "What identities are present? How are they characterized?" 3. Check: S4 (Stereotyping) and S5 (Agency) for existing characters 4. Advise: Better to write fewer marginalized characters well than many as tokens 5. Note: Absence of diversity in appropriate settings is also worth examining
---
Output Persistence
This skill writes primary output to files so work persists across sessions.
Output Discovery
Before doing any other work:
1. Check for context/output-config.md in the project 2. If found, look for this skill's entry 3. If not found or no entry for this skill, ask the user first:
- "Where should I save output from this sensitivity-check session?"
- Suggest:
explorations/sensitivity/or a sensible location for this project
4. Store the user's preference:
- In
context/output-config.mdif context network exists - In
.sensitivity-check-output.mdat project root otherwise
Primary Output
For this skill, persist:
- Representation inventory - identities present and how characterized
- Concern flags - specific issues identified with reasoning
- Recommendations - suggested alternatives or improvements
- Research notes - sources and context for concerns raised
Conversation vs. File
| Goes to File | Stays in Conversation |
|---|---|
| Representation analysis | Clarifying questions |
| Flagged concerns with reasoning | Discussion of intent |
| Recommendations | Writer's decisions |
| Context and sources | Real-time feedback |
File Naming
Pattern: {story}-sensitivity-{date}.md Example: novel-sensitivity-2025-01-15.md
What You Do NOT Do
- You do not prohibit writers from writing any identity
- You do not demand specific representation quotas
- You do not claim final authority on what's acceptable
- You do not replace actual sensitivity readers
- You do not censor difficult or challenging content
- You do not assume harm where context makes intent clear
Your role is evaluative and informative: flag concerns, explain reasoning, suggest alternatives, and respect that the author makes final decisions. The goal is awareness and intentionality, not restriction.
---
Key Insight
Representation is not about avoiding all potential criticism—that's impossible. It's about writing with awareness of impact, doing the work to understand communities you're depicting, and making intentional choices.
The question is never "can I write this?" It's "am I writing this well, with awareness of how it might land?"
Writers who engage with sensitivity concerns produce better, more authentic work. The goal is representation that enriches rather than harms—that gives readers mirrors and windows rather than funhouse distortions.
#!/usr/bin/env -S deno run --allow-read
/**
* representation-map.ts - Map and analyze character representation
*
* Analyzes character data to identify:
* - Identity distribution
* - Agency/centrality patterns
* - Potential trope risks
* - Diversity within identity groups
*
* Usage:
* deno run --allow-read representation-map.ts characters.json
* deno run --allow-read representation-map.ts --interactive
*
* Character JSON format:
* [
* {
* "name": "Character Name",
* "role": "protagonist|antagonist|supporting|minor",
* "identities": {
* "race": "...",
* "gender": "...",
* "sexuality": "...",
* "disability": "...",
* "other": ["..."]
* },
* "agency": "high|medium|low",
* "arc": "positive|negative|flat|none",
* "survives": true|false,
* "notes": "..."
* }
* ]
*/
interface CharacterIdentities {
race?: string;
gender?: string;
sexuality?: string;
disability?: string;
mentalHealth?: string;
religion?: string;
class?: string;
other?: string[];
}
interface Character {
name: string;
role: 'protagonist' | 'antagonist' | 'supporting' | 'minor';
identities: CharacterIdentities;
agency: 'high' | 'medium' | 'low';
arc: 'positive' | 'negative' | 'flat' | 'none';
survives: boolean;
notes?: string;
}
interface AnalysisResult {
characterCount: number;
roleDistribution: Record<string, number>;
identityDistribution: Record<string, Record<string, number>>;
agencyAnalysis: {
byIdentity: Record<string, { high: number; medium: number; low: number }>;
concerns: string[];
};
survivalAnalysis: {
byIdentity: Record<string, { survives: number; dies: number }>;
concerns: string[];
};
tropeRisks: string[];
diversityNotes: string[];
}
const MARGINALIZED_IDENTITIES = {
race: ['black', 'asian', 'latino', 'latina', 'latinx', 'indigenous', 'native', 'middle eastern', 'south asian', 'pacific islander', 'mixed', 'biracial', 'multiracial'],
gender: ['woman', 'female', 'trans', 'transgender', 'non-binary', 'nonbinary', 'genderqueer', 'agender', 'genderfluid'],
sexuality: ['gay', 'lesbian', 'bisexual', 'pansexual', 'queer', 'asexual', 'ace'],
disability: ['disabled', 'blind', 'deaf', 'wheelchair', 'chronic', 'neurodivergent', 'autistic', 'adhd'],
mentalHealth: ['depression', 'anxiety', 'ptsd', 'bipolar', 'schizophrenia', 'ocd', 'eating disorder'],
};
function isMarginalized(category: string, value: string): boolean {
const categoryList = MARGINALIZED_IDENTITIES[category as keyof typeof MARGINALIZED_IDENTITIES];
if (!categoryList) return false;
const lowerValue = value.toLowerCase();
return categoryList.some(term => lowerValue.includes(term));
}
function analyzeCharacters(characters: Character[]): AnalysisResult {
const result: AnalysisResult = {
characterCount: characters.length,
roleDistribution: {},
identityDistribution: {},
agencyAnalysis: { byIdentity: {}, concerns: [] },
survivalAnalysis: { byIdentity: {}, concerns: [] },
tropeRisks: [],
diversityNotes: [],
};
// Role distribution
for (const char of characters) {
result.roleDistribution[char.role] = (result.roleDistribution[char.role] || 0) + 1;
}
// Identity distribution
const identityCategories = ['race', 'gender', 'sexuality', 'disability', 'mentalHealth', 'religion'];
for (const category of identityCategories) {
result.identityDistribution[category] = {};
}
for (const char of characters) {
for (const category of identityCategories) {
const value = char.identities[category as keyof CharacterIdentities];
if (value && typeof value === 'string') {
result.identityDistribution[category][value] =
(result.identityDistribution[category][value] || 0) + 1;
}
}
}
// Agency analysis by identity
for (const char of characters) {
for (const [category, value] of Object.entries(char.identities)) {
if (value && typeof value === 'string') {
const key = `${category}:${value}`;
if (!result.agencyAnalysis.byIdentity[key]) {
result.agencyAnalysis.byIdentity[key] = { high: 0, medium: 0, low: 0 };
}
result.agencyAnalysis.byIdentity[key][char.agency]++;
}
}
}
// Agency concerns
for (const [identity, counts] of Object.entries(result.agencyAnalysis.byIdentity)) {
const total = counts.high + counts.medium + counts.low;
if (total >= 2) {
const lowPercentage = (counts.low / total) * 100;
if (lowPercentage > 50) {
const [category, value] = identity.split(':');
if (isMarginalized(category, value)) {
result.agencyAnalysis.concerns.push(
`${identity}: ${lowPercentage.toFixed(0)}% low agency (${counts.low}/${total} characters)`
);
}
}
}
}
// Survival analysis
for (const char of characters) {
for (const [category, value] of Object.entries(char.identities)) {
if (value && typeof value === 'string') {
const key = `${category}:${value}`;
if (!result.survivalAnalysis.byIdentity[key]) {
result.survivalAnalysis.byIdentity[key] = { survives: 0, dies: 0 };
}
if (char.survives) {
result.survivalAnalysis.byIdentity[key].survives++;
} else {
result.survivalAnalysis.byIdentity[key].dies++;
}
}
}
}
// Survival concerns
for (const [identity, counts] of Object.entries(result.survivalAnalysis.byIdentity)) {
const total = counts.survives + counts.dies;
if (counts.dies > 0) {
const deathPercentage = (counts.dies / total) * 100;
const [category, value] = identity.split(':');
if (isMarginalized(category, value)) {
if (deathPercentage > 50 || (category === 'sexuality' && counts.dies > 0)) {
result.survivalAnalysis.concerns.push(
`${identity}: ${counts.dies}/${total} characters die (${deathPercentage.toFixed(0)}%)`
);
}
}
}
}
// Trope risk detection
const lgbtqChars = characters.filter(c =>
isMarginalized('sexuality', c.identities.sexuality || '') ||
(c.identities.gender && isMarginalized('gender', c.identities.gender) && c.identities.gender.toLowerCase().includes('trans'))
);
const lgbtqDeaths = lgbtqChars.filter(c => !c.survives);
if (lgbtqDeaths.length > 0) {
result.tropeRisks.push(
`BURY YOUR GAYS RISK: ${lgbtqDeaths.length} LGBTQ+ character(s) die: ${lgbtqDeaths.map(c => c.name).join(', ')}`
);
}
// Magical minority detection
const marginalizedSupportingWithHighAgency = characters.filter(c =>
c.role === 'supporting' &&
c.agency === 'high' &&
Object.entries(c.identities).some(([cat, val]) =>
val && typeof val === 'string' && isMarginalized(cat, val)
)
);
const protagonists = characters.filter(c => c.role === 'protagonist');
const defaultProtagonists = protagonists.filter(c =>
!Object.entries(c.identities).some(([cat, val]) =>
val && typeof val === 'string' && isMarginalized(cat, val)
)
);
if (marginalizedSupportingWithHighAgency.length > 0 && defaultProtagonists.length > 0) {
result.tropeRisks.push(
`MAGICAL MINORITY RISK: Marginalized supporting characters with high agency serving default protagonist(s). Check: ${marginalizedSupportingWithHighAgency.map(c => c.name).join(', ')}`
);
}
// Mental health + antagonist
const mentalHealthAntagonists = characters.filter(c =>
c.role === 'antagonist' && c.identities.mentalHealth
);
if (mentalHealthAntagonists.length > 0) {
result.tropeRisks.push(
`MENTAL ILLNESS = VILLAIN RISK: Antagonist(s) with mental health conditions: ${mentalHealthAntagonists.map(c => `${c.name} (${c.identities.mentalHealth})`).join(', ')}`
);
}
// Diversity notes
const identityCounts: Record<string, number> = {};
for (const char of characters) {
for (const [category, value] of Object.entries(char.identities)) {
if (value && typeof value === 'string') {
const key = `${category}:${value}`;
identityCounts[key] = (identityCounts[key] || 0) + 1;
}
}
}
for (const [identity, count] of Object.entries(identityCounts)) {
if (count === 1) {
const [category, value] = identity.split(':');
if (isMarginalized(category, value)) {
result.diversityNotes.push(
`Only one ${value} character - consider if tokenism risk`
);
}
}
}
return result;
}
function formatReport(result: AnalysisResult): string {
let report = `
REPRESENTATION MAP
==================
Characters analyzed: ${result.characterCount}
ROLE DISTRIBUTION
`;
for (const [role, count] of Object.entries(result.roleDistribution)) {
report += ` ${role}: ${count}\n`;
}
report += `
IDENTITY DISTRIBUTION
`;
for (const [category, values] of Object.entries(result.identityDistribution)) {
if (Object.keys(values).length > 0) {
report += `\n ${category}:\n`;
for (const [value, count] of Object.entries(values)) {
report += ` ${value}: ${count}\n`;
}
}
}
if (result.agencyAnalysis.concerns.length > 0) {
report += `
AGENCY CONCERNS
`;
for (const concern of result.agencyAnalysis.concerns) {
report += ` ! ${concern}\n`;
}
}
if (result.survivalAnalysis.concerns.length > 0) {
report += `
SURVIVAL CONCERNS
`;
for (const concern of result.survivalAnalysis.concerns) {
report += ` ! ${concern}\n`;
}
}
if (result.tropeRisks.length > 0) {
report += `
TROPE RISKS
`;
for (const risk of result.tropeRisks) {
report += ` !! ${risk}\n`;
}
}
if (result.diversityNotes.length > 0) {
report += `
DIVERSITY NOTES
`;
for (const note of result.diversityNotes) {
report += ` * ${note}\n`;
}
}
report += `
RECOMMENDATIONS
1. Review any flagged trope risks carefully
2. Consider agency distribution across identities
3. If only one character of an identity, consider tokenism
4. Ensure marginalized characters have their own stories/arcs
5. Engage sensitivity readers for identities represented
This analysis is based on data provided and cannot assess:
- Quality of representation within the narrative
- Stereotyping through characterization
- Authentic voice and experience
- Reception by affected communities
`;
return report;
}
function showExampleFormat(): string {
return `
CHARACTER JSON FORMAT
=====================
Create a JSON file with this structure:
[
{
"name": "Maya Chen",
"role": "protagonist",
"identities": {
"race": "Chinese-American",
"gender": "woman",
"sexuality": "bisexual",
"disability": null,
"mentalHealth": null,
"religion": null
},
"agency": "high",
"arc": "positive",
"survives": true,
"notes": "Main character, drives the plot"
},
{
"name": "James Wright",
"role": "supporting",
"identities": {
"race": "white",
"gender": "man",
"sexuality": "straight"
},
"agency": "medium",
"arc": "flat",
"survives": true
},
{
"name": "Dr. Amara Okonkwo",
"role": "supporting",
"identities": {
"race": "Nigerian",
"gender": "woman",
"sexuality": "lesbian"
},
"agency": "high",
"arc": "positive",
"survives": false,
"notes": "Dies in chapter 15"
}
]
FIELDS:
name: Character name
role: protagonist | antagonist | supporting | minor
identities: Object with identity categories
- race: ethnic/racial identity
- gender: gender identity
- sexuality: sexual orientation
- disability: any disability
- mentalHealth: any mental health condition
- religion: religious identity
- class: socioeconomic class
agency: high | medium | low (how much they drive events)
arc: positive | negative | flat | none
survives: true | false (do they survive the story?)
notes: optional additional context
`;
}
function showUsage(): void {
console.log(`
Representation Map Tool
=======================
Analyzes character data to identify representation patterns.
Usage:
deno run --allow-read representation-map.ts <characters.json>
deno run --allow-read representation-map.ts --format
Options:
--format Show the expected JSON format for character data
Detects:
- Identity distribution across characters
- Agency imbalances by identity
- Survival disparities (Bury Your Gays, etc.)
- Trope risks (Magical Minority, etc.)
- Tokenism indicators
See --format for character JSON structure.
`);
}
async function main() {
const args = Deno.args;
if (args.includes('--format')) {
console.log(showExampleFormat());
Deno.exit(0);
}
if (args.length === 0 || args[0].startsWith('--')) {
showUsage();
Deno.exit(0);
}
const filename = args[0];
try {
const text = await Deno.readTextFile(filename);
const characters: Character[] = JSON.parse(text);
if (!Array.isArray(characters)) {
console.error('Error: JSON must be an array of character objects');
Deno.exit(1);
}
const result = analyzeCharacters(characters);
console.log(formatReport(result));
} catch (e) {
if (e instanceof SyntaxError) {
console.error('Error: Invalid JSON format');
console.error(e.message);
} else {
console.error(`Error reading file: ${filename}`);
console.error(e);
}
Deno.exit(1);
}
}
main();
#!/usr/bin/env -S deno run --allow-read
/**
* sensitivity-audit.ts - Pattern scanner for potential representation concerns
*
* IMPORTANT: This tool flags patterns for HUMAN REVIEW. It cannot determine
* context, intent, or whether a pattern is actually problematic. Every flag
* requires human evaluation.
*
* Detects:
* - Potential stereotyping language patterns
* - Uneven physical description patterns
* - Common harmful trope markers
* - Agency/voice distribution indicators
*
* Usage:
* deno run --allow-read sensitivity-audit.ts manuscript.txt
* deno run --allow-read sensitivity-audit.ts --text "Sample passage..."
*/
interface PatternMatch {
category: string;
pattern: string;
line: number;
text: string;
note: string;
}
interface AuditResult {
totalLines: number;
flags: PatternMatch[];
summary: Record<string, number>;
}
// Physical description patterns that may indicate gendered gaze
const PHYSICAL_DESCRIPTION_PATTERNS = [
{ pattern: /\b(her|she|woman's|girl's)\s+(body|figure|curves|breasts|legs|hips|waist|lips|skin)\b/gi, note: "Physical description of female body - check for male gaze" },
{ pattern: /\b(beautiful|gorgeous|stunning|sexy|attractive)\s+(woman|girl|she|her)\b/gi, note: "Appearance-focused female description - check balance with male characters" },
{ pattern: /\bher\s+(ample|full|heaving|soft)\s+/gi, note: "Potentially objectifying description - review context" },
];
// Stereotyping language patterns (require context review)
const STEREOTYPE_PATTERNS = [
// General
{ pattern: /\b(exotic|oriental)\b/gi, note: "Potentially othering language - review context" },
{ pattern: /\b(tribe|tribal)\b/gi, note: "May be appropriate or stereotyping - review context" },
{ pattern: /\bbroken\s+english\b/gi, note: "Dialect representation - check if respectful or mocking" },
// Agency markers
{ pattern: /\bsaved\s+(her|him|them)\s+from\b/gi, note: "Savior dynamic - check who saves whom" },
{ pattern: /\bteach\s+(her|him|them)\s+(about|how)\b/gi, note: "Teaching dynamic - check power dynamics" },
{ pattern: /\bexplain(ed|ing)?\s+(to|for)\s+(her|him|them)\b/gi, note: "Explaining dynamic - check condescension" },
];
// Trope markers (require context review)
const TROPE_PATTERNS = [
// Death/suffering
{ pattern: /\b(died|killed|death)\b.*\b(gay|lesbian|bisexual|trans|queer)\b/gi, note: "LGBTQ+ character death - check for Bury Your Gays pattern" },
{ pattern: /\b(gay|lesbian|bisexual|trans|queer)\b.*\b(died|killed|death)\b/gi, note: "LGBTQ+ character death - check for Bury Your Gays pattern" },
// Magical/mystical minority
{ pattern: /\b(wise|wisdom|ancient|mystical|spiritual)\s+(elder|grandmother|grandfather|old\s+(man|woman))\b/gi, note: "Wisdom figure - check for Magical Minority trope" },
// Mental health
{ pattern: /\b(crazy|insane|psycho|lunatic|mental)\s+(killer|murderer|villain)\b/gi, note: "Mental illness = violence pattern - review carefully" },
{ pattern: /\b(schizophren|bipolar|psychotic)\b.*\b(dangerous|violent|killer)\b/gi, note: "Specific diagnosis linked to violence - major concern" },
];
// Disability language patterns
const DISABILITY_PATTERNS = [
{ pattern: /\b(wheelchair\s*bound|confined\s+to\s+(a\s+)?wheelchair)\b/gi, note: "Outdated disability language - prefer 'uses a wheelchair'" },
{ pattern: /\b(suffer(s|ed|ing)?\s+from|afflicted\s+(with|by))\b/gi, note: "Suffering framing - may be othering; review context" },
{ pattern: /\b(despite\s+(his|her|their)\s+disability)\b/gi, note: "Inspiration porn marker - review for 'overcoming' narrative" },
{ pattern: /\b(special\s+needs)\b/gi, note: "Euphemism - consider if 'disabled' is more direct and preferred" },
{ pattern: /\bthe\s+(blind|deaf|disabled|crippled|handicapped)\b/gi, note: "Group-noun usage - prefer person-first or identity-first per community" },
];
// Body size patterns
const BODY_PATTERNS = [
{ pattern: /\b(fat|obese|overweight)\s+(and|but)\s+(lazy|slovenly|gross|disgusting)\b/gi, note: "Weight + negative trait - check for fatphobic stereotyping" },
{ pattern: /\blet\s+(herself|himself|themselves)\s+go\b/gi, note: "Weight judgment - review for moralizing body size" },
{ pattern: /\b(thin|slim|slender)\s+(and|but)\s+(beautiful|attractive|healthy)\b/gi, note: "Thin = positive pattern - review for anti-fat bias" },
];
// Dialogue patterns that might indicate same-voice issues across identities
const DIALOGUE_PATTERNS = [
{ pattern: /\b(yes,?\s+massa|yessir|lawdy|chile)\b/gi, note: "Dialect representation - verify authenticity, check for minstrelsy" },
{ pattern: /\bme\s+(love|want|no\s+understand)\b/gi, note: "Broken speech pattern - check for mocking vs. authentic ESL" },
];
function matchPatterns(text: string, patterns: { pattern: RegExp; note: string }[], category: string): PatternMatch[] {
const lines = text.split('\n');
const matches: PatternMatch[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (const { pattern, note } of patterns) {
pattern.lastIndex = 0;
let match;
while ((match = pattern.exec(line)) !== null) {
matches.push({
category,
pattern: match[0],
line: i + 1,
text: line.trim().slice(0, 100) + (line.length > 100 ? '...' : ''),
note,
});
}
}
}
return matches;
}
function analyzeText(text: string): AuditResult {
const lines = text.split('\n');
const flags: PatternMatch[] = [];
flags.push(...matchPatterns(text, PHYSICAL_DESCRIPTION_PATTERNS, 'Physical Description'));
flags.push(...matchPatterns(text, STEREOTYPE_PATTERNS, 'Stereotyping Language'));
flags.push(...matchPatterns(text, TROPE_PATTERNS, 'Harmful Tropes'));
flags.push(...matchPatterns(text, DISABILITY_PATTERNS, 'Disability Language'));
flags.push(...matchPatterns(text, BODY_PATTERNS, 'Body Size'));
flags.push(...matchPatterns(text, DIALOGUE_PATTERNS, 'Dialogue Patterns'));
// Sort by line number
flags.sort((a, b) => a.line - b.line);
// Summarize by category
const summary: Record<string, number> = {};
for (const flag of flags) {
summary[flag.category] = (summary[flag.category] || 0) + 1;
}
return {
totalLines: lines.length,
flags,
summary,
};
}
function formatReport(result: AuditResult): string {
let report = `
SENSITIVITY AUDIT
=================
IMPORTANT: This tool flags patterns for HUMAN REVIEW.
It cannot determine context, intent, or actual harm.
Every flag requires thoughtful human evaluation.
Lines scanned: ${result.totalLines}
Patterns flagged: ${result.flags.length}
`;
if (Object.keys(result.summary).length > 0) {
report += `SUMMARY BY CATEGORY\n`;
for (const [category, count] of Object.entries(result.summary)) {
report += ` ${category}: ${count} flag(s)\n`;
}
report += '\n';
}
if (result.flags.length === 0) {
report += `No patterns flagged. This does NOT mean the text is free of concerns.
Pattern matching cannot catch:
- Stereotyping through characterization (not language)
- Agency imbalances in plot structure
- Tropes enacted through events rather than words
- Subtle or contextual issues
Consider full human sensitivity reading for thorough evaluation.
`;
} else {
report += `FLAGGED PATTERNS\n`;
report += `${'='.repeat(50)}\n\n`;
let currentCategory = '';
for (const flag of result.flags) {
if (flag.category !== currentCategory) {
currentCategory = flag.category;
report += `\n## ${currentCategory}\n\n`;
}
report += `Line ${flag.line}: "${flag.pattern}"\n`;
report += ` Context: ${flag.text}\n`;
report += ` Note: ${flag.note}\n\n`;
}
report += `
${'='.repeat(50)}
NEXT STEPS
1. Review each flag in context - many may be appropriate
2. For concerning patterns, consider:
- Is this character/scene necessary?
- Could this be written differently?
- What might affected readers experience?
3. Consider engaging sensitivity readers for identities represented
4. Pattern absence doesn't mean concern absence - structural issues
(plot, character agency, tropes) require human analysis
This tool supplements but does NOT replace human sensitivity reading.
`;
}
return report;
}
function showUsage(): void {
console.log(`
Sensitivity Audit Tool
======================
Scans text for patterns that may indicate representation concerns.
Flags for HUMAN REVIEW - cannot determine actual harm.
Usage:
deno run --allow-read sensitivity-audit.ts <file>
deno run --allow-read sensitivity-audit.ts --text "Text to analyze..."
Pattern Categories:
- Physical Description: Gendered gaze, objectification
- Stereotyping Language: Othering, power dynamics
- Harmful Tropes: Known problematic patterns
- Disability Language: Outdated or othering terms
- Body Size: Fatphobic patterns
- Dialogue Patterns: Accent/dialect representation
IMPORTANT: This tool cannot catch:
- Characterization-based stereotyping
- Plot-level agency imbalances
- Tropes enacted through events
- Contextual or subtle issues
Always engage human sensitivity readers for thorough evaluation.
`);
}
async function main() {
const args = Deno.args;
let text: string;
if (args.includes('--text')) {
const textIndex = args.indexOf('--text');
text = args.slice(textIndex + 1).join(' ');
} else if (args.length > 0 && !args[0].startsWith('--')) {
const filename = args[0];
try {
text = await Deno.readTextFile(filename);
} catch (e) {
console.error(`Error reading file: ${filename}`);
console.error(e);
Deno.exit(1);
}
} else {
showUsage();
Deno.exit(0);
}
const result = analyzeText(text);
console.log(formatReport(result));
}
main();
Related skills
How it compares
Pick sensitivity-check over claim-investigation when reviewing inclusive language and harm in product copy rather than verifying factual assertions from external sources.
FAQ
What does sensitivity-check review?
sensitivity-check reviews product copy, UX flows, and agent-generated text for harmful stereotypes, privacy pitfalls, regulatory wording issues, and brand-risk language before public release.
When should developers invoke sensitivity-check?
Developers invoke sensitivity-check during pre-release content QA on onboarding flows, marketing pages, or agent outputs—not during backend coding or automated accessibility tooling runs.