org-lint 是做什么的?
用于检查 org/md 文件中散文格式规则的 linter,规则定义在 ~/.claude/rules/org-babel.md 中。目标是不再依赖每次手动记忆每条规则(从而避免只修复刚提到的规则而遗漏其余规则),而是通过显式列表运行文本处理。
何时使用
- 编辑或编写 .org/.md 文件中新的散文段落后直接运行(替代手动逐条检查)——在向用户展示结果之前。
- 用户显式请求(如“检查 org 规则”、“运行 lint”、与 clippy 类比等)。
- 不适用于
#+begin_src/#+begin_example块中的内容(代码)——仅限散文段落和标题。
第一步 —— 机械检查
在编辑过的文件上运行脚本:
python3 ~/.claude/skills/org-lint/scripts/lint.py 路径/到/文件.org
脚本通过正则捕获:
/在~...~/=...=周围没有空格(~a~/~b~代替~a~ / ~b~)—— 确定违规。[[file:../—— 使用相对于当前目录的链接代替root:缩写 —— 确定违规。- 文件末尾缺少换行符 —— 确定违规。
- 大写缩写出现在
=...=内(例如=CSS=)—— 需要确认(允许罕见例外)。 - 斜体
/词/—— 需要检查:是否确实是项目术语表中的术语(spcfctn/glossary.org中有对应章节?本文件首次提及时是否包含指向它的链接?),还是错误地未转换为节/文件链接(应为[[*章节][...]])。 - 文件引用为
=路径/文件.org=—— 需要检查:是否应改为真实的[[file:...]]链接。 - 词语“参数” —— 需要检查:如果指 Rust/Yew 组件的属性(
struct ...Props),正确的翻译是“属性”,而非“参数”(旧术语)。在 Yew 组件之外(函数参数、CLI、REST 过程等),“参数”是 OK 的,无需改动。 :noweb-ref 定义块位于:noweb-ref 声明块之前 —— 需要检查:声明应排在前面,按文件从上到下的阅读顺序(参见 org-babel.md)。- 多个
#+begin_src块具有 相同 的:tangle 路径,且文件中没有任何:noweb yes/<<...>>—— 确定违规(过时的连接方式,如旧版gitignore.org—— 参见 org-babel.md)。应改为单个:tangle+:noweb yes容器块,包含<<名称>>占位符和带有:noweb-ref 名称的节。 - 文件 tangles 到 多个 不同目标文件,其中 至少两个 本身使用了 noweb(例如
config.org→.env.example+parameters.php),但某个:noweb-ref/<<...>>块未包含目标前缀(使用名称代替env:名称)—— 确定违规(参见 org-babel.md 关于多目标 org 文件中前缀的规则)。对于只有一个目标文件的文件,以及只有一个目标使用 noweb 而其余为自包含单块:tangle目标(例如gitignore.org:5 个:tangle目标,但只有根.gitignore通过 noweb 骨架)—— 不需要前缀,因为不存在实际歧义。 #+begin_src标题中的参数顺序错误 —— 确定违规。正确顺序:块类型 →:noweb yes→:noweb-sep "..."→ 其余参数 →:tangle 路径或:noweb-ref 名称(在最后,参见 org-babel.md)。- 文件名为
cargo.org—— 确定违规(Rust 项目的过时名称;应重命名为config.org,参见 org-babel.md)。 :tangle使用绝对路径(/...、~/...)或向上级目录的..—— 确定违规(参见 org-babel.md);目标必须是相对于 org 文件的纯下级相对路径。如果目标确实在源树之外,则应使用符号链接或其他目录结构,而非../绝对路径(案例:farba-override→farba-theme通过../farba-theme/...,任务FS-1085)。=...=外的大写(例如НЕ、ЛИШЕ) —— 需要检查:如果是单词强调而非缩写/简写,应替换为 org 粗体(*не*),而非全大写。- 词语“hook”/“гак”(翻译“hook”,React/Yew)—— 已过时;正确翻译是“зачіпка”(仅针对散文,不涉及
use_effect_with等代码标识符)。 - 超过 105 个字符的散文行 —— 需要通过
emacsclient调用fill-paragraph(参见下文)。
“确定”的发现应始终修复。“需要手动检查”的 —— 请逐一查看,但并非所有都是错误(例如 =rest=/=forge-yew= 是合法的缩写/库名,不是违规)。
第二步 —— 手动检查清单(脚本无法捕获的内容)
- 代码标识符/类型/函数/变量/模块 —— 以及任何代码片段或表达式(例如
id=1、value == 0、items())—— 使用波浪号~...~。缩写/首字母缩略词以及库的“公开”形式名称(带连字符)—— 使用等号=...=。限定路径库::类型是代码中的类型名称,因此整体使用波浪号,即使带有库前缀。 - 缩写/首字母缩略词 —— 在句子内部全部小写,仅在句子开头首字母大写(不是全大写),除少数固定例外。
- 同一文件内的章节链接 ——
[[*标题名称][标题]],而不是斜体或纯文本。同一目录下的文件链接 —— 直接使用[[file:相邻文件.org][描述]];其他目录(尤其是同一工作目录下的不同 git 仓库)中的文件链接 —— 通过root:缩写([[root:code/ads-back/文件.org::N][描述]]),而不是相对于当前目录的../../../。缩写在工作目录根目录的.dir-locals.el中声明(eval形式,使用locate-dominating-file)—— 如果项目中尚不存在,请创建。 - 斜体
/.../—— 仅用于项目术语表中的术语(其后立即跟(/翻译/)翻译);在每个文件中首次提及该术语时,使用[[file:.../spcfctn/glossary.org::*术语][...]]链接到相应章节,而不是重复定义。
第三步 —— 对齐
对于每个编辑过的段落,通过 emacsclient --eval 调用真实的 fill-paragraph,不要手动对齐(参见 org-babel.md 中的相应规则)。在 new_string/创建文件时将段落文本写为单行,不要自己猜测换行——这完全由 fill-paragraph 决定。
务必 在调用 org-fill-paragraph 之前 调用 (font-lock-ensure):通过 find-file-noselect 以无头方式打开的缓冲区不会进行字体锁定,因此 org 链接 [[路径][描述]] 尚未“折叠”为可显示的简短文本 描述 —— 没有 font-lock-ensure,fill-paragraph 会按链接的原始长文本计算换行,而不是按其可见宽度,结果将与实时交互式 Emacs 中按 M-q 得到的内容不一致。示例:
emacsclient --eval '
(let ((buf (find-file-noselect "路径/到/文件.org")))
(with-current-buffer buf
(org-mode)
(font-lock-ensure)
(goto-char (point-min))
(search-forward "段落的唯一片段")
(org-fill-paragraph)
(save-buffer)))
'