feat(baoyu-wechat-summary): add WeChat group chat summary skill

This commit is contained in:
Jim Liu 宝玉
2026-05-13 22:16:39 -05:00
parent dea9322d0d
commit 1faee80da4
8 changed files with 1173 additions and 1 deletions
@@ -0,0 +1,349 @@
# Output formats — normal & roast digest
This reference defines the two digest variants the skill produces: the **normal** version (default, sober summary) and the **roast** version (毒舌,sarcastic critique, opt-in). Load this file during Step 4 (skeleton) and keep it open through Step 6 (audit).
Both versions share the same overall layout and writing rules; the differences are tone, the leaderboard annotations, the portraits, and the footer. Write the normal version first when both are requested — it's the anchor for incremental mode and the source of truth for the profile updates.
---
## 1. Normal version
### 1.1 Five-part structure
```
[Title line]
[📊 Stats block + Top 10 leaderboard]
[Opening summary — 1-2 paragraphs of prose]
[群友画像 — one entry per active user (3+ msgs)]
[Categorized body — 3-6 self-named sections per day]
[Optional pain-point section]
[Fixed footer]
```
### 1.2 Title line
- Single line, no markdown heading.
- Form: `{群名} 群聊精华 · {日期或日期区间}`
- Date single day: `2026-03-12`. Date range: `2026-03-12 ~ 2026-03-15`.
Example:
```
相亲相爱一家人 群聊精华 · 2026-03-12
```
### 1.3 Statistics block
- Starts with `📊 消息统计: 共 N 条消息`.
- Followed by a leaderboard, top 10 senders by message count, one per line.
- Form per line: `{排名}. {昵称}: {消息数} 条`
- Counting rules:
- Include images, emojis, links, voice transcripts — anything that occupies a chat row is one message.
- Exclude system messages and revoked messages (`[系统]`, `revokemsg`).
- For the `self_wxid` user, substitute `self_display` from EXTEND.md before counting/displaying.
- Resolve ambiguous nicknames (per SKILL.md Step 3.6) before tallying so the same person isn't double-counted.
Example:
```
📊 消息统计: 共 387 条消息
1. 蛙总: 92 条
2. 老王: 58 条
3. 阿喵: 41 条
...
```
### 1.4 Opening summary
- 1-2 paragraphs, plain prose, no headings, no bullets.
- Hook the reader: lead with the most distinctive thread of the day (a heated debate, a surprising announcement, a market move someone reacted to).
- Reference 2-4 of the day's category titles in the prose so the reader knows what's coming.
- Mention 1-2 specific people only if their contribution is central; otherwise stay topic-focused.
- No timestamps, no message counts (those live in the stats block).
### 1.5 群友画像 section
- Heading line: `群友画像`
- One entry per user with 3+ messages this batch.
- Order: by message count, descending.
- Entry header: `{昵称}{角色标签}` — the role tag is your one-line read on this person *today*. Examples: `做空美股的乐子人`, `深夜技术指导`, `论坛级吐槽担当`.
- Body: 2-5 bullets with `•` prefix. Each bullet states one observation. Quote evidence inline where natural.
- Continuity: if you loaded a prior profile in Step 3.7, carry forward the established tags/observations that still apply, and call out *change* explicitly (`今天罕见地没提空头`, `从昨天的乐观转向今天的焦虑`).
- Don't invent backstory — only what's in the messages or the prior profile.
Example:
```
群友画像
蛙总(做空美股的乐子人)
• 全天反复提"做空 SPY",被群友提醒已连续三周看错方向
• 难得正面回应技术问题:"我那个脚本是用 Bun 跑的,慢得跟蜗牛似的"
• 临近收盘转为沉默,与昨日大放厥词的状态对比明显
```
### 1.6 Categorized body
- 3-6 self-named categories per day.
- Each category is a thematic bucket — name it for the *topic*, not generic ("讨论"、"闲聊" are forbidden labels).
- Category header: `{emoji} {标题}` — one emoji prefix, then a short noun phrase.
- Suggested emoji: 🛠 工具/技术,📦 产品发布,📰 新闻/市场,💬 观点辩论,😄 笑料/段子,📚 学习分享,💸 钱与消费,🍜 生活日常。
- Body inside each category: prose with embedded quotes. Use `•` bullets when listing 3+ parallel items; otherwise paragraphs.
- Attribution: name the speaker on first mention in a thread (`蛙总说他...`). For follow-on lines in the same thread, attribution can be implicit if the chain is short and clear.
- Quotes: use 「」 for direct quotes. Quote when the wording is vivid, surprising, or characteristic; paraphrase otherwise.
- Merge: a multi-person discussion is one entry, not a list of one-line replies.
- Links: preserve the full URL inline. Article titles stay verbatim.
Example:
```
🛠 Claude Code 4.7 实测
蛙总下午把 4.7 装上后第一反应是「比 4.6 慢一倍」,老王跟着复现,怀疑是 Opus 默认配置导致。阿喵贴了官方文档 https://docs.claude.com/.../opus-4-7 ,提到可以切回 Sonnet 4.6 跑速测,三人最终结论:复杂任务 4.7 强,日常用 4.6 更顺手。
```
### 1.7 Pain-point section (optional)
- Include only when the day's chat contains at least one concrete unresolved or partially-resolved problem.
- Heading: `今日待解决问题` or `本周悬而未决`.
- One entry per problem. Format:
```
问题:<一句话描述>
提出者:<昵称>
背景:<1-2 句来龙去脉>
状态:<✅ 已解决 / ⚠️ 部分解决 / ❌ 仍未解决>
方案:<若有人提了方案,写在这;否则写"暂无方案">
```
- Skip the section entirely if there are no genuine pain points — don't pad with trivial questions.
### 1.8 Footer
Fixed line, last in file:
```
本简报由 AI 自动生成
```
No date, no signature, no version number.
---
## 2. Roast version (毒舌版)
The roast version mirrors the normal version's structure but inverts the tone. Generate only when `include_roast=true` (configured via EXTEND.md `default_version` or triggered by request keywords like 毒舌/roast/挑衅).
### 2.1 Structural parity
```
[Title line — adds "毒舌版" suffix]
[📊 Stats block — each leaderboard row gets a roast comment]
[Opening summary — absurd recap, sarcastic]
[群友画像 — 不留情面版]
[Categorized body — louder, more brutal category titles]
[Fixed footer — roast version]
```
Pain-point section is **dropped** in the roast version.
### 2.2 Title line
Form: `{群名} 群聊精华 · {日期} · 毒舌版`
Example: `相亲相爱一家人 群聊精华 · 2026-03-12 · 毒舌版`
### 2.3 Statistics block (roast)
- Same `📊 消息统计: 共 N 条消息` opener.
- Leaderboard: each row gets a parenthetical roast comment.
- Form: `{排名}. {昵称}: {消息数} 条 ({一句毒舌评语})`
Example:
```
📊 消息统计: 共 387 条消息
1. 蛙总: 92 条 (一个人撑起了空头的体面)
2. 老王: 58 条 (主要功能是给蛙总当反例)
3. 阿喵: 41 条 (发的链接比发的话还多)
```
### 2.4 Opening summary (roast)
- 1-2 paragraphs, sarcastic recap tone.
- Highlight the day's most ridiculous beat (a failed prediction, a heated argument over something trivial, a wild flex).
- Reference 2-3 of the roast category titles in the prose.
### 2.5 群友画像 (roast — 不留情面版)
- Same per-user entry format as normal, but with the role tag dialed up.
- Amplify quirks, contradictions, fail moments visible in the messages.
- 2-5 bullets with ``. Each bullet is a roastable observation backed by a direct quote.
- Don't invent — every roast must trace back to something the person actually said in the batch (or in the loaded roast profile from prior batches).
Example:
```
群友画像
蛙总(做空美股钉子户,三周亏损不改其志)
• 今天第 47 次预测 SPY 见顶,给出的理由是"我感觉"
• 被老王翻出上周聊天记录后嘴硬:「上周的不算,那是市场不理性」
• 收盘前突然安静,疑似刚看完账户
```
### 2.6 Categorized body (roast)
- Same 3-6 category structure.
- Titles can be louder, more brutal, mock-headline style.
- Examples of acceptable roast titles:
- `蛙总做空翻车: 一个人对抗整个美股`
- `老王再次试图当人生导师, 群友集体打哈欠`
- `阿喵又分享了一篇没人读完的长文`
- Bodies still preserve real quotes and traceability — sarcasm is in framing, not fabrication.
### 2.7 Footer (roast)
Fixed line, last in file:
```
本简报由一个没有感情的 AI 自动生成,如有冒犯,概不负责
```
### 2.8 Red lines (non-negotiable)
These rules override style; violate them and the roast becomes harmful. Write the spicy version first, then audit against this list and rewrite anything that crosses a line.
- **Mock public group behavior only.** Never appearance, weight, body, health, mental state, family, relationships (unless openly group-discussed at the person's own initiative), finances beyond what they publicly mention.
- **调侃 ≠ 人身攻击。** Readers should laugh at the situation, not feel sorry for the target. If you can't think of a joke that doesn't read as cruel, drop the bullet.
- **No timestamp-based sleep/timezone jokes.** Server time ≠ recipient's local time; also implies surveillance. Forbidden: `凌晨 3 点还在群里发言,怕是没睡好`, `这位是哪个时区的`, etc.
- **No medical/psychological speculation.** Even joking diagnoses (`这位需要看医生`, `典型 ADHD`) are out.
- **No outing.** Don't infer identity attributes the person hasn't volunteered (orientation, religion, politics beyond direct quotes).
- **Roast the take, not the person's right to have a take.** `这个观点错得离谱` is fine. `连这都不懂还敢发言` is not.
If a target hasn't said anything roastable in this batch (3+ messages but all neutral), give them a one-line affectionate ribbing and move on. Don't manufacture conflict.
### 2.9 Writing order
1. Draft the spicy version freely — don't self-censor mid-sentence.
2. After the body is complete, do a separate audit pass against §2.8.
3. Rewrite or delete any line that crosses a red line.
4. Then read the whole thing once more for tone calibration: is it fun, or is it mean?
---
## 3. Common formatting rules (both versions)
- **No markdown.** No `**bold**`, no `# headings`, no `*italic*`, no `[link](url)` syntax. Headings are plain text on their own line.
- **Bullets use ``.** Not `-`, not `*`, not `1.` for prose-style bullets.
- **Numbered lists** (`1.`, `2.`) are reserved for the leaderboard.
- **Subcategory hints** within a body block are plain text with no symbol prefix.
- **Links preserved verbatim.** Paste the full URL inline. Don't shorten, don't hide behind text.
- **One emoji per category title.** Don't stack 🛠💬 etc.
- **Pain-point statuses** use ✅⚠️❌ verbatim.
- **Quotes use 「」.** Single quotes for nested.
- **Names verbatim.** Don't abbreviate `蛙总` to ``, don't translate Chinese names, don't anonymize.
---
## 4. Common content rules (both versions)
- **Filter only pure noise.** Cut: lone emoji reactions, "好的"/"收到"/"哈哈哈" with no follow-on, duplicate forwards.
- **Keep gossip, anecdotes, signature moments.** These are the highlight reel — the whole point of the digest.
- **Plain language.** Preserve vivid expressions and idiosyncratic phrasings — that's what makes the speaker recognizable.
- **Keep real names.** Both for traceability and so the digest is useful as memory.
- **Tool, product, URL names complete.** `Claude Code 4.7`, not `CC`. `https://github.com/...`, not `GitHub 上那个项目`.
- **Merge, don't list.** A 30-message debate becomes one paragraph, not 30 bullet points.
- **Direct-quote deep observations.** When someone says something striking, quote it verbatim with 「」 rather than paraphrase.
- **Shared articles → title + sharer.** `阿喵分享了《一个 Rust 工程师的反思》` — include the title and who shared.
- **No timestamp-based sleep/timezone inference.** (Repeated here because it applies to both versions, not just roast — never say `凌晨 3 点还在线` in either.)
- **No fabricated facts.** Every claim must be supported by an actual message in the batch (or in a loaded profile). If you're tempted to "add color," stop.
---
## 5. Output skeleton — quick reference
When you forget the structure mid-write, this is the skeleton:
### Normal
```
{群名} 群聊精华 · {日期}
📊 消息统计: 共 N 条消息
1. {昵称}: N 条
2. {昵称}: N 条
...
10. {昵称}: N 条
{开篇 1-2 段,无标题,直入主题}
群友画像
{昵称}{角色标签}
• {观察 1}
• {观察 2}
• {观察 3}
{昵称}{角色标签}
• {观察 1}
• {观察 2}
🛠 {分类标题 1}
{该分类下的整理过的讨论 / 段落 / 引用}
📦 {分类标题 2}
{...}
今日待解决问题(可选,没有就不写)
问题: {一句话}
提出者: {昵称}
背景: {1-2 句}
状态: ⚠️ 部分解决
方案: {若有}
本简报由 AI 自动生成
```
### Roast
```
{群名} 群聊精华 · {日期} · 毒舌版
📊 消息统计: 共 N 条消息
1. {昵称}: N 条 ({毒舌评语})
2. {昵称}: N 条 ({毒舌评语})
...
{毒舌开篇 1-2 段}
群友画像
{昵称}{放大的角色标签}
• {毒舌观察 1}
• {毒舌观察 2}
🛠 {更大声的分类标题}
{保留真实引用的毒舌叙述}
本简报由一个没有感情的 AI 自动生成,如有冒犯,概不负责
```
---
## 6. Self-check before saving
Before writing the digest file, mentally walk through:
1. Stats block accurate? Counts match the filtered message set?
2. Top 10 names resolved (self_display substituted, ambiguous nicknames disambiguated)?
3. Opening hooks at least one real category title?
4. Every active user (3+ msgs) has a 画像 entry?
5. Every category has a topic-named title (not "讨论")?
6. Every quote uses 「」 and is traceable to a real message?
7. Links inline and complete?
8. No markdown bold/heading/link syntax leaked through?
9. (Roast only) Every roast bullet would pass the §2.8 red-line audit?
10. Footer line exact match?
@@ -0,0 +1,273 @@
# Profiles — user portrait files
This reference defines the per-user profile system. Profiles let the digest carry forward observations across many days so the 群友画像 section in each new digest can show continuity (`蛙总今天罕见地没提空头`) instead of starting from scratch.
Two parallel profile directories live alongside each group's digests:
- `profiles/` — observations sourced from the **normal** version of the digest.
- `profiles-roast/` — observations sourced from the **roast** version.
They are kept strictly separate. The normal-version generation reads only `profiles/`; the roast-version generation reads only `profiles-roast/`. This prevents roast snark from contaminating the sober summary and vice versa.
Load this file during Step 3.7 (load profiles for active users), Step 8.5 (update profiles after digest is written), and Step 9 (backfill).
---
## 1. File format
### 1.1 Path & naming
- Normal: `wechat/{group_id}-{group_name}/profiles/{wxid}-{nickname}.md`
- Roast: `wechat/{group_id}-{group_name}/profiles-roast/{wxid}-{nickname}.md`
The **stable** identifier is the `wxid` prefix. The `-{nickname}` suffix is for human browsability — if it changes, rename the file.
Filename sanitization: replace `/`, `\`, `:`, `*`, `?`, `"`, `<`, `>`, `|`, NUL, and control characters with `_`. Trim trailing dots and whitespace. Cap total filename length at 200 chars (rare nicknames can be very long).
### 1.2 Frontmatter
YAML frontmatter at the top of every profile file:
```yaml
---
name: "<current display name>"
wxid: "<wxid>"
aliases: ["<old nickname>", "<even older nickname>"]
first_seen: "YYYY-MM-DD"
last_seen: "YYYY-MM-DD"
total_messages: N
digest_appearances: N
avg_messages_per_digest: N.N
---
```
Field rules:
- `name`: the most recent display name from `from_nickname` (or `self_display` for the owning user).
- `wxid`: stable; never changes once written.
- `aliases`: append-only; every prior display name we've seen. Don't include the current `name` in this list.
- `first_seen` / `last_seen`: dates of first/most-recent digest appearance, YYYY-MM-DD.
- `total_messages`: cumulative count across all digests this profile has been updated from.
- `digest_appearances`: how many digest files this user has 3+ messages in.
- `avg_messages_per_digest`: `total_messages / digest_appearances`, one decimal.
### 1.3 Free-form body — normal profile
Section headers are plain text on their own line. Order is fixed.
```
角色标签
• {4-6 短语标签}
关注领域
• {领域 1}
• {领域 2}
发言风格
{1-3 句描述,可以多段}
互动模式
• {与某某的互动模式}
• {另一种互动模式}
经典金句
• [YYYY-MM-DD] 「{直接引用}」
• [YYYY-MM-DD] 「{直接引用}」
标志性事件
• [YYYY-MM-DD] {事件描述}
• [YYYY-MM-DD] {事件描述}
```
### 1.4 Free-form body — roast profile
Same plain-text section header style, different sections.
```
人设标签
• {4-6 放大版标签}
核心槽点
• {可吐槽点 1}
• {可吐槽点 2}
毒舌语录库
• [YYYY-MM-DD] 「{该用户说过的话} — {简短毒舌点评}」
• [YYYY-MM-DD] 「{...}」
经典翻车现场
• [YYYY-MM-DD] {翻车描述 + 引用 / 证据}
• [YYYY-MM-DD] {...}
```
---
## 2. Update rules
Rules differ per section. Append-only sections must never lose history; mergeable sections may be rewritten as understanding sharpens.
### 2.1 Normal profile
| Section | Update mode | Notes |
|---------|-------------|-------|
| 角色标签 | **Merge** | Cap 4-6 tags. Can replace less representative tags with stronger ones. Always keep the most consistently-supported tag. |
| 关注领域 | **Merge dedupe** | Add new domains; dedupe by meaning, not exact string. |
| 发言风格 | **Refine** | Only update when a clearly new pattern emerges. Avoid rewriting on every digest. |
| 互动模式 | **Merge** | Add new modes; can refine existing ones with more detail. |
| 经典金句 | **Append-only** | Never delete. No cap. Each entry must be dated and quoted verbatim. |
| 标志性事件 | **Append-only** | Never delete. No cap. Each entry dated. |
### 2.2 Roast profile
| Section | Update mode | Notes |
|---------|-------------|-------|
| 人设标签 | **Merge** | Cap 4-6. Can sharpen tags as patterns repeat. |
| 核心槽点 | **Append-only** | Never delete; recurring 槽点 build up here. |
| 毒舌语录库 | **Append-only** | Never delete. No cap. Each entry dated, with both the quote and the roast comment. |
| 经典翻车现场 | **Append-only** | Never delete. No cap. Each entry dated. |
### 2.3 Frontmatter on every update
- Update `name` if current display name differs from the recorded one. Push the old name onto `aliases` if not already there.
- If `name` changed, also rename the file from `{wxid}-{old_nickname}.md` to `{wxid}-{new_nickname}.md`.
- Update `last_seen` to the current digest's end date.
- Increment `total_messages` by this batch's message count for this user.
- Increment `digest_appearances` by 1.
- Recompute `avg_messages_per_digest`.
---
## 3. Step 8.5 — Update procedure
Run after the digest file(s) are written. Iterate over every user with 3+ messages in this batch.
1. **Look up the profile.**
- Scan `profiles/` (or `profiles-roast/` for the roast pass) for a file whose name starts with `{wxid}-`.
- If found: open it.
- If not found: create a new file using the frontmatter template. `first_seen = last_seen = current digest end date`, `total_messages = this batch's count`, `digest_appearances = 1`.
2. **Resolve wxid for new users.** When a new user appears, you already know their `wxid` from the wx-cli message data — use it directly. If for some reason only the nickname is known, run `wx contacts --query "{nickname}" --json` to resolve; if multiple matches, prefer the one currently in the group (cross-check `wx members <group>` if needed).
3. **Update frontmatter.** Per §2.3.
4. **Update body sections.**
- For mergeable sections (角色标签,关注领域,发言风格,互动模式 / roast: 人设标签): read the existing content, integrate new observations from this batch, rewrite the section.
- For append-only sections (经典金句,标志性事件 / roast: 毒舌语录库,经典翻车现场,核心槽点): append new entries, each dated and verbatim. Never edit or remove prior entries.
5. **Write back.** Overwrite the file.
6. **Source separation.** Pass running for the normal digest writes only to `profiles/`. Pass running for the roast digest writes only to `profiles-roast/`. Even if both versions are generated in the same skill invocation, run two separate update passes.
---
## 4. Step 9 — Backfill procedure
Triggered when the user says `回溯画像`, `初始化画像`, `backfill profiles`, or similar. This builds initial profiles from already-written digest files without re-fetching from wx-cli.
1. **List inputs.**
- List every `*.md` digest file under `wechat/{group_id}-{group_name}/` (top level, not inside `profiles/` or `profiles-roast/`).
- Partition by filename suffix: `*-roast.md` → roast pass, all others → normal pass.
- Optionally also read `history-digests.jsonl` for fast metadata lookup (date, message count) before opening individual files.
2. **Decide whether to run roast backfill.** Only run the roast pass if at least one `*-roast.md` file exists.
3. **Process in batches of 10-15 digest files.** Reading all of them at once will blow context. For each batch:
- Read the digests.
- For each user appearing in the leaderboard or 群友画像 across the batch, accumulate:
- Message counts per digest (from the stats block).
- Role tags and observations (from the 群友画像 section).
- Quotes (from inline 「」 in the body).
- Dated events (from category bodies — when the digest mentions specific incidents).
- Resolve wxid for each accumulated user via `wx contacts --query "{nickname}" --json` if not already cached. Cache the wxid↔nickname mapping for the rest of the backfill.
4. **Threshold.** Generate a profile file only for users appearing in **3 or more** digests in the corpus. Below that, skip (probably one-time visitors).
5. **Write profile files.**
- For the normal pass, write to `profiles/{wxid}-{nickname}.md`.
- For the roast pass, write to `profiles-roast/{wxid}-{nickname}.md`.
- Use the most recent nickname as the filename suffix. Push older nicknames into `aliases`.
- Sort 经典金句,标志性事件,毒舌语录库,经典翻车现场 entries chronologically by date.
- No cap on the size of append-only sections during backfill — let history flow in.
6. **Compute frontmatter.**
- `first_seen` = earliest digest date the user appeared in.
- `last_seen` = latest digest date the user appeared in.
- `total_messages` = sum of per-digest counts.
- `digest_appearances` = number of digests the user crossed the 3-message threshold in.
7. **Report.** After both passes complete, print a short summary:
- `Backfilled {N} normal profiles from {M} digests.`
- `Backfilled {K} roast profiles from {L} roast digests.` (only if roast pass ran)
- List any users skipped due to wxid resolution failures so the user can fix manually.
8. **Re-running backfill is safe.** If the user runs backfill twice, treat existing profile files as the prior state and merge — same rules as Step 8.5 updates. Don't blow away existing append-only entries.
---
## 5. Privacy guardrails
These apply to both normal and roast profiles, with an extra layer for roast.
### 5.1 Forbidden (write neither in normal nor roast)
- **Real-world full names** when only a nickname was used in the group. If the person introduced themselves with `我叫王二`, `王二` is on the table; `王晓明` inferred from another channel is not.
- **Phone numbers, emails, ID numbers, home addresses, employer addresses, exact birth dates** — even if mentioned in the group, don't lift them into profile files.
- **Health, medical, psychological information.** Even self-disclosed (`我最近有点抑郁`) — don't bake it into a permanent profile.
- **Private romantic / family details** unless openly group-discussed by the person themselves. A passing mention by another member doesn't count.
- **Embarrassing private failures.** Public ones (a take that aged badly in front of the group) are fair game; private ones (a job rejection mentioned briefly) are not.
- **Sleep / timezone inference from timestamps.** Server time ≠ recipient's local time, and it implies surveillance.
### 5.2 Allowed
- **Public group behavior** — what they said, how they argued, what they shared.
- **Direct quotes** of things said in the group (these are already public to the group).
- **Interest areas, hobbies, tool preferences** as expressed in group discussion.
- **Interaction patterns** with other group members.
- **Publicly mentioned consumption** (`蛙总今天又分享了买了什么书`) — fine if they themselves mentioned it.
- **Publicly shared travel / life anecdotes** they told the group.
### 5.3 Roast-only extras
In addition to §5.1, the roast profile must **not** include:
- **Anything about appearance, weight, body, looks.**
- **Anything about family members** (their kids, parents, partners) — only the person themselves.
- **Mental-health speculation**, even as a joke. No `这位需要看医生`, no `典型 ADHD`.
- **Identity-based roasts.** No mocking of orientation, religion, ethnicity, nationality, gender.
The roast may mock:
- Stupid takes, contradictions, factual errors.
- Repetitive behavior (`第 47 次预测见顶`).
- Self-undermining moments (`昨天说 X,今天说 not X`).
- Performative flexes that didn't land.
The rule of thumb: **roast the take, not the person.**
---
## 6. Reading profiles during digest generation (Step 3.7)
When loading profile context for a fresh digest:
1. Iterate over users active in this batch (3+ messages).
2. For the normal pass, read `profiles/{wxid}-*.md` for each. Skip if missing.
3. If the current run also generates the roast version, **separately** read `profiles-roast/{wxid}-*.md` during the roast generation pass.
4. Compile a condensed working-memory block:
- The user's current `name` and `aliases` (so you can recognize them under different names).
- 角色标签 / 人设标签 (so you can carry forward or contrast).
- The 3-5 most recent 经典金句 / 毒舌语录 entries (so you can detect callbacks and repeats).
- The 3-5 most recent 标志性事件 / 翻车现场 entries (so you can spot recurring themes).
5. Don't dump the entire profile into the digest — the profile is *context*, the digest is *today*.
If a profile contradicts what you see in today's batch (e.g., the profile says `从不主动发起话题`, but today they started three threads), call that out explicitly in the day's 群友画像 — that's the kind of contrast that makes the digest interesting.