Obsidian 目录新鲜度着色

SKILL效率
效率 未标注权限 许可证不明确
安装这个技能

Obsidian 目录新鲜度着色,按笔记日期给 Obsidian 文件树自动上色——今天最鲜艳、每天淡一档、7 天后回落分类色;未读和云盘冲突单独成层;一个脚本每日重算,自带十多道自检体检,颜色对了还查颜色看得见看不见。

一句话选型结论

按资料字段整理

Obsidian 目录新鲜度着色是效率类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类效率 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_80179e25/obsidian-freshness-coloring
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

效率
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_80179e25仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量131.2 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
整理待办与日程

把零散任务收进清单,排出优先级。

批量处理重复任务

把固定动作做成模板,重复执行。

把流程写成清单

把口头流程整理成可复用的步骤。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在效率场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
Obsidian 目录新鲜度着色当前 未标注权限 未标注 未标注 未标注 有
Nano PDF 文档 联网访问 ClawHub 市场 424.9 万 OpenClaw 有
自动更新程序 浏览器控制 ClawHub 市场 296.4 万 OpenClaw 有
Gemini 助手 联网访问 ClawHub 市场 254.7 万 OpenClaw 有
word DOCX 文档 联网访问 ClawHub 市场 239.6 万 OpenClaw 有
Excel XLSX 前端 联网访问 ClawHub 市场 227.0 万 OpenClaw 有

SKILL.md 全文

131.2 KB
模型实际读到的说明书原文,来自技能仓库

Obsidian 目录新鲜度着色

让 Obsidian 左侧文件树自己说话:扫一眼就知道哪篇是今天写的、哪篇放了一周、哪些还没读、哪些是云盘冲突垃圾。
产出物只有一个 CSS 片段文件,靠一个 Python 脚本每日重算。

目录着色的特点(本技能的设计核心)

一句话:这套着色不是「给文件夹配个固定颜色」,而是让整棵目录树随时间自己变色——谁新、谁旧、谁没读、谁是垃圾,扫一眼文件树就知道,且不需要任何人手动维护颜色。

四个特征速览:

特征 普通目录着色 本方案
颜色随时间变化 不变,配好就固定 每天重算,今天最艳 → 7 天褪灰 → 回落分类色
文件夹是否着色 只染文件夹,或只染文件 文件夹继承子树最新笔记日期,折叠也看得见
日期从哪来 多为 mtime(云盘同步会污染) frontmatter → 文件名 → mtime 三级回退 + 防伪
多套配色打架 靠手动调启用顺序,别人一改就失效 :not(#id) 拿到 id 位,压过任意类数量,与顺序无关
新目录会不会漏染 会,硬编码清单永远追不上新建的目录,且无人察觉 每次对账,未登记目录按名称哈希自动派色并点名报告
颜色之间会不会撞 不管,配完能显示就算完 每次算 CIELAB ΔE 体检,撞车直接点名(见第 16 节)
日期本身可不可信 不管,mtime 说新就是新 两级判定 mtime 可信度(按目录 + 按时间突发),不可信就换证据链;没证据就撤离着色(见第 19、23 节)
污染换个分布还认得吗 不管 认得——同步跨目录刷也能抓:不看目录看时分(60 分钟内跨 2 目录 ≥10 篇),见第 23 节
别人写的目录色坏了吗 不管,各管各的 扫 snippets 全目录查四类结构隐患(回落层自相矛盾/子串误伤/漏染/非法转义),见第 20 节
配色改了能不能留住 改源码,升级就冲掉 外部 folder_colors.json + --pin-folders 一键固化,升级不冲掉(见第 21 节)
染的是不是真东西 不管,写进 CSS 就算数 死选择器体检:拿真实路径撞每一个选择器,语法合法但永远匹配不到的直接点名(见第 22 节)
谁压过谁 靠片段加载顺序碰运气 级联顺序体检:按「元素 × 通道」实算每条规则的胜负,高优先级层被后面的层压掉就点名(见第 24 节)
只在暗色/高对比下坏 查不到,浅色下看着好好的 三个通道分别算(常规/暗色/高对比),实测揪出高对比下 3 篇未读被覆盖(见第 24 节)
嵌套目录染不染 多数方案只染顶层,二级三级目录标题是灰的 逐层显式枚举 + 继承父色,覆盖率实测 22.1% → 100%(见第 25 节)
自检的分母对不对 没人问过;只统计顶层目录,覆盖率永远 100% 覆盖率体检把目录分成已覆盖/刻意弱化/漏染三类分别计数(见第 25 节)
污染了还继续谎报吗 会,提示归提示,颜色照样错 不会——检测到即免疫,宁可无色不可谎报(实测 309 篇 → 47 篇)
自检靠不靠谱 没有自检 自检本身逐篇实测特异性、不写死常量;输入不变则结论不变(见第 17 节)
最终显示成什么颜色 靠肉眼扫一眼,看不出也就认了 终局颜色体检:按浏览器层叠算法逐篇算出实际渲染色,与设计意图对账(见第 26 节)
只在某个场景下坏一半 查不到,浅色下看着好好的 浅色常规 / 高对比两个场景分别模拟,实测揪出 12 个文件夹标题没被加深(见第 26 节)
颜色真的看得见吗 不管,color 写进 CSS 就算数 渲染弱化体检:opacity 是乘在整个元素上的,别人一句裸声明就能冲淡你;折算后算对比度再点名(见第 27 节)
弱化/归档区怎么表达 用更淡的灰色,淡到看不见为止 语义交给 opacity 通道,基础色保持站得住;「弱化是相对弱化,不是消失」(见第 27 节)
开关会不会越权 没有开关概念 会,且最难发现:一个「不参与新鲜度」的开关被当成「整篇不参与着色」,13 篇笔记(含 4 篇冲突文件)漏染、漏报 57%(见第 28 节)
出问题怎么发现 看不出来 语法自检 + 注册校验 + 陈旧度审计 + 配色体检 + 日期可信度 + 量化告警(分「实际/潜在」)

下面逐条展开。

1. 颜色会过期 —— 这是它和普通着色最本质的区别

绝大多数 Obsidian 配色方案是静态的:给某个文件夹指定一个颜色,然后永远不变。
本方案的颜色是时间的函数:

距今天数 表现
D0(今天) 最鲜艳的橙红 #C2410C,加粗 + 淡橙背景 + 左侧色条
D1 → D4 每天淡一档,橙 → 浅橙 → 藕色
D5 → D6 转灰,热度褪去
D7 及更早 不再着新鲜度色,自动回落到所在目录的分类色

也就是说:今天的笔记明天会自动变淡,一周后自动"沉底"变回分类色。
不需要任何人手动改颜色、打标签、移动文件 —— 颜色随时间自己演进,这是静态配色做不到的。

2. 三层语义叠加在同一行文件名上

一个文件名的最终颜色,是三层信息的合成结果,互不涂花:

  • 分类色(底层,常驻) —— 它属于哪个目录:工作业务蓝、投资理财红、出海 TikTok 青、古文经典紫……
  • 新鲜度色(中层,7 天有效) —— 它有多新:D0 最艳 → D6 近灰
  • 状态色(顶层,优先级最高) —— 它的状态:未读(紫底高亮)、云盘冲突(红 + 删除线)

第 8 天起中层失效,文件自然落回底层分类色 —— 新鲜度是覆盖在分类色之上的一层"临时热度",过期就自动揭掉。

3. 文件夹会继承子树里最新的那篇笔记

不只看文件,目录本身也着色:一个目录的日期 = 它子树内所有笔记里最新的那个日期。
所以即使目录是折叠状态,你也能一眼看出「今天哪个分类动过」——D0 的目录标题是橙红加粗带左侧色条的。
(同类方案只染文件不染文件夹,折叠起来就全瞎了。)

v2.13 起逐层计算:每一层目录都按自己的子树算,父子可以显示不同档位——
父目录最新笔记是 3 天前、某个子目录今天刚动过,就显示「父 D3 / 子 D0」,
一眼看出新东西落在哪一层。此前只算顶层目录。

子目录的分类色则继承最近的有色祖先(05-古文经典/道德经 沿用古籍紫),
不另派新色——53 个嵌套目录各派一色必然撞车,而且它们本来就不是新分类。详见第 25 节。

4. 日期三级回退 + 防伪,抗云盘污染

日期来源按可信度排序:

  1. frontmatter 的 日期 / 更新 / 创建 / created / date / updated
  2. 文件名里的日期(xxx-2026-10-04.md)
  3. 文件 mtime(最后手段)

为什么 mtime 排最后:天翼云盘 / 坚果云 / iCloud 同步会把 mtime 刷成同步时间,用 mtime 判新鲜度必然失真。

防伪:文件名里的日期若与 mtime 相差超过 FILENAME_DATE_MAX_SKEW(默认 400 天),判定为「正文引用的历史日期」而非笔记日期(比如古文笔记文件名里写的朝代年份),自动降级用 mtime,不会把一篇老笔记误染成"今天"。

> ⚠️ 这条防伪规则有个隐含前提,v2.8 在真实库上撞破了(详见第 19 节):
> 它是拿 mtime 当尺子去量文件名日期的。一旦 mtime 本身被云盘同步污染,尺子就是弯的——
> 2026-10-20 实测,06-有道云导入 里 221 篇 2015 年的微信收藏被同步刷成 mtime 2026-10-19,
> 于是 221 个真实日期全被判成「引用日期」丢弃,整批 2015 年的笔记被染成"昨天写的最鲜艳色"。
> v2.8 起:某目录的 mtime 一旦被认定不可信,这把弯尺就不再使用,文件名日期直接采信。

未来日期单列:frontmatter 日期晚于今天的(计划中的笔记)不再被当成"陈旧"静默丢弃,单独统计为「未来/计划」,不着新鲜度色。

5. 精确匹配,不靠子串猜

生成的每条规则都是精确到单个文件的选择器:

.nav-file-title[data-path="01-工作业务/工作业务MOC.md"] { ... }

而不是同类方案常用的子串匹配 [data-path*="2026-10-12"] —— 后者会把任何路径里含这串日期的文件都染上今天的颜色,包括恰好在文件名里引用了历史日期的笔记。

6. 选择器特异性加固 —— 用 id 位取胜,不靠堆类名(v2.3 重写)

这是多套配色共存的库里最容易踩的坑:

> 当两个片段都用了 !important 时,!important 互相抵消,
> 胜负由选择器特异性决定,与片段加载顺序无关。

6.1 v2.0~v2.2 的做法:堆类名(能用,但笨重)

在目标元素自身上重复类名与属性选择器(CSS 合法写法,不改变匹配范围):

.nav-file-title.nav-file-title.nav-file-title[data-path="X"][data-path="X"][data-path="X"] .tree-item-inner

特异性堆到「类 3 + 属性 3 + 后代类 1 = 7」,能赢,但代价是选择器极长:同一个库里 CSS 从 36KB 涨到 90KB。

6.2 v2.3 的做法::not(#id) 直接拿 id 位

CSS 规范里 :not() 取其参数中最高的特异性。所以:

.nav-file-title[data-path="X"]:not(#kb-scope) .tree-item-inner

:not(#kb-scope) 贡献 1 个 id,于是特异性是 (1,3,0)。而 CSS 比较特异性是按位比较——先比 id 位,id 多的一律赢,与后面有多少个类无关。

片段 选择器 特异性 (id-类-元素)
同类方案·动态层 .nav-files-container .nav-file-title[data-path*="…"] .tree-item-inner 0-4-0
同类方案·冲突层 .nav-files-container .nav-files-container .nav-file-title[…] .tree-item-inner 0-5-0
本方案 v2.2(堆类名) .nav-file-title×3 + [data-path]×3 + .tree-item-inner 0-7-0
本方案 v2.3(:not(#id)) .nav-file-title[data-path="X"]:not(#kb-scope) .tree-item-inner 1-3-0
假设对手堆到 20 个类 … 0-20-0 ← 仍然输给 1-3-0

收益:既把胜负从"比谁类名多"变成"比谁有 id 位"——对手无论怎么堆类名都追不上,又让 CSS 体积下降约 23%(实测 90,291 → 69,335 字节)。

要求只有一条:#kb-scope 这个 id 在 Obsidian DOM 里不存在(我们从不添加它),所以 :not() 恒真,纯为抬特异性而存在。想改名字改 BOOST_ID_NAME。

6.3 顺带修掉一个隐性 bug:特异性不能「求和」

v2.2 用「类名数 + 属性数 + id 数」求和来判断胜负。引入 :not(#id) 之后这个模型立刻失真:本方案 (1,3,0) 求和只有 4,对手 (0,5,0) 求和是 5,求和会误判对手赢,而真实规则是本方案赢。

所以 v2.3 改成三元组按位比较(spec_tuple() / spec_gt()):

(1,3,0) > (0,5,0)   → True   ← id 位优先
(0,4,0) > (1,0,0)   → False

同时修掉「属性值里的点号被当成类名」:选择器里 data-path="a/b.md" 的 .md 不是类名,不先抹掉会把 1-3-0 误报成 1-4-0。

6.4 配套的三道保险

  • 写完后回读 appearance.json,确认本片段确实启用且在末位(顺序是双保险,不是唯一依赖)
  • 竞品体检:扫描其他已启用片段里针对文件树的规则,按三元组比较,发现高于本片段就点名告警
  • 输出里直接打印 选择器特异性: 1-3-0,一眼可核

7. 幂等写入,不制造无谓的云盘同步

每次运行先比对生成内容与磁盘现有 CSS(比较时剔除"生成时间"这一行)。
内容没变就不写盘 —— 对一个 60KB+ 的片段文件,避免每天无谓改动触发云盘同步风暴和 Obsidian 重载。
输出会明确显示 内容无变化,跳过写入。

一个容易漏的细节:跳过写入 ≠ 跳过体检。陈旧片段体检是纯只读的,即使内容没变化也会照常输出——否则"今天恰好没写文件"的那天反而看不到旧片段停更的告警。

8. 零插件、零笔记改动、自愈

  • 不安装任何 Obsidian 插件。社区插件(如 File Explorer Color / Supercharged Links)都只能手动给单个文件或文件夹指定颜色,做不到"按日期自动渐变",装了只会引入 CSS 冲突。
  • 不修改任何 .md 内容,不写 frontmatter,只读。
  • 每次执行整体重写 CSS,不存在规则残留;文件增删后重跑一次即可,规则自动跟上。

9. 主题与无障碍

  • 暗色双通道:通道 A 用 body.theme-dark(Obsidian 主题自己的类,权威);通道 B 用 @media (prefers-color-scheme: dark) + body:not(.theme-light),兜底那些不写 .theme-dark 类的第三方主题。背景色自动反转,不会出现浅色块刺眼。
  • 高对比模式(prefers-contrast: more)下淡色档自动加深,保证可读
  • 动画受 prefers-reduced-motion 约束,用户要求减少动效时自动关闭

10. 生成前自检 + 生成后校验

生成前:用状态机扫描注释开合,能抓出「注释提前闭合 + 裸 * 行」这类隐性损坏——只数 /* 和 */ 的个数是查不出来的(个数依然配对,但规则已被解析器吞掉)。同类方案的自动生成片段就出过这个事故,实测能准确报错到第 2/3/4 行。校验不通过直接中止写入,原 CSS 保持不动。

生成后:回读 appearance.json 校验启用状态与顺序,并体检竞品片段特异性。

11. 「颜色会过期」的另一半 —— 绝对日期型片段会静默失效(v2.1 新增)

第 1 节说「颜色是时间的函数」,这里补上容易被忽略的反面:如果片段把日期硬编码进选择器,它就变成了一次性产物。

/* 绝对日期型:这条规则只在 2026-10-12 当天有意义 */
.nav-file-title[data-path*="2026-10-12"] .tree-item-inner { color: #C2410C; }

这类片段必须靠生成方每天重跑。一旦生成方停更,后果是:

  • CSS 没有任何语法错误(语法自检查不出来)
  • 片段仍然启用、仍在 enabledCssSnippets 里
  • 但规则里的日期整体滑出窗口,颜色悄悄没了,或更糟——把最鲜艳的「今天色」错染到过期日期上

用户只会觉得「今天的新笔记怎么没颜色」,很难联想到是另一套配色停更了。

实测案例(真实库,2026-10-14):

项 值
片段 teleagent-dynamic-colors.css(某同类方案的动态层)
硬编码日期 2026-10-06 ~ 2026-10-12,共 7 条
文件 mtime 2026-10-12 01:00,此后未再生成
实际状态 5/7 条规则仍在 7 天窗口内(看着像在工作),但最鲜艳的「今天色」已错染到 2 天前的笔记;10-13、10-14 的笔记无规则匹配

注意这里的陷阱:只判断「窗口内是否还有规则」会漏判——它还有 5 条规则在生效,肉眼完全看不出异常。

正确判据:绝对日期型片段的规则里必须包含今天。不含今天 → 即判陈旧,按 今天 - 最新规则日期 报告过期天数。本技能已实现为 audit_stale_snippets(),每次运行与 --check 都会输出告警。

本方案为什么不受影响:生成的 CSS 里没有任何绝对日期,只有「相对新鲜度」D0~D6,且每天整体重算。所以哪怕某天漏跑,也只是颜色整体滞后一天,不会出现「规则全部失配」的静默失效。

12. 告警不是形容词,是篇数(v2.2 新增)

发现别人的片段停更了,只说「今天色错染在 3 天前的笔记上」,用户仍然没法决策——20 篇被染错和 1 篇被染错,处置优先级完全不同。

所以告警必须落到本库真实篇数上。做法是先把全库笔记按日期做直方图,再与陈旧片段的规则日期交叉统计,一次算出两个数字:

本库实际受影响:20 篇 2026-10-12 的笔记被错染成「今天色」,今天的 3 篇笔记无规则匹配
  • 错染篇数 = 全库里路径包含该日期字符串的笔记数(口径必须和竞品一致:它用的是 data-path*="2026-10-12" 子串匹配,所以得按路径子串数,而不是按笔记自身日期数)。它能和同一次运行的 Dn 分档互相印证:停更 3 天 → 对应 D3 档的 20 篇,两个数字对得上说明口径没跑偏。
  • 漏染篇数 = 今天的笔记数,也就是输出里的 D0 篇数——本该最鲜艳,实际完全没颜色。

真实库连续三天的实测(同一个停更片段):

观测日 停更天数 窗口内剩余规则 错染篇数 当天漏染
10-13 1 天 6/7 少 少
10-14 2 天 5/7 中 中
10-15 3 天 4/7 20 篇 3 篇

「窗口内剩余规则」每天减少却始终不为 0——这正是它难被发现的原因:只要还有规则落在窗口内,文件树看起来就"像是有颜色"。

有了具体篇数,处置标准就能量化:错染 + 漏染超过 10 篇就停用该片段;个位数可以等生成方恢复。

13. 但「篇数」还得分「实际」和「潜在」(v2.3 修正)

上一节的算法有个隐含前提:只有那一个片段在生效。真实库里往往几套配色并存,这个前提不成立,于是 v2.2 会虚报。

2026-10-16 的真实库实测:

口径 篇数 说明
v2.2 报的「实际受影响」 20 篇 按"笔记日期 = 2026-10-12"统计
竞品真实命中(路径含该日期) 16 篇 它用的是子串匹配,口径应该按路径算
其中被本片段覆盖并压制 16 篇 本片段 (1,3,0) > 竞品 (0,1,0),浏览器采用本片段的颜色
实际受影响 0 篇 一条都没真被染错

同一个库、同一天,v2.2 说"20 篇被错染",实际一篇都没有——因为本片段对每一篇都发了更高特异性的规则,竞品那条根本轮不上。

所以 v2.3 把告警拆成两个数:

  • 实际影响(effective) = 竞品命中 − 被本片段压住的。这是现在正在发生的。
  • 潜在风险(potential) = 竞品命中总数。这是"本片段若被停用"会发生什么。

两个数不一致时,告警级别从 [告警] 自动降为 [提示],措辞也改掉:

[提示] 片段 teleagent-dynamic-colors 为「绝对日期型」,最新规则日期 2026-10-12 已过去 4 天…
本库命中 16 篇,但全部被本片段压制(特异性 1-3-0 > 0-1-0),实际 0 篇受影响;
若本片段被停用,这 16 篇会立刻错染成「今天色」

为什么值得较这个真:告警一旦天天喊狼来了,用户就会整段忽略——而真出事那天(比如本片段被别的工具从启用清单里挤掉)恰恰是最需要被看见的。分级之后的规则很简单:

输出级别 含义 该做什么
[提示] 旧片段停更了,但被本片段压着,当前无害 不用动,记着「本片段是唯一的压制者」这件事
[告警] 有笔记真的被染错了(本片段没覆盖到) 按篇数处置:>10 篇停用旧片段,个位数等生成方恢复

--strict 也随之调整:只在 level=告警 时才以退出码 2 结束。被压制的陈旧片段不该让定时任务天天红。

配套的两个开关:

# 定时任务把"别人的片段停更"当真错误抛出(陈旧时退出码 2)
python scripts/refresh_note_colors.py --vault "<vault>" --check --strict

不加 --strict 不影响退出码,避免误伤既有自动化。--json 的 stale_report 同时输出结构化告警(片段名 / 最新规则日期 / 过期天数 / 窗口内剩余规则数 / 错染篇数 / 漏染篇数),可直接喂看板。

14. 随时可做的只读体检(v2.1 新增)

python scripts/refresh_note_colors.py --vault "<vault>" --check

不写 CSS、不动 appearance.json,只输出:统计 + 片段注册状态 + 竞品特异性告警 + 陈旧片段告警。
怀疑颜色不对时先跑这个,比直接重跑安全——不会产生任何副作用。

15. 目录配色清单会「漏染」——而且没人会发现(v2.4 新增)

前面 14 节都在讲颜色怎么变化,这一节讲颜色根本没被派上的情况——这是所有硬编码配色方案的通病,也是本方案 v2.4 之前自己身上的一个真实 bug。

15.1 病根:目录色是一张手写清单

不管是本方案的 FOLDER_COLORS,还是同类方案的静态层,目录色都是一张手写的映射表:

FOLDER_COLORS = {"00-收件箱": "#D97706", "01-工作业务": "#2563EB", ...}

知识库是会长的。新建一个目录,若没人回头改这张表,代码里那句 if fd not in FOLDER_COLORS: continue 就会静默跳过——不报错、不告警、不写日志。

15.2 真实库的实测代价(2026-10-17)

未登记目录 笔记数 占比
WX同步 11 篇 6.5%
SkillHub 6 篇 3.6%
合计 17 篇 10.1%

v2.4 首次运行就把它俩揪了出来,自动派色为 WX同步 → #0F766E、SkillHub → #7C2D12。

这两个目录长期无分类色,而且同类方案的静态层同样漏掉了它们——说明这不是谁写错了,是「硬编码清单」这个做法本身的结构性缺陷。

15.3 为什么「没颜色」比想象的严重

不是少个颜色这么轻。回顾第 2 节的三层语义:第 8 天起新鲜度色失效,文件要回落到分类色。分类色不存在意味着:

  • 这批笔记在 7 天内是有热度的(新鲜度色生效)
  • 第 8 天直接掉到 Obsidian 默认灰,而不是掉到自己的分类色
  • 于是它们和「根目录散落的临时文件」混成一片 —— 分类色作为长期身份标识的作用完全失效

更麻烦的是时间越久越难发现:新建目录的头几天,里面的笔记都是新的,新鲜度色盖着,看着挺正常;等热度退了才发现"这一片怎么灰扑扑的",但那时已经说不清是哪天开始的。

15.4 解法:每次扫描都对账 + 自动派色

v2.4 起不再静默跳过。每次运行把实际扫到的顶层目录与配色清单对账:

未登记配色目录: 2 个 / 涉及 17 篇 —— 已自动派色(建议补进 FOLDER_COLORS 固化)
  SkillHub: 6 篇 -> #7C2D12
  WX同步: 11 篇 -> #0F766E

自动派色有三个必须守住的约束:

  1. 稳定可复现 —— 而且不能依赖"这批目录有几个"。
    第一版写成「按遍历顺序逐个取,占用了就顺移下一个」,看着合理,实测一验就露馅:新建或删掉另一个未登记目录,其余目录的颜色会整体平移。正确做法是两轮——先在候选色表上按目录名 SHA1 哈希直接取(每个目录独立算,别的目录来去都不影响它),只有真撞色的才顺移。回归验证:

两目录: {SkillHub: #7C2D12, WX同步: #0F766E} 四目录: {SkillHub: #7C2D12, WX同步: #0F766E, 新目录A: …, 新目录B: …} 稳定性: WX同步 True | SkillHub True

  1. 不与已登记色撞车 —— 派色前排除 FOLDER_COLORS 里已占用的值。
  2. 更不能与语义色撞车 —— 排除集还要并上 FRESH_COLORS、FRESH_COLORS_HICONTRAST、未读色、冲突色、根目录色。这是实测踩出来的:初版把 SkillHub 派成了 #C2410C,正好是 D0「今天」的橙红,于是"这个目录是橙红的"和"这篇是今天写的"两种语义撞在一棵树上,反而制造误导。

生成的 CSS 里也会标注来源,方便日后回查:

/* SkillHub(自动派色:本目录未登记配色) */

--json 另有结构化字段 uncolored_folders(目录 / 篇数 / 颜色),可喂看板做长期趋势。

15.5 自动派色只是止血,不是终点

自动色能保证不漏染,但不能保证好看或语义贴切(WX同步 拿到玫红纯属哈希结果)。所以它定位是兜底 + 告警:

  • 看到报告里出现未登记目录,顺手补进 FOLDER_COLORS 就固化了
  • 想彻底关掉自动派色:设 AUTO_FOLDER_COLOR = False,退回"只染已登记目录"(不推荐,等于重新接受漏染)

这一节的通用教训:凡是「手写清单 + 静默 continue」的组合,都会随数据增长悄悄失效。要么对账后告警,要么干脆不让它静默。本方案之前的 12~14 节都在处理别人的片段静默失效,这一节处理的是自己的。

16. 颜色写对了,不等于「能看出来」(v2.5 新增)

前 15 节解决的是「颜色有没有写进 CSS」。这一节解决的是更隐蔽的一层:每个颜色单看都合理、CSS 语法完全合法、片段正常启用、颜色值也没写错——但两个颜色放在一起人眼分不开,整套着色就白做了。

16.1 三种撞车,全部静默

类型 判定 后果
A 回落撞车 目录分类色 ≈ 新鲜度色阶某档(ΔE<20) 第 8 天新鲜度失效、回落到分类色时肉眼看不出变化,用户以为这篇还新鲜 —— 等于新鲜度着色静默失效
B 目录互撞 两个目录的分类色 ΔE<20 分类色失去辨识作用,等于没分类
C 状态色撞车 未读色/冲突色 ≈ 某目录色(ΔE<12) 该目录下的状态标记被"淹没"

A 类最阴,因为它和第 1 节「颜色会过期」直接对冲:本方案的全部价值在于「颜色过期时你能看出来」,而回落撞车让过期的那一刻恰好不发生视觉变化——你设计的信号,被自己选的颜色抹掉了。

16.2 实测数据(2026-10-18,171 篇)

给目录色逐个算 ΔE76 后,揪出 4 处真撞车,全部已换色:

目录 原色 撞上 ΔE 新色
Clippings #B45309 D1 #D4621F 9.8 #854D0E
Excalidraw #9333EA 05-古文经典 #7C3AED 7.1 #115E59
00-收件箱 #D97706 D2 #E07B39 14.5 #CA8A04
02-投资理财 #DC2626 D0 #C2410C 18.8 #9F1239

换色后仍保留原语义(暖=待处理 / 红=投资 / 褐=剪藏 / 青=画布),只是把距离拉开。修复后整库体检归零。

16.3 防虚警:只对"真会发生"的撞车报警

这一步比算法本身更重要——告警一旦天天刷屏,人就会习惯性忽略,体检器也就白做了(和第 13 节把「命中篇数」拆成「实际/潜在」是同一个纪律)。两条过滤:

  1. 回落撞车只判参与新鲜度着色的目录。04-模板、90-归档、Templates 在 EXCLUDE_FRESHNESS 里,永远不经历 D0~D6,算出来 ΔE 也很小(90-归档 vs D6 只有 7.5),但报了就是假警报——它们压根不会"回落"。
  2. 刻意同色组显式声明。模板/归档/工具脚本本来就都属于「弱化区,灰、不抢眼」,彼此不需要互相区分。不声明的话这几个灰阶会稳定刷出 6 条互撞告警,全是真数值、全是假问题。用一行配置把意图写明:

python INTENTIONAL_SAME_GROUP = {&quot;04-模板&quot;, &quot;Templates&quot;, &quot;90-归档&quot;, &quot;tools&quot;}

显式声明意图,比让体检器闭嘴、或假装没看见都诚实。

淡色档的低对比度(D2~D6 在浅色主题下 1.99~2.97:1)也只报一行区间摘要而非逐档 5 行——「逐级变淡」是设计意图,靠 prefers-contrast 高对比档兜底,但同样不能让它每天刷屏。所有逐档数值仍保留在 --json 的 palette_report 里,要查随时能查。

16.4 两个踩坑(都是修 bug 时自己造的)

坑 1:把候选池榨干,反而制造出同色。 给自动派色加 ΔE 过滤时,直接按 ΔE≥20 对全部保留色过滤。但保留色有 30+ 个(14 个目录色 + 7 档新鲜度 + 7 档高对比 + 未读/冲突/根/全局),要求与每一个都拉开 ΔE20 几乎不可能,候选池被榨到只剩 1 个槽位。池子一小于待派目录数,那条「撞了就顺移」的兜底就永远顺移到同一个颜色——SkillHub 和 WX同步 拿到同一个 #A21CAF。修撞车反倒制造了更严重的撞车。

坑 2:池子依赖批次,把稳定性搞没了。 改成「池子够分就停」后,池大小依赖 len(names),于是同一个目录在单目录批次和多目录批次下拿到不同颜色(WX同步 单独派 #A21CAF、四个一起派 #15803D),等于把第 15.4 节好不容易建立的稳定性又退回去。

结论(通用):候选池只能取决于已占用色集合,绝不能取决于这批要派几个;宁可把阈值放宽到 ΔE12,也绝不能让两个目录共用同一颜色。两条硬保证冲突时的优先级——唯一性 > 稳定性:共用色 = 分类直接失效,比"某个目录换了个颜色"严重得多。有限色板下「绝对稳定 + 绝对唯一」不可兼得(鸽笼原理),别硬凑,想清楚在文档里写明白。

16.5 怎么读报告

配色体检: 发现 1 处撞车
  [提示] D2~D6 淡色档在浅色主题下对比度 1.99~2.97:1(低于 3:1) —— 「逐级变淡」是设计意图…
  • 只出现这一行 = 健康,淡色档是有意设计
  • 出现「回落撞车」= 该改 FOLDER_COLORS,否则这批笔记的新鲜度信号是假的
  • --strict 下「回落撞车(告警级)」与陈旧片段同类,都会让进程以退出码 2 结束,可接流水线
  • --json 的 palette_report 给出结构化字段(类型/级别/目录/色值/ΔE),可喂看板做长期趋势

这一节的通用教训:配色方案的正确性不止「颜色值写对」,还有「颜色之间分得开」。前者有语法校验兜底,后者没有任何现成工具会告诉你——必须自己算感知距离,而且必须对「什么算真问题」做诚实界定,否则体检器自己会变成新的噪声源。

17. 告警会自己翻转 —— 覆盖口径与 !important 的两处真 bug(v2.6 新增)

前 16 节都在往里加检查。这一节反过来:检查本身出了错,而且错得让人信以为真。

2026-10-19 的例行刷新里,同一批 16 篇笔记、一个字没改,陈旧告警却从昨天的「实际 0 篇受影响」自动升级成了「16 篇受影响」。追下去发现三处问题,全部已修。

17.1 分类色缺 !important,回落被竞品整层压掉(真 bug,有实证)

第 1 节承诺「第 8 天回落到所在目录的分类色」。实测发现:这层回落从来没真正生效过。

v8 加目录分类色时只写了裸 color: #15803D,没带 !important。而竞品片段 teleagent-dynamic-colors 的绝对日期规则是带 !important 的:

/* 竞品:带 !important,特异性 (0,4,0) */
.nav-files-container .nav-file-title[data-path*=&quot;2026-10-12&quot;] .tree-item-inner {
  color: rgba(255, 68, 68, 1) !important;
}
/* 我方 v8/v9:裸 color,特异性再高也没用 */
.tree-item.nav-folder[data-path=&quot;WX同步&quot;] .tree-item.nav-file .tree-item-inner {
  color: #15803D;
}

!important 面前特异性无效——这是第 6 节那条「靠特异性取胜」的前提条件:只有双方都带 !important 时,才轮到特异性说话。我方分类色没带,直接判负。

后果非常具体:凡是路径里含某个日期、且已过 7 天的笔记,本该显示分类色(绿/青/紫),实际一直显示竞品那条过期的红。2026-10-19 实测 16 篇(05-古文经典、02-投资理财、WX同步 等)。用户以为看到了「回落」,看到的是别人的残影。

修法是给目录级规则补 !important,优先级顺序不变:

层 选择器特异性 说明
未读 / 冲突 / 新鲜度 / 根目录 1-3-0(:not(#kb-scope) 拿 id 位) id 位压过一切纯类组合
目录分类色(本次加固) 0-6-0 + !important 压过竞品 0-4-0,但仍低于文件级 id 位
竞品动态层 0-4-0 + !important 被上面两层压住

17.2 覆盖口径太窄,告警自己翻转(比 bug 更危险)

v7 建立的 covered(本片段发过规则的笔记集合)口径是 新鲜度 + 未读 + 冲突 + 根目录,漏了「被目录分类色覆盖的文件」。

于是出现这样一条曲线:

日期 竞品最新规则日期 陈旧天数 告警级别 报告篇数
10-18 2026-10-12 6 天 提示 实际 0 篇(16 篇命中全被压制)
10-19 2026-10-12 7 天 告警 实际 16 篇(0 篇被压制)

笔记一篇没变,告警级别自己翻了。原因:这批笔记第 7 天从 D6 桶里掉出来,不再算「新鲜度覆盖」,于是被判成「本片段没管它」。可它们明明还被分类色管着——只是分类色没被计入口径。

这类 bug 比 17.1 更危险,因为它生产假结论:运维看到「告警升级」会去查库,查不到任何变化,几次之后就不再信这条告警了。

修法:把分类色覆盖的文件计入 covered。

17.3 竞品特异性改为实测,并逐篇比较

原来审计里对手特异性是拍脑袋写死的:

their_spec = (0, 1, 0)   # 只数了 [data-path*=&quot;…&quot;] 这一个属性位

真实选择器是 .nav-files-container .nav-file-title[data-path*=&quot;…&quot;] .tree-item-inner = (0,4,0)。低估对手会得出「我们稳赢」的自我安慰——换个更强的竞品片段立刻翻车。

改成从 CSS 里把含该日期的选择器抠出来实测,并且逐篇比较(文件级 1-3-0、目录级 0-6-0 混用,不能拿一个统一值代表):

their_spec = _max_sel_spec(css, latest_str) or (0, 1, 0)
suppressed = sum(1 for r in hit
                 if r in covered_spec and spec_gt(covered_spec[r], their_spec))

回归验证(同一批 16 篇,改变对手强度):

对手特异性 压制篇数 实际受影响 告警级别
0-4-0(真实值) 16 0 提示
0-6-0 0 16 告警
0-8-0 0 16 告警

改之前(写死 0-1-0)三种情况都会报「16 篇被压制、0 篇受影响」——结论恒真,等于没检查。

踩坑:_max_sel_spec 初版按字符扫描,把竞品文件里坏掉的注释(第 2~4 行是没被 /* */ 包住的裸文本,恰好含日期)当成选择器解析,量出 (0,4,16) 这种荒唐值(裸单词被当成元素选择器)。修正:先 re.sub 去掉成对注释,再逐行跳过以 * 开头的残留行。教训:解析别人的文件时,"先假设它是坏的"要写进代码,不能只在脑子里假设。

17.4 新增:mtime 簇污染检测 —— 50 篇"假新鲜"(2026-10-19 实测)

当天报告里 D1(1 天前)突然冒出 58 篇,而 D0 只有 3 篇、D2 只有 2 篇。查来源:58 篇里 50 篇的日期只来自 mtime,其中 05-古文经典 子目录独占 30 篇——全是《道德经》《论语》《孙子兵法》这类根本不可能是昨天写的古文笔记。

成因:天翼云盘 eCloud 重新拉取 / 整目录复制 / 解压覆盖,会把大批老文件的 mtime 统一刷成同一天。脚本照章办事(mtime 是三级回退的最后一级),于是把一批老笔记染成「昨天写的最鲜艳色」。全程没有任何报错,肉眼看只是"怎么一片橙"。

这类污染在元数据层面查不出来——文件确实是被改过。只能靠形状识别:

> 同一天 + 日期来源纯 mtime + 文件名里也没有日期 + 成批出现

于是新增 audit_mtime_clusters(),阈值 MTIME_CLUSTER_MIN = 15。实测抓到:

日期可信度: 发现 1 处疑似 mtime 污染
  [提示] 2026-10-18(1 天前)有 50 篇笔记的日期**只**来自文件 mtime、文件名里也没有日期,
  疑为云盘同步/批量触碰导致的 mtime 污染(05-古文经典 30 篇、(根目录) 5 篇、tools 4 篇);
  这批笔记实际可能并不新,新鲜度色仅供参考(本函数只报告,不擅自改判)

只报告、不擅自改判,是刻意的:真的当天批量导入 50 篇新笔记也会长成这个形状,机器分不清"同步刷了 mtime"和"真的导入了 50 篇"。给不出可靠判断时就别动手——把数字摆出来,判断权留给人。这也顺带回答了一个长期指标:mtime 来源占比应该持续下降(本库 51/178,仍偏高),补 frontmatter 日期 才是根治办法。

--json 新增 mtime_clusters / mtime_cluster_warnings 两个字段,可喂看板做趋势。

17.5 怎么读这一节的报告

片段校验: 校验通过:已启用且在末位
  [提示] 片段 teleagent-dynamic-colors 为「绝对日期型」,最新规则日期 2026-10-12 已过去 7 天…
  ;本库命中 16 篇,但全部被本片段压制(逐篇比较,我方最弱 0-6-0 &gt; 对方 0-4-0),实际 0 篇受影响
  ;若本片段被停用,这 16 篇会立刻错染成「今天色」
  • 「逐篇比较,我方最弱 X > 对方 Y」是关键——说明两个数都是实测的,不是写死的
  • 仍是「提示」而非「告警」= 我们确实压住了,--strict 不会因此退出码 2
  • 「若本片段被停用」那句是潜在风险,提醒别轻易关掉本片段

这一节的通用教训:给系统加自检,和给系统加功能一样会引入 bug,而且自检的 bug 更贵——功能错了你会在界面上看见,自检错了你会看见一个"看起来很专业"的错误结论并据此行动。所以自检本身至少要满足两条:① 结论能被独立复现(这里是"逐篇实测特异性"而不是拍脑袋常量);② 输入没变时结论不能自己变(17.2 那条翻转曲线就是最好的反例)。

18. 关系图谱也按目录分类着色(v2.7 新增)

前 17 节都在讲左侧文件树着色。这一节补上另一个界面:关系图谱(Graph View)——用户要求"查看关系图谱中也需要颜色分类",于是把目录分类色同步到图谱节点。

18.1 为什么不能靠加 CSS

文件树是普通 DOM,CSS color 直接生效。图谱节点是 canvas 渲染,CSS 打不进去;节点颜色只能由 .obsidian/graph.json 里的 colorGroups 机制控制。所以这条需求不能再加 CSS 片段,得给 refresh_note_colors.py 新增"生成 graph.json color groups"的能力。

18.2 graph.json 的 colorGroups 格式(实测官方格式)

{
  &quot;colorGroups&quot;: [
    { &quot;query&quot;: &quot;path:01-工作业务/&quot;, &quot;color&quot;: { &quot;a&quot;: 1, &quot;rgb&quot;: 2450411 } }
  ]
}

三个最容易踩的坑:

坑 说明
rgb 是十进制整数,不是 hex 字符串 #2563EB 要存成 int(&quot;2563EB&quot;, 16) = 2450411。手改这文件时几乎人人踩
path: 是子串匹配 path:01 会匹配 01-工作业务/x.md 也会匹配 a/01-b.md。必须尾部加 / 锚定:path:01-工作业务/
first-match-wins 数组从上到下,第一个匹配的组染色,后面的不再看。顺序重要

18.3 只染分类色,不掺新鲜度

图谱节点只染分类色(FOLDER_COLORS + 自动派色),不跟 D0~D6 新鲜度渐变。理由:图谱是用来看结构的,再叠新鲜度会让节点颜色每天变、且 canvas 上渐变不如文件树直观,反而过载。分类色是"这个节点属于哪个目录"的长期身份,正合适。

18.4 保留用户手建的颜色组

graph.json 里可能已经有用户自己建的组(按 tag:#重要、[status:done] 等)。本方案不覆盖这些组,只管理"按目录分类"那一类:

  • 区分"我们的组"和"用户的组":colorGroup 项里没有 name 字段,所以用 query 集合判据——凡是 query 命中我们本次生成的 path:&lt;目录&gt;/ 集合的算 ours(替换为本次最新色),其余算 theirs(原样保留)。
  • 顺序:用户组排在前,我们的目录组排在后。first-match-wins 下用户组优先——符合"用户主动建的强调应覆盖底层目录色"的自然预期。
  • 其他图谱设置原样保留:collapse-filter / search / showTags / 力学参数等一个不动,只改写 colorGroups 字段。

18.5 与文件树同口径、同色值、同幂等

  • 同口径:图谱排除 EXCLUDE_FRESHNESS 目录(模板/归档),与文件树 build_css 的分类色输出范围一致。
  • 同色值:直接复用 FOLDER_COLORS 的 hex,转成十进制 rgb 写入。
  • 同幂等:整段 colorGroups 未变化则跳过写入,与 CSS 片段同一纪律,不制造云盘同步抖动。

18.6 与 --check / --no-register 的关系

  • --check 只读体检会顺带回读 graph.json,报告"11 个目录组已就位(另有 N 个用户组)";若发现某个目录组颜色过期会 [告警]。
  • --no-register 同时跳过 graph.json(与 appearance.json 同进退,保持"最小副作用"语义)。
  • 想完全关掉图谱着色:设 GRAPH_COLOR_GROUPS = False。

18.7 怎么读报告

图谱着色: 已写入 11 个目录分类色组(保留 1 个用户组)
图谱着色: 内容无变化,跳过写入(11 个目录组,保留 1 个用户组)   ← 幂等
校验通过:11 个目录组已就位(另有 1 个用户组)                    ← --check
[告警] 图谱 colorGroups 与当前目录配色不一致:颜色过期 1 个目录组  ← 有人改了 graph.json

实测验证(最小测试 vault,3 篇笔记 + 1 个预置用户组):生成 11 个目录组且保留用户组、第二次运行幂等跳过、手动改坏某目录 rgb 后 --check 准确报"颜色过期"、正式生成修正回正确值且不重复追加。

19. 宁可无色,不可谎报 —— mtime 污染免疫(v2.8 核心)

19.1 问题:提示写得再准,颜色本身还是错的

v2.6 加了 mtime 簇污染检测,会告诉你"某目录 232 篇笔记的日期只来自 mtime、集中在同一天,疑似云盘污染"。
但那一版只提示、不改判——理由是"真的当天批量导入也会长这个形状,机器分不清"。

这个理由站得住,可代价在 2026-10-20 变得无法接受:

v2.7(只提示) v2.8(免疫)
扫描 419 篇 419 篇
7 天内着色 309 篇(74%) 47 篇(11%)
D1 一天 235 篇 3 篇
CSS 体积 241 KB 55 KB

419 篇里 309 篇挂着新鲜度色,D1 单独占 235 篇——新鲜度信号被噪声彻底淹没。
更糟的是这批"新鲜"笔记里,221 篇其实是 2015 年的微信收藏。
一个"我知道这批可能不准"的提示,并不能阻止界面继续向你撒谎。

19.2 病根:防伪规则拿 mtime 当尺子

第 4 节那条文件名日期防伪,逻辑是「文件名日期与 mtime 差超过 400 天 → 判定为引用日期,丢弃」。
它默认 mtime 是准的。云盘一刷,这个前提就没了:

06-有道云导入/微信收藏/视频 2015-04-25 1208.md
  v2.7: 文件名日期 2015-04-25 距 mtime 2026-10-19 超过 400 天
        -&gt; 判为「引用日期」丢弃 -&gt; 用 mtime -&gt; 2015 年的收藏染成「昨天写的」
  v2.8: 该目录 mtime 已被证伪 -&gt; 弯尺停用 -&gt; 直接采信 2015-04-25

被这条规则误伤的正是最有证据的那批笔记——它们明明在文件名里写着日期。
反倒是没有任何日期证据的笔记,v2.7 敢直接采信 mtime。证据链整个是反的。

19.3 判据:按目录,不按全库

某目录下「没有 frontmatter 日期」的笔记,mtime 高度集中在同一天
  且篇数 ≥ MTIME_POLLUTION_MIN(15)
  且占该目录笔记总数 ≥ MTIME_POLLUTION_RATIO(60%)
→ 该目录 mtime 不可信

为什么按目录判:云盘重拉通常一次一个目录。实测同一次同步里
06-有道云导入 236/236=100%、05-古文经典 30/31=97%,
而 tools 4 篇、根目录 5 篇只是零星几篇。按全库判会把后面这几个正常目录一起误伤。

为什么排除 frontmatter 笔记:有 frontmatter 日期的笔记根本不看 mtime,压根不受污染影响,
把它们算进簇里只会稀释比例、导致真污染被漏判。

19.4 免疫后怎么办:换证据,不是换猜测

判定不可信后,脚本不会去猜一个日期,而是回到证据链:

证据 处置
frontmatter 日期 等字段 照常采信(本来就最高优先级)
文件名里的日期 直接采信,不再用 mtime 这把弯尺去量它
什么都没有 判定「日期未知」→ 不参与新鲜度着色,回落到分类色

第三条是本节的价值观:没有证据就不表态。无色(分类色)是一个诚实的"我不知道",
而谎报 D1 是一个错误的"这是昨天写的"。两者在界面上都是有色块,但后者会让人做错决定。

实测 06-有道云导入 236 篇:221 篇纠正回真实日期(全部掉出 7 天窗口)、11 篇免着色、4 篇另有 frontmatter。
05-古文经典 31 篇:30 篇免着色。

19.5 三条退出通道(自动判断必须可被推翻)

自动判断最怕"我想让它染但它不染,还找不到开关"。v2.8 留了三层:

  1. --trust-mtime —— 一次性关总闸,退回 v2.7 的"只提示不改判"
  2. MTIME_IMMUNITY = False —— 永久关总闸(脚本顶部)
  3. MTIME_TRUST_FOLDERS = {&quot;某目录&quot;} —— 精确放行:这个目录的 mtime 永远可信
    (适合你确实每天整批导入的目录)

运行报告会逐目录点名:哪个目录、多少篇、mtime 都在哪天、占比多少、纠正几篇、免着色几篇。
不静默。

19.6 一个刻意的妥协:不动「今天」的簇

同一天冒出一大批 mtime,有两种都讲得通的解释:被同步刷了,或者你今天真的整批导入了。
昨天/前天的簇只有前一种解释;今天的簇两种都有可能——
而如果是真的导入,抑制掉就丢掉了货真价实的新鲜度信号。

所以 MTIME_IMMUNITY_SKIP_TODAY = True:今天的簇不下手。
好在污染躲不掉——明天它变成 D1 的同类簇,那时判据就只剩一种解释了。
代价是污染识别晚一天,换来真实导入不会被误杀。

19.7 这一节的通用教训

  • 提示 ≠ 修复。给系统加一个"我知道这里有问题"的告警很容易,但告警不会改变输出;
    输出错了,告警写得越准确,越显得系统在明知故犯。
  • 别用一把可能弯的尺子去量别人。任何"以 X 为基准判断 Y"的规则,都要先问 X 本身可信吗。
  • 没有证据就别表态。宁可输出一个中性的"未知",也不要输出一个看着正常但错误的结论。
  • 自动判断必须留退出通道,并且要在报告里说清楚怎么退。

20. 目录着色的结构体检 —— 不只查自己,也查别人怎么把目录色写坏的(v2.9 核心)

前面十九节查的都是「本片段的颜色对不对」。v2.9 补上另一半:同一个 snippets 目录里,所有片段的目录着色写得对不对。

为什么要往外接这一层。目录着色有个很不像 bug 的特点:它坏起来基本不报错。
CSS 语法合法、片段正常启用、颜色也确实写进了文件,只是你眼睛看到的是错的。
学同库的 TeleAgent 两层片段时这一点看得很清楚——它坏的地方,全都不是"报错",而是"没报错"。

audit_snippet_hygiene() 扫 snippets 目录下的所有片段(不只已启用的),查四类隐患:

编号 隐患 判据 实测(本库 423 篇)
H1 回落层自相矛盾 同一套配色里「分类色没写 !important」+「日期色写了 !important」 30 篇
H2 文件名级子串误伤 [data-path*=&quot;index.md&quot;] 这类以文件名做子串 2 条规则、各误伤 1 篇
H3 目录漏染 顶层目录不在任何片段的目录清单里 7 个目录 / 264 篇(本片段已兜底)
H4 非法 CSS 转义 属性值含 \0 等数字转义 1 条规则整体失效

H1 值得单独说:它是「自己跟自己打架」,不是跟我打架

TeleAgent 的分层是对的:静态层给目录分类色,动态层给 7 天新鲜度色。
但它的第一层写的是裸 color,没有 !important,而动态层是带 !important 的。

!important 面前特异性不作数。于是:

  • 命中动态层的笔记,分类色永远赢不了——所谓「第 8 天回落到分类色」在这批笔记上从未发生
  • 更要命的是动态层把日期硬编码进选择器([data-path*=&quot;2026-10-12&quot;]),生成器 2026-10-12 停更后日期就冻结了
  • 两者叠加的后果:这批笔记被永久钉死在 2026-10-12 那天的颜色上,一天都不会再变

这里的关键认知是:这跟我没关系。即使把本片段整个删掉,那 30 篇也是错的。
v2.3~v2.6 一直在解决"我和对手谁压过谁",H1 说的是另一件事——对手自己的两层也在打架。
所以体检报告把它标成 [提示] 而不是 [告警]:本片段已用 1-3-0 接管,当前不实际生效;
只有当你停用本片段、或它自己停用了才会显现。报告里这句话是写明的,不含糊。

> 顺带一条通用教训:v2.6 修过"分类色漏写 !important"这个 bug(第 17 节),
> 当时以为是本片段的疏漏。H1 说明这是一整类方案的通病——
> 分层配色里,只要有一层带 !important、另一层不带,分层就名存实亡。

H2/H3/H4:小问题,但都是"没人会去看"的那类

  • H2:[data-path*=&quot;index.md&quot;] 的本意是给根目录索引文件加斜体青色,实测把 tools/_index.md 一起染了;
    [data-path*=&quot;知识看板.md&quot;] 则把 00-知识看板.md 染了——而它想匹配的 知识看板.md 在库里根本不存在。
    子串匹配的成本就在这里:你以为在说"根目录那个 index",实际在说"全库所有名字里含 index.md 的东西"。
  • H3:v2.4 只能看见"本片段清单里没有的目录"。H3 换成全局视角——其他片段也救不了的目录有哪些。
  • H4:[data-path*=&quot;^\04-模板&quot;] 里的 \0 后跟数字是非法 CSS 转义,整条选择器被判无效,
    那条规则从来没生效过,也不会有任何提示。

怎么读这段报告

目录着色体检: 发现 4 处结构隐患
  [提示] 回落层自相矛盾:30 篇笔记同时命中「无 !important 的目录分类色」和
        「带 !important 的日期色」(teleagent-color-coding(分类层) vs
        teleagent-dynamic-colors(日期层) 30 篇)……
  • 全部是 [提示] = 本片段已接管,不用动手,但它告诉你"别人的片段停更后会变成什么样"
  • 出现 [告警] = 本片段没启用(或没接管),真有笔记被染错,按篇数处置
  • --json 里对应 hygiene_report,每条带 type / level / notes / samples

21. 配色可以固化 —— 让「建议补进 FOLDER_COLORS」不再是一句空话(v2.9)

v2.4 到 v2.8 有个一直没解决的死循环:报告每天写「未登记目录已自动派色(建议补进 FOLDER_COLORS 固化)」,
但 FOLDER_COLORS 写在脚本源码里。

  • 改源码 → 下次升级脚本要重新改一遍
  • 不改 → 这批目录(实测 258 篇,占全库 61%)永远只能靠自动派色兜底,且颜色可能随旁人变动而平移

一个无法一键执行的建议,等于没建议。 v2.9 把固化落到脚本同目录的 folder_colors.json:

{
  &quot;_说明&quot;: &quot;未登记目录的固化配色…本表优先于脚本内的 FOLDER_COLORS。升级脚本不会覆盖本文件。&quot;,
  &quot;06-有道云导入&quot;: &quot;#A21CAF&quot;,
  &quot;SkillHub&quot;: &quot;#3F6212&quot;,
  &quot;WX同步&quot;: &quot;#1E3A8A&quot;,
  &quot;images&quot;: &quot;#5D1E8C&quot;
}
  • 脚本启动时自动合并(外部表优先,可手工改色),_ 开头的键当注释忽略
  • --pin-folders 把本次自动派色结果追加进去,只补不覆盖已有条目
  • 升级脚本不会冲掉这个文件——固化结果和脚本版本解耦了

固化顺带暴露了一个此前看不见的问题:自动派出来的颜色,从来没进过配色体检。
它们不在 FOLDER_COLORS 里,audit_palette() 压根看不到;一旦固化进去,体检立刻报了 8 处目录互撞(含 2 处 [告警])。
换句话说,v2.4~v2.8 的自动派色一直在派体检通不过的颜色,只是没人查得到。

修法是两头一起改:

  1. 候选色板从 12 色扩到 26 色——已占用色(13 分类色 + 7 档新鲜度 + 7 档高对比 + 4 状态色)有 31 个,
    12 色根本凑不出"离所有占用色都 ≥20"的候选,旧逻辑会一路放宽到"只要不重复就行"(实测派到 ΔE 6.2 的紫)。
  2. 选池从「阈值阶梯放宽」改成「最远点贪心」——每次挑一个离已占用色和已入选色都最远的颜色,
    且只收确实拉得开的(≥ DE_THRESHOLD),收不够 MIN_AUTO_POOL_FLOOR 个才降标准。
    结果只由已占用色决定,不随这批要派几个目录变化,v2.4 的稳定性不受影响。
  3. 报告里直接报最近邻 ΔE,撞了就点名撞的是谁,方便手改:
未登记配色目录: 4 个 / 涉及 258 篇 —— 已自动派色(建议补进 FOLDER_COLORS 固化)
  06-有道云导入: 236 篇 -&gt; #A21CAF  [最近邻 05-古文经典 #7C3AED ΔE31.8]
  SkillHub: 10 篇 -&gt; #3F6212  [最近邻 05-每日回顾 #16A34A ΔE35.6]
  WX同步: 11 篇 -&gt; #1E3A8A  [最近邻 01-工作业务 #2563EB ΔE34.2]
  images: 1 篇 -&gt; #5B21B6  [最近邻 05-古文经典 #7C3AED ΔE18.7,偏近,建议手改]

实测效果(同一批 4 个目录、258 篇,一字未改):

指标 v2.8 v2.9
未登记目录最近邻 ΔE 最小值 10.4(#0F766E vs Excalidraw) 18.7
固化后配色体检撞车数 9 处(含 2 处 [告警]) 1 处(设计意图,非撞车)
未登记目录两两 ΔE 最小值 12.4 24.9

一个诚实的取舍:色相空间就那么大,"离 31 个已占用色都 ≥20"的候选只找到 4 个。
池子小 → 哈希碰撞 → 顺移 → 同一个目录在不同批次可能拿到不同颜色。
v2.4 曾把"稳定性"看得极重,v2.9 的判断是:稳定性现在由 folder_colors.json 保证,不必再让算法硬扛——
先保证颜色可用(不撞车),再让用户一键固化把颜色钉死。跑一次 --pin-folders 即可。

22. 染的是不是真东西 —— 死选择器体检(v2.10 核心)

前 12 版查的都是「CSS 写对了没有」:语法自校验、片段注册、特异性对抗、陈旧日期、配色撞车。
但目录着色还有一类故障,以上五道检查一道也查不出来:

> 选择器完全合法,CSS 解析通过,片段也好好启用着,只是它要匹配的东西在库里根本不存在。

于是你既看不到报错,也看不到异常,只会看到一个非常容易被忽略的现象——
那个目录的标题没有分类色。

实测来源。 TeleAgent 静态层给 04-模板 的文件夹标题写了两条选择器:

.nav-folder-title[data-path*=&quot;/04-模板&quot;]   .tree-item-inner,   /* 前导斜杠 */
.nav-folder-title[data-path*=&quot;^\04-模板&quot;]  .tree-item-inner,   /* 锚点 + 转义 */
.nav-file-title [data-path*=&quot;04-模板/&quot;]    .tree-item-inner {  /* 这条是好的 */
  color: #7788aa;
}

两条文件夹规则在本库命中数都是 0。结果就是 04-模板 这个目录标题永远没有分类色,
而它下面 8 篇文件却照常有色——目录配色只坏了一半,正是肉眼最难发现的那种坏法。

判据:不靠语法理论推断,直接拿真实路径去撞。
语法理论这次骗了人:v2.9 曾据" \0 是非法转义"推出"整条规则失效",
拿真实 CSS 解析器一跑,prelude errors = 0,\04 是合法转义,规则活得好好的。
所以 v2.10 只认命中数——0 命中即死,并给出四种死因:

死因 例子 说明
前导斜杠 [data-path*=&quot;/04-模板&quot;] Obsidian 的 data-path 是库内相对路径,永不带前导斜杠
正则锚点 [data-path*=&quot;^04-模板&quot;] *= 是子串匹配,^ $ 会被当普通字符
CSS 转义 [data-path*=&quot;^\04-模板&quot;] 实际参与匹配的字符与字面看到的不同
目标不存在 [data-path=&quot;99-不存在的目录&quot;] 目录改名或删除后残留的旧规则

跑起来长这样(本库实测,2 处都是真问题):

死选择器体检: 发现 2 处永不生效的选择器
  [提示] 片段 teleagent-color-coding 的文件夹标题选择器 [data-path*=&quot;/04-模板&quot;]
         在本库命中 0 条 —— 前导斜杠:Obsidian 的 data-path 是库内相对路径,
         永不带前导斜杠;意图目录 04-模板 下 8 篇仍会被本片段接管
  [提示] 片段 teleagent-color-coding 的文件夹标题选择器 [data-path*=&quot;^\04-模板&quot;]
         在本库命中 0 条 —— 含正则锚点 ^ / $:*= 是子串匹配,锚点会被当普通字符;
         意图目录 04-模板 下 8 篇仍会被本片段接管

三个假警报,都是自己造的,也都修了(23 条噪声 → 2 条真问题):

  1. 拿剪过枝的 rows 当语料。 rows 会按 EXCLUDE_FRESHNESS 整棵剪掉 04-模板、90-归档,
    但这两个目录在 Obsidian 文件树里照样显示、照样被着色。
    结果是它们全被报成"死选择器"。死选择器的定义是"在 Obsidian 里匹配不到",
    语料就必须是文件树本身
    ——v2.10 改成直接遍历文件系统,只排除点目录。
  2. 把目录属性误判成文件属性。 .nav-folder[data-path=&quot;01-工作业务&quot;] .nav-file-title
    的属性明明挂在 folder 上,初版看见这行里有 nav-file-title 就当文件选择器,
    拿文件语料去撞,把自己 12 条目录规则全报成死。
    修法:data-path 属于紧挨着它的那个元素,取属性前最后一个复合选择器来定性。
  3. 精确匹配被当成标记词漏掉。 [data-path=&quot;99-不存在&quot;] 因为没有斜杠、
    不像路径,被"标记型选择器"的过滤规则吃掉。修法:data-path=&quot;X&quot;(精确匹配)天生就是路径,
    命中 0 只可能是路径写错,不必再猜它是不是 未读 这类标记词。

标记型选择器要放过。 [data-path*=&quot;未读&quot;] 命中 0,只说明当前没有这类文件,不是写错了。
只有"看起来像个路径"的才值得报,否则天天刷屏,告警就没人看了。

也体检自己。 本片段的选择器是从真实路径生成的 data-path=&quot;...&quot;,理论上是 0 死选择器——
但"理论上"三个字,正是前面十二版反复出事的地方,所以自己也一起撞。

23. 日期只告诉你「同一天」,时分告诉你「同一件事」—— 突发级 mtime 免疫(v2.11 核心)

v2.8 加了 mtime 污染免疫,判据是按目录:同一目录里 ≥15 篇同天、且占该目录 ≥60% → 该目录 mtime 不可信。
在 2026-10-20 那次整目录重拉里它很准(06-有道云导入 236/236、05-古文经典 30/31)。
但云盘同步不是按目录挑的——它一次触碰整棵库,落到哪个目录全看当时同步了什么。

实测漏网(2026-10-22):2026-10-18 被刷的 46 篇散在 6 个目录,最多的一个目录只有 4 篇。
目录级判据「≥15 篇且占 60%」一条都没命中,于是 16 篇老笔记被染成 D4 亮橙,占 D4 总数 22 篇的 73%:

被误染的 内容
(根目录) 4 篇 dashboard.md、dashboard-2.md、未命名.md、概念A.md
tools/ 4 篇 三个「接入说明」+ _index.md
05-读书笔记/ 3 篇 欢迎.md、Agent.md、执行报告
01-工作业务/ 2 篇、02-投资理财/ 2 篇、00-收件箱/ 1 篇 使用说明、课程精要、老墨跟踪

更该反省的是:这一版自己的「日期可信度」体检已经把 16 篇点名报出来了,
可它只提示、不改判——颜色照样是错的。这正是 v2.8 用来批评 v2.7 的那句话:
告警不改变输出,等于明知故犯。 上一版修好的 bug,换个分布就回来了。

换一个不受目录结构影响的指纹:时间。
人一天写几十篇不会挤在同一小时,同步/批量脚本会。实测:

那天 纯 mtime 篇数 时间分布
2026-10-18 46 43 篇落在 21:52~21:55(3 分钟)
2026-10-19 232 222 篇落在 09:34~09:59

日期只告诉你"同一天",时分告诉你"同一件事"。

判据(detect_mtime_bursts()):对「没有 frontmatter 日期」的笔记,
同一天内按时序做滑动窗口,窗口 ≤MTIME_BURST_WINDOW_MIN(60) 分钟、
≥MTIME_BURST_MIN(10) 篇、且跨 ≥MTIME_BURST_MIN_FOLDERS(2) 个目录 → 判为同一次批量触碰,整批免疫。

为什么要求跨 ≥2 个目录 —— 这是刻意收窄的口子:
单目录内的突发有可能是你真的一小时写了十几篇,那种情况交给目录级判据(它还要 60% 占比,误伤更小)。
反过来,单目录、低占比的同步(300 篇的目录被刷了 20 篇)两种判据都抓不到。
宁可漏判,不可把真实写作判成污染——漏判只是少染几篇,误判是把新鲜度信号整片掐掉。

两级判据互补,缺一就漏:
  目录级(v2.8)  同目录 ≥15 篇 且 占比 ≥60%   → 整目录重拉
  突发级(v2.11)  跨 ≥2 目录 ≥10 篇 且 ≤60 分钟 → 跨目录整批触碰
  都抓不到         单目录、低占比的小批量同步     → 刻意留口子,走 --trust-mtime 兜

效果(同一批 433 篇,一字未改):

指标 v2.10 v2.11
7 天内着色 53 37
D4(4 天前) 22 6(16 篇假新鲜撤掉)
日期来源 mtime 27 11
日期未知(免着色) 41 57
「日期可信度」告警 1 条(16 篇) 0 条(已被处置,不再重复报)
CSS 体积 60,355 字节 51,527 字节

回归(合成库,三组各置不同时刻):单目录 12 篇同刻 → 不免疫;跨 2 目录 12 篇同刻 → 免疫;
跨 2 目录 8 篇(不足 10)→ 不免疫。三种都与设计一致。
同一批数据跑 --trust-mtime 得 315 篇(D3 一天 235 篇)——总闸有效,也顺便说明污染的真实规模。

通用教训:一条判据按空间(目录)分组,就一定漏掉跨空间的事件。
不是上一版没修好,是判据的维度不够。补第二个维度时要先想清楚它会误伤谁,
再决定是加阈值收窄,还是留一条退出通道。

24. 谁压过谁 —— 级联顺序体检(v2.12 核心)

前面 23 节查的全是「规则写对了没有」:语法自检、片段注册、特异性对抗、陈旧审计、配色撞车、死选择器。
漏掉一个维度:特异性相同、都带 !important 时,由源码顺序决胜。

本片段内部恰恰全是同特异性:文件级规则一律 .nav-file-title[data-path=&quot;X&quot;]:not(#kb-scope) .tree-item-inner = 1-3-0,
目录级一律 0-6-0,全部带 !important。也就是说层与层之间完全靠顺序分胜负,顺序一错,优先级阶梯就整段翻转,
而 CSS 语法自检、片段注册校验、特异性体检一个都查不出来。

实测故障(2026-10-23,本库 436 篇):无障碍高对比档(旧第 7 节)原本排在「未读/冲突」之后。
它是「新鲜度档的加深版」,本意只是让淡色档在 prefers-contrast: more 下能看清,
可排到最后之后,它连未读也一起盖了:

被覆盖的未读笔记 新鲜度档 高对比色 未读色(本该显示)
01-工作业务/LLM Wiki架构精读-查询协议与prompt落盘-2026-10-18.md D5 #44403C 紫色未读高亮
01-工作业务/WorkBuddy驱动Obsidian自进化-三步法融合-2026-10-18.md D5 #44403C 紫色未读高亮
01-工作业务/知识库判断层升级-我的判断强制-2026-10-20.md D3 #7C2D12 紫色未读高亮

为什么难发现:浅色、正常对比度下一切正常——只有开了系统「提高对比度」偏好的人,
才会永远看不到未读标记。他不会去怀疑配色脚本,只会以为自己没标未读。
这和 v2.11 批评过的「告警不改变输出」是同一类病:只在你不看的那条路径上坏。

修法不是改颜色,是改位置:高对比档是新鲜度档的变体,必须紧跟它要修正的那一层,
放在最后反而会越过所有更高优先级的层。已上移为 3.7 节,排在 3.5 文件夹新鲜度之后、4 未读之前。

新增 audit_cascade_order():

对「每个元素 × 每个通道」:
  收集命中它的所有规则
  按 (是否 !important, 特异性三元组, 源码顺序) 取胜出者
  胜出者的层级 必须 == 这批规则里的最高层级,否则点名

两个实现要点,都是实测踩出来的:

  1. 通道是叠加关系,不是互斥。 @media (prefers-contrast: more) 里的规则不会关掉常规规则,
    所以「高对比模式」这个上下文里参与竞争的既有常规层也有高对比层。
    初版按通道各自独立取候选集 → 高对比通道里只剩新鲜度一条规则 → 永远查不出冲突(体检自己静默失效)。
  2. 计数按元素,不按选择器。 同一条规则会被 _file_sel 拆成 4 个选择器变体(兼容不同 Obsidian 版本 DOM),
    按选择器累加会让「3 篇未读」虚报成「12 处命中」。

回归:真实顺序 0 条;把层级整体倒排(合成故障)→ 报出 18 类冲突。
--check --strict 会把这类冲突计进退出码,定时任务可直接接告警。

通用教训:分层配色里,「高优先级」不是一个属性,是一个位置。
只要同层同特异性,谁写在后面谁就是高优先级——写文档时说「未读优先级最高」没用,
得让未读真的排在后面,而且要有体检盯住这个顺序,否则下一次有人往末尾追加一段就又翻了。
(v2.6 修过同类的「分层里一层带 !important 另一层不带 → 分层名存实亡」,
v2.12 是它的另一半:分层顺序错了,分层同样名存实亡。)

25. 该染的目录染了没有 —— 目录覆盖率体检(v2.13 核心)

前面 24 节查的全是「规则写对了没有」。
这一节查的是一件更基础的事:该染的目录,染了没有——也就是分母。

实测(2026-10-24,本库 461 篇):

口径 v2.12 及以前(只染顶层) v2.13(嵌套继承)
全库目录数 71(顶层 18 / 嵌套 53) 71
目录覆盖率 15/68 = 22.1% 68/68 = 100%
没有分类色的目录 53 个 0
涉及笔记(含后代) 528 篇 0
文件夹新鲜度 9 个(只有顶层) 12 个(逐层)
CSS 体积 74,770 字符 86,677 字符(+16%)

表现形态(和 v2.10 查出的死选择器同一个形状,只是这次是批量的):
父目录标题有色 → 它下面的文件也有色 → 中间那层文件夹标题是灰的。
05-古文经典(紫)下面挂着 古文名篇、孙子兵法、孟子、庄子、论语、道德经 六个灰色子目录,
而这六个目录里的笔记全是紫色。只看父层和文件层都正常,只有展开到中间层才露出破绽。

病根有两层,第二层比第一层更值得记:

  1. v3 为了治子串误伤,把 [data-path*=&quot;X&quot;] 换成精确匹配 [data-path=&quot;X&quot;]——
    这个决定本身是对的(子串会误伤同名文件,v2.9 实测 2 处)。
    但换成精确匹配后,目录清单仍然只枚举顶层(folders = {r.split(&quot;/&quot;)[0] ...}),
    精确匹配 + 只枚举顶层 = 嵌套目录一条规则都没有。
  2. v2.4 起的「未登记目录」自检,分母也只有顶层目录。
    于是覆盖率天天算出 100%,而实际上 53 个目录没染。
    自检全绿不等于全覆盖,前提是分母要包含全部对象。
    这是本技能 13 个版本里藏得最深的一个 bug——不是因为难查,而是因为从来没人问过分母对不对。

修法取 TeleAgent 静态层的优点,但不沿用它的手段:
它的 [data-path*=&quot;00-收件箱&quot;] 是子串匹配,嵌套目录(05-古文经典/道德经)天然含父名,
所以覆盖率天然是 100%——这是它比我们强的地方。
但子串会误伤([data-path*=&quot;index.md&quot;] 染到 tools/_index.md)。
v2.13 的做法是 显式枚举所有层级的目录路径,每个目录单独发一条精确选择器:

/* 继承自 05-古文经典(6 个子目录) */
.nav-folder-title[data-path=&quot;05-古文经典/古文名篇&quot;]:not(#kb-scope),
.tree-item.nav-folder[data-path=&quot;05-古文经典/古文名篇&quot;] &gt; .tree-item-inner,
.nav-folder-title[data-path=&quot;05-古文经典/古文名篇&quot;], ... { color: #7C3AED !important; }

颜色为什么是「继承最近的有色祖先」而不是「给每个子目录另派一色」:

  1. 语义对:05-古文经典/道德经 就是古文经典下的一层,不是一个新分类。
  2. 不新增颜色:53 个嵌套目录各派一色,26 色板必然撞车(鸽笼原理,v2.9 已论证)。
  3. 视觉成块:父子同色在文件树里形成连续色带,比几十种杂色更好读。

新增 audit_folder_coverage(),把目录分成三类分开算——这一步不能省,
否则又会像 v2.5 那样被自己的假警报淹没:

类别 判据 本库
刻意弱化 路径含 EXCLUDE_FRESHNESS 段(04-模板/90-归档/tools 及其子树),设计上就是灰的 3 个
已覆盖 顶层登记色 + 嵌套继承色 68 个
漏染 剩下的,才是真问题 0 个

顺带修掉两个自己造的坑:

  1. 点目录被染色:05-读书笔记/.weread-cache 这类点目录 Obsidian 文件树根本不显示,
    初版却给它发了规则。这个 bug 当场被 v2.10 的死选择器体检抓住
    ([告警] 片段 知识库目录着色 的文件夹标题选择器 ... 在本库命中 0 条)——
    前面 24 节做的体检在这里第一次逮到了自己,算是交叉验证。
  2. 空目录漏染:顶层目录 日记 暂时一篇 md 都没有,folders 由笔记反推所以没它,
    而它的配色早就登记在 FOLDER_COLORS 里。分母要跟「看得见的东西」对齐,
    故顶层目录改为「笔记反推 ∪ 磁盘实际存在的顶层目录」。

文件夹新鲜度也一并逐层:以前只有顶层目录算「子树最新笔记」,
现在 05-每日回顾、05-古文经典/古文名篇 各自按自己的子树算,
父显示 D3、子显示 D0 是正确信息——一眼看出新笔记落在哪一层。

回归:同一批 461 篇一字未改,--no-nested 退回 v2.12 行为 → 覆盖率 22.1%、53 个目录漏染;
默认 → 100%。

通用教训:一个「覆盖率」类指标,先问分母是谁。
分母写错时,指标不是不准确,而是精确地误导——它每天都在报 100%,
报得越稳定,越没人会去怀疑它。

26. 颜色不是写出来的,是层叠算出来的 —— 终局颜色体检(v2.14 核心)

前面 25 节查的全是「规则」:语法对不对、注册没有、特异性够不够、顺序排没排反、
覆盖率到没到 100%、选择器死没死、颜色撞不撞。
没有一条回答用户真正在问的问题:这篇笔记最后到底显示成什么颜色?

为什么必须单独查一遍:层叠的结果不等于任何一条规则的意图。
v2.12 那个坑就是现成的例子——无障碍高对比档原本排在未读之后,
两者特异性相同、都带 !important,于是开了系统高对比的人看不到未读标记,而浅色模式下一切正常。
顺序体检能抓到它,是因为我们恰好登记了这两层;
终局模拟不需要预先知道哪两层会打架,它直接算结果再和期望比。

模拟用的就是浏览器的四条规则(不是近似):

  1. 带 !important 的赢过不带的
  2. 同 !important 比特异性三元组(按位比较,不能求和——v2.6 已论证求和会失真)
  3. 还相同则源码顺序靠后的赢
  4. @media 里的规则是在对应场景下额外参与,不是替换常规规则(通道是叠加关系)

参与模拟的只有 appearance.json 里启用的片段,按启用顺序拼接;
没启用的 CSS 根本不会被 Obsidian 加载,算进去等于自己吓自己。

实测(2026-10-25,本库 467 篇 / 68 个目录):体检一次就揪出真 bug。

场景 对象 期望(设计意图) 实际(层叠算出来)
高对比模式 01-工作业务(D0 文件夹) #9A3412(加深档) #C2410C(普通档)
高对比模式 05-读书笔记/每日荐书(D1) #9A3412 #D4621F
高对比模式 tools、tools/商品化、tools/商品化/发货包(D4) #8A5A3B #EFA87E
高对比模式 images(D5) #57534E #C9A79B

共 12 个文件夹标题。病根很具体:v2.12 把高对比档从末尾上移到未读之前时,只搬了文件那一段,
3.7 节里全是 _file_sel(),一个 _folder_title_sel() 都没有。
后果是「D2~D6 淡色档靠 prefers-contrast 兜底」这句话对文件夹标题压根不成立——
同一个目录,文件标题加深了、文件夹标题还是淡灰,对比度约 2:1,正是高对比模式要消灭的东西。
修法也只是补发一段(文件夹新鲜度 3.5 与本段同为 1-2-0 且都带 !important,靠 3.7 在后取胜)。

--explain &lt;路径&gt;:把「为什么是这个颜色」摊开

目录着色最常被问的一句话是「这篇怎么是这个色」。以前只能靠人肉推理,现在直接打印:

[explain] 05-读书笔记/每日荐书/今日荐书-2026-10-24.md(file)
  场景 浅色常规:期望 #0891B2(分类色(05-读书笔记)) / 实际 #0891B2
    0-6-0     胜出  imp=True   知识库目录着色  color=#0891B2  [.tree-item.nav-folder[data-path=&quot;05-读书笔记&quot;] ...]
    0-3-0           imp=True   知识库目录着色  color=#0891B2  [.nav-folder[data-path=&quot;05-读书笔记&quot;] .nav-file-title]
    0-3-0           imp=False  teleagent-color-coding  color=#e056a0  [.nav-file-title[data-path*=&quot;05-读书笔记/&quot;] ...]

一眼看出:竞品那条也命中了,但它没带 !important,就算特异性一样也直接判负。
只有真正那条决胜规则标「胜出」——同色的其它规则只是陪跑,全标会让人以为有 3 条同时生效。

三个自造的坑(都是实测撞出来的,比算法本身更值得记):

# 坑 后果 修正
1 祖先集合写成 range(1, len(parts)) 01-工作业务/a.md 的祖先算出空集,它唯一的父目录反而丢了 → 分类色后代规则一条都命中不了 → 224 篇被误报「实际无色」 range(1, len(parts) + 1)
2 文件自身没进祖先集 .nav-file-title[data-path=&quot;X&quot;] .tree-item-inner 这类写法里,带 data-path 的是 .tree-item-inner 的父元素,只查祖先目录就永远匹配不上 → 文件级规则全灭,分类色后代规则(0-3-0)反而压过新鲜度/未读(1-3-0)→ 134 处误报 祖先集 ∪ {自身路径}
3 体检读磁盘上的 CSS --check 与「内容无变化跳过写入」这两种情况下读到的都是上一版产物,刚修好的问题继续报,等于体检在验旧货 audit_final_color(rows, stats, css) 传入本次生成的 CSS

回归四组(同一批 467 篇,验证体检不是空转通过):

构造 期望 实测
原样 0 处 0 处
去掉整个高对比档 大量文件 + 文件夹 70 处(D0~D6 文件 58 + 文件夹 12)
去掉未读层 未读篇数 12 处(6 篇未读 × 2 场景)
只去掉高对比的文件夹段 12 处 1 处(删段脚本只切到 1 条,方向正确)

通用教训:一条体检只要查的是「规则」,就永远查不出「规则之间的关系」造成的故障。
规则和结果之间隔着一整套层叠算法,而那套算法有四个决胜顺位,任何一个都可能让写对的东西不生效。
要么模拟结果,要么承认有一部分故障你永远看不见。

27. 颜色对了 ≠ 看得见 —— 渲染层弱化体检(v2.15 核心)

第 26 节好不容易做到了「逐篇算出实际渲染色」,但它比对的仍然是 CSS 里的 color 值。
v2.15 要问的是下一层:这个颜色到了眼睛里还剩多少。

CSS 里能把一个颜色变淡的从来不止 color 本身 —— opacity 是乘在整个元素上的,谁写的都算。
你只接管 color、不接管 opacity,别人一句裸声明就能把你的分类色冲淡一半,
而所有体检都在比对 color 值,算出来「实际 == 意图」,全绿。

实测(2026-10-26):TeleAgent 静态层给三个目录写了弱化透明度,全部不带 !important:

目录 它的 opacity 我们的分类色 冲淡后实际显示 白底对比度
00-收件箱 0.8 #CA8A04 #D5A136 2.34:1
90-归档 0.6 #A1A1AA(旧) #BBBBC2 1.91:1
04-模板 0.7 #9CA3AF(旧) #AEB4BD 2.09:1

对照 WCAG:正文 4.5:1、大字 3:1。1.91:1 等于没有颜色 —— 用户看到的是一片灰,
而「终局颜色体检」还在报告「每篇的实际显示色都等于设计意图」。

判据就两步:把 alpha 折算进颜色,再算对比度。

blend(fg, bg, alpha)   # opacity 乘在整个元素上 = 前景按 alpha 与背景混成实色
contrast_ratio(blend(color, BG, alpha), BG)

不折算,配色体检算出的对比度就是自欺欺人。

处置:把 opacity 通道收归自己(新增 CSS 第 9 节「弱化通道接管」)。
思路来自 TeleAgent —— 色相说「这是什么」,透明度说「重不重要」,两个通道解耦,
比把所有语义挤在一个 color 通道里强。但手段不学它:值写进 WEAKEN_OPACITY、进体检、
可 --no-weaken 一键退回。没设计过弱化值的目录一律接管为 opacity: 1,撤销别人的误伤。

两个自造的坑(都是新体检当场抓出来的):

# 坑 实测 修法
1 弱化值拍脑袋 初版写 0.82 / 0.72,被同一版新增的体检判死:#9CA3AF@0.82 → 2.09:1、#A1A1AA@0.72 → 1.91:1 弱化是相对弱化,不是消失。改更深的基础色(#6B7280 / #71717A)+ 更保守的 opacity(0.88 / 0.82),体检归零
2 开关只管生成、不管体检 --no-weaken 关掉接管后,体检仍假设「我们声明过 opacity=1」,外部冲淡被算成已接管 → 关掉开关照样全绿 took_over 必须同时受 WEAKEN_ENABLED 约束;开关要管「生成」和「体检」两头,否则体检验的是一个不存在的产物

顺带修的一处:收件箱脉冲动画原本用 opacity: 0.75 → 1 做呼吸。
v2.15 起 opacity 归配色系统管,动画的 !important 和接管的 !important 必然打架,必有一伤。
改用 text-shadow 呼吸:视觉不变,但完全不碰 color / opacity 两个已被占用的通道。
动画不该占用你正在管理的视觉通道。

回归两组(同一批 492 篇):

构造 期望 实测
默认(已接管) 通过 通过
--no-weaken 报出被冲淡的目录 2 处:00-收件箱 2.34:1、90-归档 2.31:1,并注明「本片段未接管」

通用教训:查 CSS 值的体检,永远查不出「作用在值之上的东西」。
color 之上还有 opacity、filter、mix-blend-mode、主题的透明背景,
每多一层,你写进去的颜色就离用户看到的更远一点。

28. 一个开关干了两件事 —— 语义越权(v2.15 第二个核心)

这是比第 27 节更老的问题,藏了 13 个版本。

有个常量叫 EXCLUDE_FRESHNESS,字面意思是「不参与新鲜度着色」。
但 collect() 里用它是这么写的:

if any(s in rel.split(&quot;/&quot;) for s in EXCLUDE_FRESHNESS):
    dn[:] = []          # 整棵子树从扫描里消失
    continue

一个管新鲜度的开关,被当成「整篇笔记不参与着色」用了 ——
04-模板 / Templates / 90-归档 三个目录的笔记连分类色、未读色、冲突色都不发。

实测后果(2026-10-26,本库):

项 修复前 修复后 说明
扫描笔记 479 篇 492 篇 13 篇此前根本没进扫描
云盘冲突文件 3 篇 7 篇 漏报 57%
目录覆盖率分母 68 71 三个可见目录此前被算进「刻意弱化不参与」

其中最要命的是冲突文件:那 4 篇漏掉的正是要用户去删的云盘同步垃圾,
染不上色就永远发现不了、永远删不掉。而报告天天写着「冲突文件 3 篇」,没人会去核对。

为什么 13 个版本没人发现 —— 三个条件同时成立,缺一不可:

  1. 颜色其实早就登记了:这三目录在 FOLDER_COLORS 里有色(#9CA3AF / #9CA3AF / #A1A1AA),
    只因循环里一句 if fd in EXCLUDE_FRESHNESS: continue 整条规则不发;
  2. 所有体检都按同一个开关剪枝:覆盖率、配色、级联、终局颜色,
    全都先 if k not in EXCLUDE_FRESHNESS —— 分母里没有它们,结果恒绿;
  3. 形态是「整体没有」而不是「一半没有」:不像死选择器那种「文件有色、目录灰」的错半拍,
    一整片区域干干净净,看着像「本来就没配色」。

修法:拆成两个开关,各管一件事。

开关 含义 行为
EXCLUDE_ALL_COLOR Obsidian 文件树根本不显示的地方(.obsidian / .trash / .git / .workbuddy) 整棵排除,不扫描
EXCLUDE_FRESHNESS 日期对它没意义(模板、归档) 照常扫描,照常发分类色 / 未读 / 冲突色,只是跳过新鲜度

通用教训:一个开关的语义边界,就是它的名字承诺的范围。
一旦被调用方拿去当另一个意思用,而体检又复用了同一个开关做剪枝,
那么这个越权就永远不会被发现 —— 因为检查者和被检查者共用同一副有色眼镜。
体检的分母不能来自被体检对象的同一个开关(第 25 节「分母错是精确误导」的又一个实例,
这次错的不是目录层级,是语义层级)。

何时使用

  • 用户想让 Obsidian 文件树 / 侧边栏按新旧着色、显示"哪篇是新写的"
  • 用户要求刷新 / 更新知识库目录颜色、更新 7 天新鲜度
  • 需要标记未读笔记、标记云盘同步冲突文件
  • 同库已有多套配色片段、颜色互相打架时
  • 定时任务里每日刷新一次颜色

操作流程

1. 定位库根

缺省回退:脚本放在 &lt;库根&gt;/tools/ 下时可省略 --vault,自动取脚本上级目录的上一级。
显式指定(推荐):

python scripts/refresh_note_colors.py --vault &quot;C:/path/to/vault&quot;

2. 先干跑看统计

python scripts/refresh_note_colors.py --vault &quot;&lt;vault&gt;&quot; --check

推荐用 --check(只读体检)而非 --dry-run:除了统计,还会顺带报告片段注册状态、竞品特异性、陈旧片段告警,且完全不写盘。

确认扫描篇数、7 天内着色篇数、未读 / 冲突篇数、日期来源分布、选择器特异性是否合理。

3. 正式生成

python scripts/refresh_note_colors.py --vault &quot;&lt;vault&gt;&quot;

产出 &lt;库根&gt;/.obsidian/snippets/知识库目录着色.css,并自动在 appearance.json 的 enabledCssSnippets 中启用且置末位。
只生成 CSS 不动配置:加 --no-register。
给流水线消费的机器可读统计:加 --json(含结构化 stale_report)。
定时任务里想让「别人的片段停更」冒出来,用 --check --strict(陈旧时退出码 2)。

4. 输出一行结果

共扫描 N 篇,7 天内着色 M 篇,未读 K 篇。

失败时如实报告原因,不要静默跳过。

硬边界(不可违反)

  • 禁止手动编辑生成的 .obsidian/snippets/知识库目录着色.css —— 下次刷新整体覆盖,手改必然丢失。要改规则就改脚本顶部的可调参数。
  • 禁止为此安装任何 Obsidian 插件 —— 社区插件做不到按日期自动渐变,装了只会引入冲突。
  • 不修改任何笔记内容 —— 只读 frontmatter 与文件名,绝不回写笔记。
  • 不删除其他工具的片段 —— 同库若有其他工具的颜色片段,保留启用,只调整顺序让本片段置末位;冲突交给特异性解决。
  • 写入范围(供安全审核参考) —— 脚本只在目标 vault 的 .obsidian/ 目录内写入,具体四个文件:
  • .obsidian/snippets/知识库目录着色.css(生成的 CSS 片段)
  • .obsidian/appearance.json(注册片段到 enabledCssSnippets 并置末位)
  • .obsidian/graph.json(关系图谱 colorGroups,v2.7 起)
  • folder_colors.json(目录配色表外部化,仅 --pin-folders 时写入 vault 根)

读取范围:笔记的 frontmatter / 文件名 / mtime、同目录其他 CSS 片段、上述 .obsidian 配置文件。不联网、不上传任何数据、不执行外部命令、不删除任何文件、不修改笔记内容。所有写操作幂等,内容无变化时跳过写入。

可调参数(脚本顶部)

参数 作用 默认
FADE_DAYS 新鲜度周期(天) 7
FRESH_COLORS D0–D6 色阶 橙红 → 灰
FOLDER_COLORS 目录 → 分类色映射 见脚本
FOLDER_FRESHNESS 文件夹是否按子树最新笔记着色 True
FOLDER_FRESHNESS_TOP_LEVEL_ONLY 只染顶层目录 / 逐层子目录都染 True
FILENAME_DATE_MAX_SKEW 文件名日期与 mtime 的最大允许偏差(天) 400
BOOST_ID 用 :not(#id) 加固特异性(False 退回堆类名) True
BOOST_ID_NAME :not() 里那个确保不存在的 id 名 kb-scope
BOOST_CLASS_REPEAT 仅 BOOST_ID=False 时生效:类名重复次数 3
BOOST_ATTR_REPEAT 仅 BOOST_ID=False 时生效:属性重复次数 3
IDEMPOTENT_WRITE 内容无变化时跳过写盘 True
INBOX_PULSE 收件箱脉冲动画 True
TREAT_INBOX_AS_UNREAD 收件箱一律视为未读 True
EXCLUDE_ALL_COLOR 整棵排除、不扫描的目录(Obsidian 根本不显示) .obsidian / .trash / .git / .workbuddy / .codebuddy
EXCLUDE_FRESHNESS 只跳过新鲜度、分类色/未读/冲突照常的目录(v2.15 起与上面彻底分开) 模板 / Templates / 归档
WEAKEN_OPACITY 弱化区自己声明的 opacity(v2.15)——值进体检,不拍脑袋 90-归档 0.82 / 04-模板 0.88 / Templates 0.88
WEAKEN_ENABLED 弱化通道总闸:接管自己的 + 撤销别人的(v2.15) True
ATTENUATION_AUDIT 渲染弱化体检:把 opacity 折算进颜色再算对比度 True
ATTENUATION_MIN_CONTRAST 折算后对比度低于此值即报告「实际看不清」 2.6
UNREAD_FALSE 判定为"未读"的 frontmatter 取值 否/false/no/未读/0/n
GLOBAL_FILES 根目录索引 / 日志类文件名 index / log / README / AGENTS 等
AUTO_FOLDER_COLOR 未登记目录自动派色(v2.4) True(关掉即恢复静默漏染)
AUTO_PALETTE 自动派色的候选调色板 26 色(v2.9 从 12 色扩到 26 色)
MIN_AUTO_POOL 自动派色候选池容量上限(固定值,不随批次变化) 8
MIN_AUTO_POOL_FLOOR 池子下限:候选不够好也至少收这么多,保证有得派 4
FOLDER_COLORS_FILE 外部目录配色表(优先于 FOLDER_COLORS,升级不覆盖) tools/folder_colors.json
DE_THRESHOLD ΔE 低于此值判为「视觉难区分」 20.0
DE_WARN ΔE 低于此值判为「严重撞车」 12.0
INTENTIONAL_SAME_GROUP 刻意同色组(弱化区),不参与互撞判定 模板 / 归档 / tools
MTIME_CLUSTER_MIN 同一天纯 mtime 来源笔记达到此数才提示疑似污染(v2.6) 15
MTIME_IMMUNITY mtime 污染免疫总闸(v2.8);关掉退回"只提示不改判" True
MTIME_POLLUTION_MIN 同目录同一天纯 mtime 笔记达此数才算污染簇 15
MTIME_POLLUTION_RATIO 且占该目录笔记总数不低于此比例 0.6
MTIME_TRUST_FOLDERS mtime 永远可信的目录白名单 空
MTIME_IMMUNITY_SKIP_TODAY 不对「日期就是今天」的簇下手(留给真实导入) True
MTIME_BURST_MIN 一个时间窗内达此篇数才算「突发」(v2.11) 10
MTIME_BURST_WINDOW_MIN 判定为「同一次」的时间窗(分钟) 60
MTIME_BURST_MIN_FOLDERS 突发至少跨几个目录才认(1 = 单目录也免疫,误伤更大) 2
GRAPH_COLOR_GROUPS 是否把目录分类色同步到关系图谱 graph.json(v2.7) True(只染分类色,保留用户组)
NESTED_FOLDER_COLOR 嵌套目录是否也着分类色(v2.13,关掉退回只染顶层) True
NESTED_FOLDER_NEED_NOTES 是否只给「子树内有笔记」的嵌套目录发规则 False(连附件目录一起染,覆盖率才是真的)
NESTED_FOLDER_FRESHNESS 嵌套目录是否逐层算文件夹新鲜度(父子可不同档) True
FINAL_COLOR_AUDIT 终局颜色体检总闸(v2.14) True(关掉不影响生成,只少一道体检)
--explain &lt;路径&gt; 摊开某篇笔记的全部候选规则与决胜者(v2.14) —
--as-folder 配合 --explain,按「文件夹标题」而不是文件来算 —

> BOOST_ID=True 时特异性已在 id 位,正常情况不需要再调任何参数——对手堆多少类名都追不上。
> 只有在极端场景(对手也用了 id 选择器)才需要额外处理;BOOST_ID=False 是回退开关,退回 v2.2 的堆类名写法。

未读与冲突的判定口径

  • 未读:frontmatter 已读: 否(或 read: false 等)→ 文件名含「未读」→ 位于 00-收件箱/(可用 INBOX_UNREAD_IGNORE 排除说明页)。三者依次兜底。
  • 冲突:文件名含「冲突文件」(天翼云盘 / 坚果云同步冲突产物),删除线 + 整行弱化,提示清理,优先级最高。

排错

详见 references/着色规则与冲突排查.md。最常见几类问题:
- 终局颜色体检报「实际显示色与设计意图不符」:先看 --explain &lt;路径&gt; 打出来的决胜规则属于哪个片段——是自己某一层漏发(如 v2.14 的高对比文件夹段),还是被别人的片段压住(看 imp 与特异性三元组)。

  1. 颜色部分不对(最隐蔽) —— 多半是特异性被别的 !important 片段压过。先看输出的 选择器特异性 是不是 1-x-0;不是则说明 BOOST_ID 被关了。是 1-x-0 还被压,说明对手也用了 id 选择器,此时再考虑调 BOOST_CLASS_REPEAT / BOOST_ATTR_REPEAT。
  2. 颜色完全没生效 —— appearance.json 的 enabledCssSnippets 里没有本片段。用 --no-register 跑完再手动启用。
  3. 新鲜度失真 —— 大量笔记靠 mtime 判日期(云盘同步污染)。看统计里 mtime 占比是否过高,是的话逐步补 frontmatter 的 日期 字段。
  4. AI 工作文件被误着色 —— 确认 .workbuddy / .codebuddy 在 EXCLUDE_FRESHNESS 里。
  5. 校验失败中止写入 —— 多半是改脚本时注释写坏了,看报错行号。
  6. 今天的笔记没颜色,但别的日期有 —— 典型的「绝对日期型片段停更」。看输出里 为「绝对日期型」 那一行,先看级别再决定动不动手:
    - [提示] = 被本片段压着,当前 0 篇受影响,不用动
    - [告警] = 真有笔记被染错,按篇数处置:合计超过 10 篇就停用该片段,个位数可以等生成方恢复
    详见第 11、12、13 节。
  7. 某个目录整片没颜色 / 一批笔记第 8 天后变成默认灰 —— 该目录没登记配色。看输出里的 未登记配色目录 一行(v2.4 起会自动派色并列出目录与篇数)。v2.9 起固化不用改源码:跑一次 --pin-folders,结果写进 tools/folder_colors.json;想换色直接编辑那个 JSON。详见第 15、21 节。
  8. 某一档(比如 D1)篇数突然暴涨 —— 先看有没有 mtime 免疫 或 mtime 突发免疫 那两段。v2.8 起整目录重拉已被自动处理,v2.11 起跨目录批量触碰也一并处理:被点名的批次不再用 mtime 定日期,暴涨会自己消失。若仍有残留暴涨,看 日期可信度 一行——那是单目录、低占比的小批量同步(两种判据刻意留的口子),根治是给这些笔记补 frontmatter 日期;想调灵敏度改 MTIME_CLUSTER_MIN / MTIME_POLLUTION_RATIO / MTIME_BURST_MIN / MTIME_BURST_MIN_FOLDERS。详见第 19、23 节。
    8b. 某个整目录的文件全都没了新鲜度色 —— 先看 mtime 免疫 是不是点名了它。若这批笔记确实是你今天刚导入的,用 --trust-mtime 跑一次,或把该目录加进 MTIME_TRUST_FOLDERS。详见第 19.5 节。
  9. 陈旧告警的篇数/级别自己变了,但笔记没动 —— 检查是不是 covered 口径问题(v2.6 已修:分类色覆盖的文件现已计入)。若仍出现,用 --json 看 stale_report 的 miscolored_effective / miscolored_suppressed 两个字段是否自洽。详见第 17.2 节。
  10. --json 没有任何输出 / 流水线拿到空结果 —— v2.9 前 json.dumps 不认 datetime.date,untrusted_folders 里的日期字段一序列化就抛 TypeError,整段结构化输出全丢;而人肉跑(不带 --json)看起来完全正常,属于"只有流水线知道、人不知道"的静默故障。v2.9 已用 default=str 兜底。教训:给机器看的出口,必须单独跑一次验证,别默认它跟人看的输出同源。
  11. 报告里出现「回落层自相矛盾」 —— 说明同库有另一套片段的分类色漏写了 !important,被它自己带 !important 的日期层压住,这批笔记的"第 8 天回落"不会发生。标 [提示] 时本片段已接管、不用动;标 [告警] 时按篇数处置(>10 篇就停用那个片段)。详见第 20 节。
  12. 关系图谱节点没按目录染色 —— 图谱是 canvas 渲染,CSS 管不到,靠 .obsidian/graph.json 的 colorGroups。先跑 --check 看"图谱着色"那行:报"已就位"说明 graph.json 正确(去 Obsidian 图谱设置里确认 colorGroups 面板没被手动重置);报"颜色过期/缺失"说明 graph.json 被改过,正式跑一次即可修正。完全不想染色相图谱设 GRAPH_COLOR_GROUPS=False。详见第 18 节。
  13. 某个目录的标题没有分类色,但它下面的文件有色 —— 典型的死选择器:规则语法合法、片段也启用,就是选择器匹配不到任何东西。看输出里 死选择器体检 那一段,它会点名片段、选择器、死因和意图目录下有多少篇。若是别人的片段,正确做法是修那个片段(或让它别启用),而不是在本片段里加颜色盖过去——盖过去等于把"这条规则已经坏了"这件事也一起盖住。详见第 22 节。
  14. 死选择器体检突然报出一大片 —— 先确认语料没错:v2.10 起语料直接遍历文件系统、不再用剪过枝的 rows。如果仍成片报错,多半是有人改了脚本里的属性归属判断(data-path 属于紧挨着它的元素),或把 EXCLUDE_FRESHNESS 里的目录名写进了选择器。用 --json 看 dead_selectors 里的 reason 字段分类核对。
  15. 一批散在多个目录的老笔记被染成鲜艳色 —— 典型的跨目录同步污染:每个目录都凑不够「≥15 篇且占 60%」,目录级判据一条都抓不到(实测一次漏掉散在 6 个目录的 16 篇)。v2.11 起看 mtime 突发免疫 那段,它会点名日期、起止时刻、篇数与涉及的目录。若这批确实是你刚整批导入的,用 --trust-mtime 跑一次。详见第 23 节。
  16. 某个目录/批次的文件全都没了新鲜度色,但它们是新的 —— 先看 mtime 免疫 或 mtime 突发免疫 有没有点名。被点名的批次改从 frontmatter/文件名取日期,两者都没有就判定「日期未知」而撤回着色(宁可无色,不可谎报)。根治是补 frontmatter 日期;临时放行用 --trust-mtime 或 MTIME_TRUST_FOLDERS。详见第 19、23 节。
  17. 未读/冲突标记在别人电脑上好好的,在某些人那儿不显示 —— 先看 级联顺序体检 那一行。若无告警,问清楚对方是不是开了系统的「提高对比度」或暗色偏好:v2.12 前高对比档排在未读之后,同特异性同 !important 时由顺序决胜,未读会被新鲜度色整段盖掉(实测 3 篇)。v2.12 已把高对比档上移为 3.7 节。若体检本身报「通过」却仍不显示,确认 CSS 里 3.7 无障碍 排在 4. 未读 之前。详见第 24 节。
  18. 父目录和文件都有色,中间那层子目录标题是灰的 —— 典型的嵌套目录漏染:目录清单只枚举顶层,二级三级目录一条规则都没有。看输出里 目录覆盖率 那一行:v2.13 起默认逐层补染(实测 22.1% → 100%)。若仍不是 100%,看 [告警] 点名的是哪些目录——多半是新目录落进了 EXCLUDE_FRESHNESS(刻意弱化)或点目录(Obsidian 不显示)。想退回旧行为用 --no-nested。详见第 25 节。
  19. 报告说「冲突文件 3 篇」,磁盘上一搜却有 7 篇 —— v2.15 前的语义越权:EXCLUDE_FRESHNESS(名为"不参与新鲜度")被当成"整篇不参与着色"用,模板/归档目录整棵不进扫描,那 4 篇冲突文件连删除线都没有。v2.15 已拆成 EXCLUDE_ALL_COLOR(真正不扫描)与 EXCLUDE_FRESHNESS(只跳过新鲜度)。自检方法:报告里的冲突文件数应等于 文件名含&quot;冲突文件&quot; 的实际文件数。详见第 28 节。
  20. 颜色明明写对了,看着却灰蒙蒙一片 —— 先看 渲染弱化体检 那一行。opacity 是乘在整个元素上的,同库另一个片段一句裸 opacity: 0.6 就能把你的分类色冲淡 40%(实测 90-归档 掉到 1.91:1,等于没颜色)。v2.15 起本片段会用第 9 节接管 opacity;若你用了 --no-weaken,体检会点名"被谁冲淡、本片段未接管"。注意弱化不等于消失:自己调 WEAKEN_OPACITY 时,体检会当场验对比度。详见第 27 节。
  21. 收件箱脉冲动画不动了 / 弱化值不生效 —— 两者抢同一个 opacity 通道且都带 !important,必有一伤。v2.15 已把脉冲改为 text-shadow 呼吸,彻底让出 color / opacity 两个通道。若你自己加了别的动画,别再动这两个属性。详见第 27 节。

与同类方案的差异(对照 TeleAgent 两层片段)

同库常见的 TeleAgent 配色分两层:静态层 teleagent-color-coding.css(目录分类色 + 未读 + 冲突 + 收件箱脉冲)、动态层 teleagent-dynamic-colors.css(7 天渐变)。本方案取其长、补其短:

维度 TeleAgent 本方案
日期来源 只认文件名里的日期字符串 frontmatter → 文件名 → mtime 三级回退
匹配方式 [data-path*=&quot;2026-10-12&quot;] 子串,会误伤含该日期的任意路径 精确到单文件的 data-path=&quot;...&quot;,不误伤
文件夹着色 只染文件,折叠后看不见热度 文件夹继承子树最新笔记日期,折叠也可见
日期防伪 无,文件名里的历史日期会被当今天 与 mtime 偏差超阈值即判为引用日期,自动降级
未来日期 无处理,被当陈旧丢弃 单独统计为「未来/计划」
选择器特异性 冲突层 0-5-0、动态层 0-4-0(纯类) 1-3-0(含 id 位),压过任意类数量,不依赖加载顺序,且体积还小 23%
CSS 自检 无(其动态层第 2 行起注释已损坏,规则被吞) 状态机校验,不通过拒绝写入
日期表达 绝对日期:日期硬编码进选择器,停更即静默失效 相对新鲜度 D0~D6,无绝对日期,漏跑只滞后一天
陈旧自检 无(其动态层停更 2 天后仍在"假装工作") audit_stale_snippets() 按"规则是否含今天"判定并告警
告警粒度 — 直接给出错染 / 漏染的具体篇数(实测 16 篇命中 / 0 篇漏染),可量化决策
告警真伪 — 区分实际影响与潜在风险:被本片段压住的只报 [提示],真染错才报 [告警]
目录覆盖 硬编码清单,未登记目录静默跳过(实测漏 2 个目录 18 篇) 每次对账 + 名称哈希自动派色 + 点名报告,不漏染
嵌套目录 子串匹配天然覆盖子目录(这是它的优点),代价是误伤同名文件 逐层显式枚举 + 继承父色:精确匹配不误伤,覆盖率实测 22.1% → 100%(v2.13)
覆盖率的分母 无此概念 audit_folder_coverage() 分「已覆盖/刻意弱化/漏染」三类计数,分母含全部层级(v2.13)
配色可辨性 无检测(03-出海TikTok #ff9f43 与其橙系新鲜度色同族;04-模板/Templates 完全同色) 每次算 ΔE 体检三类撞车,实测揪出 4 处并换色
回落可感知 不检测,回落撞车会让「过期」看不出来 A 类撞车判定 + 只判参与新鲜度着色的目录(不虚报)
告警噪声 — 刻意同色组显式声明、淡色档合并为一行,不刷屏
日期可信度 无(mtime 被云盘刷了照样当新的) 按目录判定 mtime 可信度:先检测污染簇,认定不可信后换证据链,实测纠正 221 篇、免着色 41 篇
污染后怎么办 无(只能眼看着全树变橙) 免疫:换证据链 + 无证据即撤回着色,宁可无色不谎报;留 3 条退出通道
自检是否可靠 — 对手特异性逐篇实测(不写死常量)、覆盖口径含分类色,输入不变则结论不变;并给自检本身做回归验证
自己的静默失效 同类方案与其动态层都无自检 v2.4 把「对账」、v2.5 把「色差体检」也用在自己身上,不只体检别人
回落层是否自洽 静态层裸 color、动态层 !important,自己的两层打架,30 篇被永久钉死在硬编码日期 v2.9 起全局体检 H1 会点名,且本片段分类色统一带 !important
子串是否误伤 [data-path*=&quot;index.md&quot;] 连带染到 tools/_index.md;目标文件其实不存在 全部用精确 data-path=&quot;...&quot;,并体检他人片段
配色能否固化 只能改生成方源码 外部 folder_colors.json + --pin-folders,升级不冲掉
自动派色质量 无对应能力(未登记目录直接不染) 26 色板 + 最远点贪心选池 + 报最近邻 ΔE,最近邻 10.4→18.7
选择器染的是不是真东西 不查;其静态层给 04-模板 写的两条文件夹规则命中数均为 0(前导斜杠 + 锚点转义),该目录标题从未上色,8 篇文件却有色 死选择器体检:拿真实路径撞每个选择器,0 命中即点名并给死因;v2.10 修掉自己一处同类误报(v2.9 曾据"非法转义"误判整条规则失效)
层与层谁赢 未读层 .nav-files-container .nav-file-title[data-path*=&quot;未读&quot;] .tree-item-inner = 0-4-0,动态层日期规则也是 0-4-0,双方都带 !important → 纯靠片段加载顺序分胜负;动态层是后生成的、通常排在后面,未读会被日期色压掉(冲突层 0-5-0 多一个类才幸免) 高优先级层固定排在后面,且 v2.12 起有 audit_cascade_order() 按「元素 × 通道」实算每条规则胜负,被压掉就点名;高对比档曾越过未读(实测 3 篇),已上移为 3.7 节
暗色主题 只给 2 个目录写了暗色覆盖(00-收件箱、90-归档),其余 7 个目录在暗色主题下仍用为浅底调的配色 双通道全量覆盖(.theme-dark 权威通道 + prefers-color-scheme 兜底通道),背景色反转而非沿用浅色值
无障碍高对比 无 prefers-contrast 独立加深档;v2.12 起该档排在未读/冲突之前,不会盖掉状态层(见第 24 节)
最终显示成什么颜色 不查,写进 CSS 就算数 终局颜色体检:按层叠四顺位逐篇算实际渲染色并与设计意图对账,实测揪出 12 个文件夹标题没被高对比档兜住(v2.14)
能不能解释「为什么是这个色」 不能,只能肉眼猜 --explain &lt;路径&gt; 摊开全部候选规则与那条决胜规则(v2.14)
弱化通道(opacity) 思路对、手段错:用 opacity 表达"重不重要"(色相说是什么、透明度说重不重要,两通道解耦)确实比挤在一个 color 通道强;但值过狠(90-归档 0.6 → 实测 1.91:1,等于没颜色)、写在别人片段里、且自己无体检 取其思路、弃其手段:语义收归自己的 WEAKEN_OPACITY,值进体检(初版 0.82/0.72 当场被自己判死),未设计过的一律接管为 opacity:1 撤销误伤,可 --no-weaken 一键退回(v2.15)
opacity 会不会冲淡别人 会,且无声:一句裸 opacity 就能把同库其他片段的分类色冲淡 20%~40%,而对方所有体检都在比对 color 值,全绿 渲染弱化体检:把 alpha 折算进颜色(blend)再算对比度,被谁冲淡、接管了没有,逐目录点名(v2.15)
开关语义 — v2.15 前自己踩过:EXCLUDE_FRESHNESS(名为"不参与新鲜度")被当成"整篇不参与着色",13 篇笔记含 4 篇冲突文件漏染、报告漏报 57%;已拆为 EXCLUDE_ALL_COLOR + EXCLUDE_FRESHNESS(见第 28 节)
可接流水线 无 --strict 陈旧时退出码 2;--json 输出结构化 stale_report/hygiene_report/dead_selectors(v2.9 修掉 date 序列化崩溃)
写盘策略 每天无条件重写 幂等,内容无变化不写盘
暗色主题 仅 .theme-dark 双通道,含系统偏好兜底
片段共存 带 !important,会压制他人 带 !important + 特异性加固 + 保留对手 + 竞品体检
高对比 / 动效 部分 prefers-contrast + prefers-reduced-motion

常见问题

5 问
答案由本站依据条目字段整理
Obsidian 目录新鲜度着色怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_80179e25/obsidian-freshness-coloring。安装方式为未标注。装完刷新技能列表或重开客户端。

Obsidian 目录新鲜度着色支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

Obsidian 目录新鲜度着色权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

Obsidian 目录新鲜度着色收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

Obsidian 目录新鲜度着色用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。