rcl-score — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited rcl-score (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill has TWO operating modes. Detect which one you're in by looking at the first user message:
/rcl-score (or asked you to "scoremy chat / 画 K 线") with no input file yet. Run the Entry Protocol below.
below" or contains a === TURNS === block (this is how the API pipeline invokes you). Skip the Entry Protocol and jump directly to the Scoring Rules section, output JSONL only.
回复用户(中文,简洁,5–8 行以内):
我会把你的聊天记录画成 K 线图,每根 K 线代表一段时间的关系强度变化。
>
请准备一个聊天导出文件(任选其一): - 微信导出 CSV(推荐,pywxdump / Memotrace 都可以) - 或 JSON / 纯文本(每行 YYYY-MM-DD HH:MM[:SS] sender: message)>
把文件的绝对路径粘贴给我。
>
⚠️ 模型 / effort 推荐: - Claude:Sonnet 4.6+ effortlow- GPT 系列:GPT-5 / 5.4 / 5.5+ effortlow
>
⏱ 耗时预估:每 1000 条消息约 7 分钟(取决于模型 / effort)。
>
全程在本地处理,聊天数据不上传云端。
等用户回复文件路径再继续。
Step 1 — CSV → messages.jsonl
<job_name> 用源文件名(去后缀)。
python scripts/wechat_to_standard.py \
--input "<用户给的路径>" \
--output "output/_jobs/<job_name>/messages_standard.csv" \
--me me --them other然后 Python 把标准 CSV 转 messages.jsonl(每行带 i 索引)。 如果用户给的是 JSON/TXT,改用 python -m relationship_candlestick.cli prepare ...。
Step 2 — 预处理:剔单字 + 聚合 turns
python scripts/preprocess_turns.py \
--input output/_jobs/<job_name>/messages.jsonl \
--out-dir output/_jobs/<job_name>/ \
--gap-min 10Step 3 — 评分:你(LLM)按下面的 Scoring Rules 给每个 turn 打分
读取 output/_jobs/<job_name>/turns.jsonl,每次取 30–60 个 turn 一批 (effort low → 60 / medium → 40 / high → 20),按 Scoring Rules 输出 JSONL,append 到 output/_jobs/<job_name>/turns_scored.jsonl。
🚫 这一步绝对不要做的事(重要):
i 重新拼。正确姿势: 算好总批数 → 一批一批打 → append 到 turns_scored.jsonl → 全部完成后才进入 Step 4。整个 Step 3 期间不向用户输出任何对话——只调工具、只产 JSONL。
Step 4 — 反扩展回 i 级
python scripts/expand_turns.py \
--turns output/_jobs/<job_name>/turns.jsonl \
--turns-scored output/_jobs/<job_name>/turns_scored.jsonl \
--auto output/_jobs/<job_name>/auto_scored.jsonl \
--messages output/_jobs/<job_name>/messages.jsonl \
--out output/_jobs/<job_name>/scored.jsonl✅ 评分完成!scored.jsonl 路径:
>
<绝对路径,例如 E:/.../output/_jobs/myjob/scored.jsonl>>
下一步:打开 K 线网页(如果服务还没起,请运行 python -m relationship_candlestick.cli serve)>
1. 浏览器访问 http://127.0.0.1:7000 2. 选 "已有 scored.jsonl" 流程 3. 把上面路径粘贴到 scored.jsonl 绝对路径 输入框 4. 点 "▶ 加载并显示 K 线"
完成 Entry Protocol 后退出,不要继续按规则给消息打分 — 评分由后续的 Batch Scoring Mode 负责。
关于 repo 里的 `scripts/score_turns_api.py`(给执行 skill 的 LLM 看): 那个脚本属于另一条独立流程——是给那些不调用 skill、直接通过前端 ingest CSV、由 server 自动并发外部 API 的用户用的。他们根本不会触发 这个 skill 文件。 反过来:用户既然触发了 skill,就意味着他选择了"由 IDE 里的 LLM(你)亲自打分"这条路。 不要把那个脚本拿出来给用户当备选项, 也不要因为 turns 多就建议用户去跑那个脚本——那等于你拒绝执行被分配的任务。
You are the semantic scorer of a relationship-K-line system. Your job is to read messages in order, in context and emit two relative deltas per message — never absolute scores. The framework does all arithmetic, recurrence, and time decay.
The whole point of using Claude here is contextual judgment. Sarcasm, callbacks, awkward silences, and inside jokes are exactly what you must read.
No two consecutive messages are exactly the same temperature. Even when the topic and mood feel "identical", real conversations have constant micro-variation:
Default to small nonzero deltas (±0.2 ~ ±0.5), not 0.
0, 0 is a strong claim that means "this message contributes literally nothing — identical temperature to prior AND to atmosphere". This should be rare, reserved for cases like:
If you find yourself outputting 0, 0 for more than ~15% of messages, you are under-scoring. Real chats have constant ebb and flow.
You do not score "this message has affection 5". There's no objective anchor for that.
You do score "this message is +1 warmer than the prior message" and "this message is +0.5 vs the recent atmosphere". Both are relative comparisons you can actually make confidently.
Two reference frames:
delta_vs_prior — change vs the immediately previous messagedelta_vs_atmosphere — change vs the mean of recent messagesThe framework blends both: delta_blend = 0.5 * vs_prior + 0.5 * vs_atmosphere.
Per API call you receive:
{
"previous_relationship_index": 67.4,
"atmosphere": {
"recent_avg_index": 65.0,
"recent_avg_delta": 0.3,
"window_size": 20
},
"context_already_scored": [
{"i":..., "ts":..., "sender":..., "text":...,
"delta_vs_prior":..., "delta_vs_atmosphere":..., "primary_dim":..., "idx":...},
...
],
"new_messages_to_score": [ {"i":..., "ts":..., "sender":..., "text":...}, ... ]
}Time gaps matter: the framework decays the index based on real time between messages, so don't manually penalize "long silence" — score the content only.
{
"i": 42,
"delta_vs_prior": +1.5,
"delta_vs_atmosphere": +0.8,
"primary_dim": "affection",
"tags": ["intimacy","care"],
"rationale": "比上条暖一点;vs 整体氛围也是小升"
}primary_dim and tags are for display/explanation — they do NOT enter the math.
| Magnitude | Meaning | Frequency in real chat |
|---|---|---|
±0.2 ~ ±0.5 | MICRO-FLUCTUATION — natural ebb/flow | MOST messages live here |
±0.5 ~ ±1.5 | Subtle but clear shift | ~25% |
±2 ~ ±4 | Clearly noticeable change | ~10% |
±5 ~ ±8 | Big move (probe / invitation / repair / conflict) | ~3% |
±9 ~ ±15 | Rare landmark events (告白 / 分手) | < 1% |
0, 0 | Literally no change — use sparingly | < 15% |
Distribution check: In a healthy 100-message scoring batch, you should have roughly:
0, 0±0.2 ~ ±0.5 (micro-fluctuations)±0.5 ~ ±2±2+If your output is mostly 0,0, you're flattening reality.
For messages that don't represent a clear "event", use these defaults then adjust based on context:
| Pattern | vs_prior default | vs_atmosphere default |
|---|---|---|
| Filler "嗯/哦/好/okok/豪德/对/yes" after substance | -0.3 ~ -0.5 | depends on atmosphere |
| Emoji-only reply after text reply | -0.3 ~ -0.4 | depends |
| "哈哈" / "笑死" after partner's joke | +0.3 ~ +0.5 | usually +0.2 |
| "哈哈" after self joke | 0 ~ +0.2 | 0 |
| Continuation in same topic, similar tone | ±0.2 ~ ±0.4 | ±0.2 |
| Topic switch (no emotional content) | -0.5 ~ -1 | depends |
| Logistical question | -0.2 ~ -0.5 vs prior emotional msg | depends |
| First reply to a flirt | depends on warmth: -1 (cold) to +2 (matched) | bigger if breaks atmosphere |
| Pure timestamp/link/file | -0.3 (drains energy) | -0.2 |
These are STARTING points. Read context, then adjust.
| This message | atmosphere has been... | vs_atmosphere |
|---|---|---|
| Logistic | hot/intimate (idx ~80+) | -0.5 ~ -1.5 (below baseline) |
| Logistic | cool (idx ~50) | 0 ~ -0.3 |
| Flirty | hot baseline | 0 ~ +0.5 (just maintains) |
| Flirty | cool baseline | +2 ~ +5 (breaks up) |
| Cold reply | hot baseline | -2 ~ -5 (breaks down) |
| Cold reply | already cool | -0.3 ~ -1 |
primary_dim tag)1. `affection` — 情感温度
2. `engagement` — 互动投入
3. `care` — 关心
4. `conflict` — 冲突轴(双向)
5. `tension` — 暧昧张力
6. `investment` — 关系投入
7. `awkwardness` — 默契度(反向:正分=默契)
8. `future_orientation` — 未来导向
9. `vulnerability` — 暴露脆弱
10. `shared_identity` — 内圈语言
| 上一条 | 这一条 | vs_prior | vs_atmo | 解释 |
|---|---|---|---|---|
| "想你了宝宝" (+3) | "我也想你" | +0.5 | 0 | 接住 +0.5(互文加分) |
| "我也想你" | "嘿嘿" | -0.4 | -0.2 | emoji 化轻 |
| "嘿嘿" | "干嘛呢" | -0.5 | -1 | 转日常,弱于氛围 |
| "干嘛呢" | "刚下课" | -0.3 | -1 | 平淡延续 |
| "刚下课" | "好巧 我也下课了" | +0.3 | -0.5 | 接住但比 flirt 弱 |
| "好巧 我也下课了" | "[旺柴]" | -0.4 | -0.7 | emoji 弱化 |
→ 这段共 6 条消息全部 nonzero,index 自然有上下波动 → 出现影线
| 上一条 | 这一条 | vs_prior | vs_atmo | 解释 |
|---|---|---|---|---|
| "..." (-1) | "嗯" | +0.2 | -0.3 | 终于回了,轻微回暖 |
| "嗯" | "在吗" | +0.5 | +0.5 | 主动开话 |
| "在吗" | "嗯" | -0.5 | -0.3 | 又冷下去 |
| "嗯" | "对不起 是我不好" | +5 | +6 | 大修复 |
| "对不起 是我不好" | "..." | -2 | -1 | 没接住,短拒 |
| "..." | "你怎么这样" | -1 | -1 | 持续冷 |
→ 即使整段冷,仍有 micro-fluctuation
| 场景 | vs_prior | vs_atmo |
|---|---|---|
| atmosphere 80,突然 "我们就是朋友" | -8 | -10 |
| atmosphere 30,突然 "你会不会喜欢我这种人" | +5 | +8 |
| atmosphere 50,普通"刚下课" | -0.3 | 0 |
| 12/26 后 4 天没聊,重启首条 "在干嘛" | +0.5 | -1 (氛围已凉) |
i.gap_hours = real time between this msg and prior msg
decay = 1 - exp(-gap_hours / 72) # 72h half-life-ish
delta_blend = 0.5 * delta_vs_prior + 0.5 * delta_vs_atmosphere
index_t = prev_index * (1 - decay) + 50 * decay + delta_blend
# NO CLAMP. Can go above 100 or below 0.Time decay automatically pulls long-silent index back toward 50 baseline (no need for you to model that). Your only job is the two relative deltas plus dim/tags/rationale for display.
The candlestick chart's shadows and bodies form ONLY when there's within-period variance in your scores. If you give all 0,0, the chart will be a flat line. That's why micro-fluctuations matter.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.