| name | anti-pua |
| description | Analyze manipulative or emotionally abusive chat logs, detect anti-PUA patterns such as gaslighting, blame shifting, negging, isolation, cold violence. Bilingual (EN+ZH). Outputs profiles + interactive HTML dashboard. |
| trigger | /anti-pua |
| user-invocable | true |
| allowed-tools | Read, Write, Bash, Agent, AskUserQuestion |
| skill-locator | scripts/locate.py |
| version | 2.0.1 |
/anti-pua
Turn any chat export into a structured manipulation analysis — bilingual PUA pattern detection (EN+ZH), abuser/victim profiles with scored dimensions, temporal hotspot heatmaps, and an interactive single-file HTML dashboard.
Usage
/anti-pua <file> # single chat export (.md .txt .csv)
/anti-pua <folder> # folder of chat exports, merged chronologically
/anti-pua <file1> <file2> ... # multiple files merged
/anti-pua # scan current directory for chat files
# Optional: specify tagging method upfront to skip the interactive prompt
/anti-pua <path> --method regex # keyword rules only (fast, lower recall)
/anti-pua <path> --method hybrid # regex + LLM (recommended, default)
/anti-pua <path> --method llm # LLM semantic only
# Run individual steps
/anti-pua prepare <path> # Step 2 only: ingest raw files → records.json
/anti-pua normalize <path> # Step 3 only: clean + dedup → normalized chunks
/anti-pua summary <path> # Step 4 only: detect PUA patterns → tagged.json
/anti-pua analyze <path> # Step 5 only: aggregate → profiles + report
/anti-pua visualize <path> # Step 6 only: render HTML dashboard
/anti-pua full <path> # explicit full pipeline (same as default)
# Advanced Commands
### Append — 追加新聊天记录
/anti-pua append [--workspace ]
追加新聊天记录到已有 workspace,自动:
1. 备份旧快照至 `snapshots/{date}/`
2. 合并新记录到 `records.json`
3. 重新运行 normalize → tag → analyze
4. 生成 `change_report.md`(对比新旧)
**Prerequisites:** `.anti-pua/analysis.json` 必须存在(先运行 `/anti-pua full`)
---
### Strategy — 生成应对策略
/anti-pua strategy [--workspace ]
基于当前分析生成应对策略:
- 短期策略:针对高严重度片段 (severity≥3) 的具体应对建议
- 长期策略:针对 victim_susceptibility 薄弱维度的系统性改善建议
输出:`strategy.json` + `strategy.md`
**Prerequisites:** `.anti-pua/analysis.json` + `.anti-pua/tagged.json` 必须存在
---
### Timeline — 状态跟踪
/anti-pua timeline [--workspace ]
聚合历史快照,生成趋势分析:
- 扫描 `snapshots/` 目录 + 当前 `analysis.json`
- 计算指标变化方向和幅度
- 提供趋势解读和建议
输出:`timeline.json` + `trend_report.md`
**Prerequisites:** 需至少 2 个快照(运行 append ≥1 次或手动创建快照)
What anti-pua is for
This skill is built for pattern recognition across chat logs that are too long or emotionally difficult to analyze manually.
Three things it does that Claude alone cannot:
- Persistent structured output — results are stored in
.anti-pua/ and survive session resets. Re-run individual steps without reprocessing everything.
- Hybrid detection: regex + LLM semantic analysis — keyword rules catch explicit patterns; LLM analysis catches contextual manipulation, implicit threats, and power-dynamic abuse that keywords miss. Every flagged instance is tagged with severity (1–5), confidence score, and reasoning. You know what was found vs inferred.
- Cross-chunk temporal analysis — hotspot detection across time reveals escalation patterns you would never spot reading message-by-message.
Use it for:
- Analyzing a chat log you suspect contains manipulation
- Documenting a pattern of emotional abuse for your own clarity
- Preparing structured evidence for a support conversation or professional consultation
Safety boundaries: This tool informs — it does not diagnose, treat, or adjudicate. See Safety Rules below.
What You Must Do When Invoked
If no path is given, use . (current directory). Do not ask for a path.
Determine which step(s) to run from the command:
- Sub-command (
prepare, normalize, summary, analyze, visualize) → run that step only
full or no sub-command → run all steps (2–7) in order
Step 0 — Resolve SKILL_DIR
Every tool that loads skills reads this SKILL.md from disk. You therefore know — or can find — the path this file was loaded from. Use the first method that succeeds:
Method 1 — Tool-injected variable
SKILL_DIR="${CLAUDE_SKILL_DIR}"
SKILL_DIR="${OPENCODE_SKILL_DIR}"
Method 2 — locate.py 自定位(通用 fallback)
locate.py 利用 Python 的 __file__ 知道自己在磁盘上的位置。
先找到它,它会告诉你一切:
SKILL_DIR=$(python -c "
import pathlib, sys
# 候选安装路径 —— 按实际情况增减
candidates = [
pathlib.Path.home() / '.claude/skills/anti-pua',
pathlib.Path.home() / '.config/opencode/skills/anti-pua',
pathlib.Path('/path/to/workbuddy/skills/anti-pua'),
]
found = next((p for p in candidates if (p / 'scripts/locate.py').exists()), None)
if found:
print(found)
else:
# 最后手段:从 home 递归搜索(较慢)
hit = next(pathlib.Path.home().rglob('skills/anti-pua/scripts/locate.py'), None)
print(hit.parent.parent if hit else 'NOT_FOUND')
")
SKILL_DIR=$(python "$SKILL_DIR/scripts/locate.py")
Method 3 — 从 SKILL.md 路径推断(工具已告知文件路径时)
import pathlib
skill_md_path = pathlib.Path("/此处替换为工具给出的SKILL.md实际路径")
SKILL_DIR = skill_md_path.parent
任意方法成功后,设置:
SCRIPT_DIR="$SKILL_DIR/scripts"
执行验证,失败则停止:
python "$SCRIPT_DIR/locate.py"
Step 1 — Detect Input Files & Choose Tagging Method
1a — Detect files
python - <<'EOF'
import json, sys
from pathlib import Path
path = Path("INPUT_PATH")
exts = {".md", ".txt", ".csv"}
if path.is_file():
files = [path] if path.suffix in exts else []
elif path.is_dir():
files = sorted(f for f in path.rglob("*") if f.suffix in exts and ".anti-pua" not in str(f))
else:
files = []
print(json.dumps({"files": [str(f) for f in files], "count": len(files)}))
EOF
Replace INPUT_PATH with the actual path. Present a clean summary:
Input: N file(s) found
chat_log_1.md (42 KB)
chat_log_2.txt (18 KB)
- If 0 files found: stop with "No supported chat files (.md .txt .csv) found in [path]."
- If files found: proceed.
1b — Choose tagging method
Skip this step if --method was specified in the command.
For full or no sub-command, ask the user now — before the pipeline starts — so the run is uninterrupted. Use the AskUserQuestion tool:
Question: "选择 PUA 标记方式(将在 Step 4 使用)"
Options:
1. "Python(正则匹配)" — 仅用关键词规则匹配,速度快但覆盖率低
2. "Python + LLM(推荐)" — 先用正则初筛,再用 LLM 语义分析,覆盖率最高
3. "LLM only" — 仅用 LLM 语义分析,跳过正则匹配
Default: option 2 (hybrid). Record the choice; use it in Step 4 without asking again.
For a single-step summary invocation, ask at the start of that step instead.
Step 2 — Ingest (parse raw files → records.json)
Prerequisites: Input files detected in Step 1a. SKILL_DIR resolved in Step 0.
python "${SCRIPT_DIR}/ingest.py" \
--input INPUT_FILES \
--workspace WORKSPACE_PATH
Replace INPUT_FILES with space-separated quoted file paths, WORKSPACE_PATH with the workspace directory (parent of where .anti-pua/ will be created).
Supported input formats: .md (飞书/微信聊天记录), .txt (带时间戳的对话), .csv.
Print a clean summary after completion:
Ingest: N messages from M file(s) → .anti-pua/records.json
If the script exits non-zero, stop and report the error. Do not proceed.
Step 3 — Normalize (clean + dedup → normalized chunks)
Prerequisites: .anti-pua/records.json must exist (produced by Step 2).
python "${SCRIPT_DIR}/normalize.py" \
--workspace WORKSPACE_PATH
What it does:
- Re-parses source files with improved speaker detection
- Handles two date formats:
**YYYY-MM-DD HH:MM · Speaker** and **HH:MM · Speaker** under ## date section headers
- Filters noise: Zoom invite fragments, standalone URLs,
[已删除], section headers
- Deduplicates across overlapping source files
- Normalizes speaker name variants (e.g.
SpeakerA_typo → SpeakerA)
- Splits into chunks of ≤200 messages
Print a clean summary:
Normalize: N clean records → M chunks in .anti-pua/normalized/
Speakers: [name1, name2]
Time range: [start, end]
Step 4 — Tag (detect PUA patterns → tagged.json)
Prerequisites: .anti-pua/normalized/ chunks must exist (produced by Step 3). Falls back to .anti-pua/records.json if normalized directory is absent. Tagging method must have been chosen in Step 1b (or passed via --method).
Use the tagging method chosen in Step 1b. Do not ask again.
Method A: Python Only (regex)
python "${SCRIPT_DIR}/tag.py" \
--workspace WORKSPACE_PATH
Reads from .anti-pua/normalized/ chunks. Falls back to .anti-pua/records.json if no normalized directory exists.
Method B: Python + LLM (hybrid, recommended)
Sub-step 4b-1 — Regex tagging:
python "${SCRIPT_DIR}/tag.py" --workspace WORKSPACE_PATH
Sub-step 4b-2 — LLM semantic analysis via subagent(s):
⚠️ CRITICAL: Inject references/psychological_theory.md verbatim into every subagent prompt. The subagent has zero prior knowledge of PUA taxonomy.
First, read the theory file:
Read "${SKILL_DIR}/references/psychological_theory.md"
Then determine batching based on chunk count M (from Step 3):
| Chunks M | Subagents | Chunks per batch |
|---|
| M ≤ 5 | 1 | all M |
| 6 ≤ M ≤ 10 | 2 | ceil(M / 2) |
| M > 10 | ceil(M / 5) | 5 each (last batch may be smaller) |
Dispatch batches in parallel. Each subagent gets this prompt:
<psychological_theory>
{FULL CONTENT of references/psychological_theory.md — do NOT summarize, inject verbatim}
</psychological_theory>
<task>
Analyze chat chunks for PUA patterns using the theory above.
1. Read these chunk files: <list of chunk paths for this batch>
2. For each message, determine if it exhibits any PUA pattern described in the theory
3. Only flag genuine manipulation — do NOT over-flag normal academic/work conversation
4. Provide reasoning for each flagged message, referencing specific theory concepts
5. Write results to: <workspace>/.anti-pua/llm_tags_<batch_range>.json
Output format:
{
"meta": {"method": "llm", "analyzer": "semantic", "chunks": [...], "total_messages": N, "flagged_count": N, "tag_counts": {...}},
"records": [{"id": N, "speaker": "...", "timestamp": "...", "text": "...", "tags": [...], "severity": 1-5, "confidence": 2-5, "reasoning": "..."}]
}
PUA categories: Gaslighting, Invalidation, Intermittent Reinforcement, Negging, Blame Shifting, Guilt Tripping, Projection, Isolation, Cold Violence
Severity: 1 (minor) to 5 (extreme harm risk)
Confidence: 2 (low) to 5 (high)
Only include flagged messages (non-empty tags). Each reasoning MUST reference a specific theory concept.
</task>
Use mode: "bypassPermissions" for the Task tool. Wait for all subagents to complete before proceeding.
Sub-step 4b-3 — Merge:
python "${SCRIPT_DIR}/tag_merge.py" --workspace WORKSPACE_PATH
Produces tagged.json with meta.method = "regex+llm" and meta.merge_stats.
Method C: LLM Only
Same as Method B sub-steps 4b-2 and 4b-3, but skip sub-step 4b-1. Write LLM results as the starting point for the merge (or directly as tagged.json if only one batch).
After Tagging (any method)
Print:
Tag [method]: N/M messages flagged (K chunks)
Top tags: [Tag: N, Tag: N, Tag: N]
Merge: regex_only=N llm_only=N both=N ← hybrid only
Step 5 — Analyze (profiles + report → analysis.json + profiles.json + report.md)
Prerequisites: .anti-pua/tagged.json must exist (produced by Step 4).
python "${SCRIPT_DIR}/analyze.py" \
--workspace WORKSPACE_PATH
Print after completion:
Analyze: N instances → analysis.json + profiles.json + report.md
Top patterns: [Tag: N, Tag: N, Tag: N]
Step 6 — Visualize (HTML dashboard)
Prerequisites: .anti-pua/analysis.json must exist (produced by Step 5). If this file is absent, stop and tell the user: "analysis.json not found — run /anti-pua analyze <path> first."
python "${SCRIPT_DIR}/visualize.py" \
"${WORKSPACE_PATH}/.anti-pua/analysis.json" \
-o "${WORKSPACE_PATH}/.anti-pua/dashboard.html"
Print after completion:
Dashboard → <workspace>/.anti-pua/dashboard.html
Step 7 — Final Report to User
After all steps complete, print a structured summary:
─────────────────────────────────────────
Anti-PUA Analysis Complete
─────────────────────────────────────────
Messages analyzed: N
PUA instances found: N (High: N Mid: N Low: N)
Top manipulation patterns:
1. [Tag] — N instances
2. [Tag] — N instances
3. [Tag] — N instances
Outputs:
.anti-pua/report.md ← start here (human-readable)
.anti-pua/dashboard.html ← interactive dashboard
.anti-pua/profiles.json ← raw scores
.anti-pua/analysis.json ← full structured data
─────────────────────────────────────────
⚠️ This is pattern analysis, not diagnosis.
Do not add commentary or interpretation beyond the report structure. Let the report speak.
Output Location
All output goes to <workspace>/.anti-pua/. Never write results into the skill directory.
<workspace>/.anti-pua/
├── records.json # raw ingested records (Step 2)
├── normalized/
│ ├── manifest.json # chunk metadata + speaker list + time range
│ └── chunk_000.json ... # clean records per chunk (≤200 each)
├── llm_tags_*.json # LLM semantic analysis results (Step 4, LLM/hybrid)
├── tagged_regex.json # backup of regex-only tags (Step 4, hybrid)
├── tagged.json # final merged tags + severity + confidence
├── analysis.json # aggregated analysis: profiles, hotspots, roles
├── profiles.json # standalone profiles for downstream tools
├── report.md # 7-section human-readable report
└── dashboard.html # interactive single-file HTML dashboard
Re-running: Each step overwrites only its own output files. Running full again overwrites all files. Running a single step (e.g. visualize) overwrites only that step's output, leaving others intact. If you want a fresh run, delete .anti-pua/ first.
Extended Output Structure
.anti-pua/
├── snapshots/ # 历史快照(append 时自动创建)
│ └── YYYY-MM-DD/
│ ├── analysis.json
│ ├── profiles.json
│ ├── tagged.json
│ └── dashboard.html
├── change_report.md # 追加后的变化报告
├── strategy.json # 策略结构化数据
├── strategy.md # 策略人类可读报告
├── timeline.json # 历史指标聚合
└── trend_report.md # 趋势分析报告
PUA Tag Taxonomy
Bilingual rule engine — keywords matched in both English and Chinese.
| Tag | English triggers | Chinese triggers |
|---|
| Gaslighting | "i never said that", "you're too sensitive", "you're imagining things" | "我没说过", "你记错了", "你想太多了", "你又在乱想" |
| Invalidation | "calm down", "no big deal", "stop being so emotional" | "别小题大做", "至于吗", "别闹了", "有完没完" |
| Intermittent Reinforcement | "forget it", "do what you want", "this conversation is over" | "随便你", "你爱怎样怎样", "你自己看着办", "算了我不管了" |
| Negging | "no offense but", "for your own good", "you would look better if" | "不是我说你", "我这是为你好", "你这也太", "已经很不错了" |
| Blame Shifting | "if you hadn't", "you made me", "this is your fault" | "要不是你", "都是你害的", "都怪你", "是你的问题" |
| Guilt Tripping | "after everything i did", "you owe me", "think about how i feel" | "我为你做了这么多", "你想过我的感受吗", "我图什么" |
| Projection | "look in the mirror", "you're just like", "who do you think you are" | "明明是你自己", "你才是那种人", "你自己反省一下" |
| Isolation | "stay away from", "only i understand you", "they're using you" | "少跟…来往", "只有我才真正为你好", "你别听他们的" |
| Cold Violence | "whatever", "figure it out yourself", "not my issue" | "随便", "爱咋咋", "我管不着", "别来烦我" |
Safety Rules
- Do not diagnose. Informational pattern analysis only — not clinical or legal determination.
- Do not blame the victim. Language stays supportive and non-judgmental throughout.
- Separate evidence from inference. Quote the text; name the tag; state confidence.
- Express uncertainty. Use confidence scores and explicit uncertainty notes in every report.
- Avoid therapeutic or legal directives. This tool informs, it does not treat or adjudicate.
JSON Contracts
| File | Key fields |
|---|
records.json | meta.source_files[], meta.total_messages, meta.speakers[], meta.language, records[] |
normalized/manifest.json | total_records, chunk_count, chunk_size, speakers[], time_range[], chunks[] |
normalized/chunk_NNN.json | chunk_id, chunk_index, language, record_count, records[]{speaker, timestamp, text, source_file} |
tagged.json | meta.method (regex / regex+llm / llm), meta.total_messages, meta.flagged_count, meta.tag_counts{}, meta.merge_stats{regex_only, llm_only, both_methods} (hybrid only), records[]{tags[], severity, confidence, matched_rules[], source_methods[], reasoning} |
llm_tags_*.json | meta.method (= "llm"), meta.analyzer, meta.chunks[], meta.total_messages, meta.flagged_count, meta.tag_counts{}, records[]{id, speaker, timestamp, text, tags[], severity, confidence, reasoning} |
tagged_regex.json | Backup of regex-only tagged.json before LLM merge (hybrid method only) |
analysis.json | analyzed_date, total_messages, flagged_count, profiles{abuser_dimensions{}, victim_susceptibility{}}, tag_counts{}, severity_breakdown{high,medium,low}, temporal_hotspots[]{date,count,top_tags[]}, speaker_roles{}, uncertainty_notes[] |
profiles.json | profiles{abuser_dimensions{}, victim_susceptibility{}}, tag_counts{}, severity_breakdown{}, temporal_hotspots[], uncertainty_notes[] |
report.md | 7-section report: overview, abuser strategy distribution, victim vulnerability profile, temporal analysis, high-severity examples, alternative interpretations, uncertainty notes |
dashboard.html | Single-file interactive dashboard (pure HTML/CSS/JS, no external runtime dependencies) |
snapshots/{date}/analysis.json | Same structure as analysis.json |
change_report.md | Markdown: 整体变化、新增操控片段、Tag 分布变化、快照位置 |
strategy.json | generated_date, short_term[]{fragment_id, fragment_text, tags, severity, immediate_response, boundary_action, self_protection}, long_term{priority_dims[], strategies[], relationship_framework}, uncertainty_notes[] |
strategy.md | Markdown: 短期应对策略、长期改善策略、关系决策框架、不确定性说明 |
timeline.json | generated_date, snapshots[]{date, source, total_messages, flagged_count, severity_breakdown, tag_counts, top_hotspot}, trend{flagged_count{}, severity_high{}, overall_interpretation}, recommendations[] |
trend_report.md | Markdown: 整体趋势、快照索引、建议 |