简体中文 · English
为充分发挥顶级模型的理解、推理与归纳能力,本 Skill 采用“模型设计规则,代码批量执行”的架构:模型先作为一次性的“分类规则设计师”,观察目标分类树与待分类数据,为当前项目生成定制化的分组与分类规则;规则确定后,再由普通 Python 代码高速、稳定、可重复地完成批量分类。
如果您想进一步了解它的详细工作原理,请将以下 Prompt 发给您的 Agent:
请阅读当前 archive-classifier 仓库,用通俗易懂的语言分步讲解其底层工作原理,重点说明 Agent 如何分析目标分类树与待分类数据、生成定制化的分组与分类规则,以及 Python 代码如何批量执行规则并形成人工审阅闭环。
如需安装,请将以下 Prompt 发给您的 Agent:
请前往 https://github.com/misdolicc/archive-classifier,为你自己安装并配置该 Skill。
这是一个 Agent Skill。SKILL.md 是供 Agent(如 Codex、Claude Code、OpenCode、TRAE 等)阅读的精简操作规程。
本 README.md 是给人看的,把同一套流程展开讲清楚每一步在做什么、为什么这么设计、遇到问题怎么排查。
-
文件只能落在"叶子节点"——目标分类树中没有子目录的目录。中间节点(还有子分类的目录) 不允许直接塞文件,否则以后没法再往下细分。
-
整体保持在一起——一份数据手册和它的 3D/2D 模型、一份多语言/多分卷的手册、一个厂商的产品资料夹、 一个软件安装包,都作为一个单元整体处理,绝不会被拆散到不同目标目录。
-
软件/驱动/库/整机转储按"包"为粒度——安装包、SDK、驱动库、运行时数据转储 (
.ors/.res/.dll/.bin、安装程序、日志)、整个设备资料夹,作为一个单元移动, 绝不逐文件拆分处理(那样既没有意义,数量也太大)。 -
结构/名字看不出来就先去看一眼,而不是硬猜——这条用在两个判断上:
- 单元深度(
eff_depth)——不该凭顶层文件夹的名字就给整个分支拍板一个深度, 写之前先用units_lib.preview_depth(src, top)看一眼该分支下子目录的真实形状 (各深度目录是不是已经没有子目录的完整包),再决定这一层该设几;同一顶层分支下 不同子树形状不一样时,eff_depth(parts)可以按parts[1](甚至更深)分开处理, 不是只能靠一张对全分支一视同仁的固定表; - 分类目标(
classify())——路径/文件名是编号、代码这类没有语义信息、仅凭名字 会被判uncertain兜底的单元,值不值得打开来看由 Agent 按单元/簇自行判断: 父目录/厂商上下文已经给够信息就不用开;属于整包处理的软件/驱动/库转储(第 3 条) 也不用开——反正整包不拆,开了也不影响归类结果。
这两步都是 Agent 在当次会话里编写/修改
eff_depth/classify()时当场做的判断, 不是逐文件/逐目录调用 AI 的自动化流水线(几千个文件、大量二进制文件的场景下, 那样做既没意义又极慢极贵)。 - 单元深度(
-
不确定就标记,交给人——落到通用的"其他/Other"叶子、或者压根没有合适类别的东西, 都会带上
status=uncertain和一句简短原因,方便人工在审阅页面里重点复核。 -
默认只出计划,不动文件——只有明确要求执行时,才会用
move_plan.py,而且默认DRY_RUN=True。
archive-classifier/
├── SKILL.md # 给Agent看的操作规程
├── README.md # 给人看的全流程介绍
├── README_EN.md # 英文版完整说明
├── head-cn.png # 中文版头图
├── head-en.png # 英文版头图
├── scripts/
│ ├── units_lib.py # 通用工具库:遍历/叶子发现(含可信清单 stable_leaf_dirs)/单元枚举/深度形状预览/计数/写计划/验收
│ ├── classify_example.py # 可运行的分类驱动骨架(复制它改造成 run.py)
│ ├── verify_coverage.py # 独立的覆盖率/合法性/重复性验收脚本
│ ├── build_review_html.py # 用 units.json + 目标树叶子列表生成审阅 HTML(并把 review_server.py 复制到工作空间)
│ ├── review_server.py # 本地后端:打开源文件预览 + 接收重分类队列(无外部依赖,可单独复制运行)
│ ├── reclassify.py # 对"标记为需要重新分类"的单元重新跑一遍分类规则
│ └── move_plan.py # 执行已审阅的计划:整单元复制/移动进目标叶子
└── templates/
└── review.html # 审阅界面工具模板骨架
其中 units_lib.py 是唯一的"通用引擎",其余脚本都是围绕它的驱动/工具;每个具体分类项目
只需要新写一个 run.py(复制 classify_example.py 改造),提供两个函数:
eff_depth(parts) -> int:某个路径分支应该在第几层被当作一个"单元"。classify(rel) -> (leaf, status, note):某个源单元应该归入目标树的哪个叶子目录。
下面按实际执行顺序讲解,每一步给出对应脚本/函数和示例。假设:
- 源目录:
E:/dump/2025-资料整理(杂乱、待分类) - 目标分类树:
E:/知识库(已经存在的分类骨架,只需要把文件放进合适的叶子)
不要对成千上万个文件逐一列出,先摸底:
import sys; sys.path.insert(0, "scripts")
import units_lib as U
print(U.summarize("E:/dump/2025-资料整理"))
# {"total": 8342, "per_top": {"设备厂商资料": 5210, "软件安装包": 2100, "杂项": 1032}}summarize()(units_lib.py)统计每个顶层目录下递归的文件总数,
以及总文件数,按数量从多到少排列,能一眼看出"体量集中在哪个顶层目录",
也能提前发现里面藏着的软件安装包/驱动转储(体积大但文件数可能很集中在少数几个包里)。
目标分类树里"没有子目录的目录"才是合法的归档目标。但要用 stable_leaf_dirs(),不要直接用
leaf_dirs():
leaves = U.stable_leaf_dirs("E:/知识库")
# ["01_物料手册/华为/Datasheet", "01_物料手册/华为/3D模型", "05_学习资料/其他", ...]原因见下面的"leaf_dirs() 是每次都实时扫描目标树、纯靠"有没有子目录"
判断叶子,一旦目标树里已经有分类挪进去的内容,这个判断就会失真。stable_leaf_dirs() 首次调用时
会自动引导(bootstrap)一份持久化清单(写在 目标树根目录/.archive_classifier_leaves.json),
之后每次都读这份清单,不再受目标树实际内容变化的影响。
后续所有分类规则产出的目标路径,最终都要能在这份列表里精确匹配到(verify() 会检查这一点)。
⚠️ 一个容易踩的坑:目标树里已经有内容了,再扫描会把内容误判成叶子假如上一轮分类已经把一个文件夹 A(比如某个产品资料包)挪进了叶子
01_物料手册/华为/Datasheet里。这一轮再对目标树做实时扫描(leaf_dirs())会发现:
Datasheet目录现在有子目录(A)了 → 不再被判定为叶子(明明它还是那个该用的目标);A自己如果没有子目录 → 被误判成一个新叶子(其实它只是被挪进去的内容,不是分类类目)。两个方向都是错的,而且这不是理论假设——这是这个 skill 被实际使用几轮之后必然会遇到的情况。 解决方式就是上面说的:任何时候要拿"目标树有哪些合法叶子",都调用
stable_leaf_dirs(dst), 不要直接调用leaf_dirs(dst)。 后者只在两种场合还有用:(a)stable_leaf_dirs内部第一次 引导清单时;(b)你想要看一眼目标树"实时"的真实结构做诊断时(比如怀疑清单本身有问题)。 所有的驱动脚本(verify、build_review_html.py、move_plan.py、reclassify.py)现在都已经 走stable_leaf_dirs,正常使用不需要自己操心这个区别,只有你自己写 Python 直接调用units_lib时才需要留意选对函数。
规模大/混杂时,应该先跟用户确认粒度,而不是自己假设:
- 文件级:适合数量不大(几百个)、已经比较规整的文档集合。
- 包/文件夹级(推荐用于转储):数千+文件、软件安装包、设备资料夹——整包移动。
粒度通过 eff_depth(parts) 函数表达——它告诉 enumerate_units()
"某个顶层目录下,第几层目录才算一个完整单元"。
这个深度不该凭顶层文件夹的名字拍脑袋定——写之前先用 units_lib.py:preview_depth 看一眼该分支下子目录的真实形状:
U.preview_depth("E:/dump/2025-资料整理", "华为服务器资料")
# {2: {"count": 3, "sample": [
# {"path": "华为服务器资料/FusionServer2288", "has_subdir": False},
# {"path": "华为服务器资料/交换机CE6857", "has_subdir": False},
# {"path": "华为服务器资料/路由器AR2200", "has_subdir": True}]},
# 3: {"count": 1, "sample": [{"path": "华为服务器资料/路由器AR2200/资料", "has_subdir": False}]}}深度 2 的目录里,前两个 has_subdir 都是 False(已经是完整产品包,无需再拆),但
"路由器AR2200"是 True(下面还分了"资料"子目录)——这种情况下,depth=2 对这个分支整体
是合适的,"路由器AR2200"内部的子目录也不会被拆开,因为一旦某目录被判定为单元根,
其所有子孙目录就不再单独成为单元(见下方算法说明)。如果反过来,某分支下深度2的目录
几乎全部 has_subdir=True(说明真正的产品/资料在深度3),就该把这个分支的深度设成3。
看完形状再落笔:
BASE = {"01_物料手册": 3, "02_行业装备": 3, "05_学习资料": 2}
def eff_depth(parts):
top = parts[0]
return BASE.get(top, 2) # 默认深度 2:顶层 -> 子目录 即为一个单元01_物料手册 深度设为 3,意味着 01_物料手册/华为/交换机 这样"厂商/产品"两层都存在时,
产品目录整体是一个单元;05_学习资料 深度 2,则 05_学习资料/Python教程 这一层就是单元,
不再往下拆。如果同一个顶层分支下不同子树的形状不一样(比如某个厂商产品都在深度2,
另一个厂商内部还分了子系列在深度3),eff_depth(parts) 可以按 parts[1](甚至更深的段)
分开处理,不是只能靠 BASE 对整个顶层分支一视同仁:
def eff_depth(parts):
top = parts[0]
if top == "华为服务器资料" and len(parts) > 1 and parts[1] == "路由器AR2200":
return 3 # 这一个产品系列内部还分了子系列,其余产品仍按默认/BASE 的深度处理
return BASE.get(top, 2)枚举单元的核心算法在 units_lib.py:enumerate_units:
- 一个目录如果"深度正好等于该分支应有的单元深度",或者"更浅但已经没有子目录了", 就被判定为一个"单元根";
- 单元根一旦确定,其所有子孙目录不会再被单独当作单元(避免嵌套重复计数);
- 不属于任何单元根的散落文件,各自单独成为一个"单文件单元";
- 保证每个文件恰好属于一个单元——这是后面覆盖率验收的基础。
如果源目录里有目标树完全没有对应位置的内容(比如混进了竞品设备资料,而目标树只为"我方设备" 建过分类),不要擅自发明新目录——应该向用户提出建议的新顶层类别,确认后,在目标树里 先手动建好新的叶子目录,这样后续分类规则产出的目标路径才是"真实存在的叶子",能通过验收。
建完新叶子目录后,别忘了紧接着登记进可信清单:
U.add_leaves("E:/知识库", "02_行业装备/竞品设备资料")原因见上一步的"stable_leaf_dirs() 不会自动重新扫描目标树发现这个刚建好的新叶子,
必须显式调用 add_leaves() 登记,它才会出现在后续分类可用的叶子列表里。这也是刻意的:
"这是我刚新建的分类目录"和"这是被挪进去的内容",只能靠人工/Agent 明确知道自己做了什么来
区分,没法靠重新扫描目标树自动分辨。
同时,遇到语义模糊的现有类别(例如"产品信息资料"到底是指"我们要测试的对象"还是"我们自己的设备"), 也应该跟用户确认清楚并记下来,避免分类规则一开始就理解错方向。
复制 scripts/classify_example.py 为项目专属的 run.py,
只需要修改文件里明确标出的 3 处:
① 路径与兜底叶子(脚本顶部常量,务必用绝对路径 + 正斜杠,且不要通过命令行传中文路径, 原因见下方"Windows + 中文路径注意事项"):
SRC = "E:/dump/2025-资料整理"
DST = "E:/知识库"
PLAN = "E:/dump/2025-资料整理_文件分类移动计划.txt"
JSONF = "E:/dump/2025-资料整理_units.json"
FALLBACK_LEAF = "05_学习资料/其他" # 必须是目标树里真实存在的叶子② 单元深度 eff_depth(parts)——见上一节。
③ 分类规则 classify(rel)——按"先限定顶层大类范围,再用关键字细化"的方式写有序规则,
第一条命中的规则生效;所有规则都没命中时,兜底落到 FALLBACK_LEAF 并标记 uncertain:
def classify(rel):
parts = rel.split("/"); top = parts[0]; low = rel.lower()
OK = lambda leaf: (leaf, "normal", "")
UNC = lambda leaf, why="待人工复核": (leaf, "uncertain", why)
if top == "设备厂商资料":
if "半导体" in rel:
return OK("同行业装备资料/半导体检测装备")
return OK("同行业装备资料/汽车电子检测装备")
if top == "软件安装包":
if "驱动" in low or "driver" in low:
return OK("软件驱动库/设备驱动")
return UNC("软件驱动库/其他软件", "未识别具体软件类型")
return UNC(FALLBACK_LEAF, "未归类 — 待人工复核")规则编不出来的情况——遇到编号/无语义文件名,该不该打开看内容,由 Agent 自己判断:
路径和文件名给不出任何有用线索时(一堆 GB50054-2011.pdf、7F3D2A1.pdf 这种编号/代码型文件),
继续硬凑关键字规则只会瞎猜。这时候按情况处理:
- 父目录/厂商上下文已经够用 → 不用开文件,比如
国家标准资料/GB50054-2011.pdf光看父目录国家标准资料就能归类,文件名本身的编号无所谓; - 编号本身有规律 → 用正则匹配编号前缀(如
re.match(r'GB\s?/?T?\s?\d+', name)), 不用逐个打开,看几个样本认出规律就行; - 规律也看不出来、数量不多、值得看一眼 → Agent 直接用 Read 工具打开该文件(或提取 PDF/Word 文本)确认内容,据此写规则或者干脆建一张"文件名 → 目标叶子"的精确对照表;
- 属于软件安装包/驱动库/整机资料这类整包处理的单元(核心思路第 3 条) → 不用开, 反正整包就是一个单元,内部文件类型再杂也不拆分处理,打开了也不影响归类结果。
这一步是 Agent 在编写/修改 classify() 时当场做的判断和操作,不是一套自动化的"每个文件
都调用一次 AI 生成摘要"的流水线——几千个文件、大量二进制文件的场景下,那样做时间和成本都不划算,
也没必要。真正认不出来的,照旧老实标 uncertain,交给审阅页面里的人工复核。
PYTHONIOENCODING=utf-8 python run.pyrun.py 的主流程(骨架里已经写好):
-
U.enumerate_units(SRC, eff_depth)枚举出所有单元及各自的文件数; -
U.build_rows(units, classify)对每个单元跑一遍classify(),生成[{src, leaf, count, status, note}, ...]; -
U.dump_json(rows, JSONF)写出units.json(后续 HTML 审阅工具 / 重分类脚本都依赖它); -
U.write_plan(rows, PLAN, SRC, DST)写出人类可读的计划文本:源目录根:E:/dump/2025-资料整理 目标目录根:E:/知识库 粒度:整包/文件夹为单位(软件/驱动/库/整包不拆)。格式:源单元 => 目标叶子 (文件数)[待确认] ========================================================================================== 设备厂商资料/华为/交换机X1 => 同行业装备资料/半导体检测装备 (214 文件) 软件安装包/未知安装器v2 => 05_学习资料/其他 (1032 文件) [待确认:未识别具体软件类型] -
U.verify(rows, SRC, DST)自检一遍并打印结果(下一步细说)。
run.py 运行结束会自动打印验收结果,也可以单独用 verify_coverage.py
对着已生成的 units.json 复查:
PYTHONIOENCODING=utf-8 python scripts/verify_coverage.py三项必须全部通过(units_lib.py:verify):
| 检查项 | 含义 | 不通过说明什么 |
|---|---|---|
coverage_ok |
各单元文件数之和 == 源目录实际文件总数 | 有文件被漏掉或被重复计数了 |
invalid_leaves |
计划里的目标路径必须是目标树里真实存在的叶子 | classify() 写错了目标路径,或目标树缺这个叶子 |
duplicate_units |
同一个源单元不应该出现两次 | 单元枚举逻辑有问题(理论上不会发生) |
三项都为空/为真才算 PASS,否则要回去修 eff_depth/classify/目标树,直到通过。
invalid_leaves 的校验用的是 stable_leaf_dirs()(可信清单),不是实时扫描目标树——所以
即便目标树里已经有之前分类挪进去的内容,这一步也不会被污染而误报。
PYTHONIOENCODING=utf-8 python scripts/build_review_html.py编辑 build_review_html.py 顶部的
UNITS/SRC/DST/OUT/NAME/BIG/COPY_BACKEND 常量后运行,它会把 units.json 的内容和
stable_leaf_dirs(DST) 的叶子列表(可信清单,不受目标树里已有内容干扰),一起塞进
templates/review.html 模板的三个
占位符(__LEAVES__/__UNITS__/__META__),生成一个单文件、零依赖的 HTML,双击即可在浏览器里打开使用。
同时(COPY_BACKEND 默认为 True)会把 scripts/review_server.py 复制一份到 OUT 所在的
工作空间目录里(和 review.html/units.json/计划文件放在一起)——这样工作空间自成一体,
在那个目录里直接 python review_server.py review.html 就能启动后端,不用再回到 skill 自身的
scripts/ 目录去找这个文件。review_server.py 本身只用标准库、不依赖 units_lib,所以能单独
拷贝、独立运行。每次生成/重建 HTML(包括 reclassify.py 触发的重建)都会重新复制一份,
保证工作空间里的副本始终是当前版本;如果 OUT 恰好就在 scripts/ 目录下(源和目标是同一个
文件),会自动跳过复制。
审阅页面能做什么:
- 搜索 / 按大类筛选 / 按源目录或目标目录分组 / 按路径或文件数排序;
- 筛选药丸:待确认 / 已改目标 / 未审阅 / 已审阅 / 大包(超过
BIG阈值)/ 待重分类; - 直接编辑目标路径(自动补全 + 非法目标红框提示,只能选真实存在的叶子);
- 逐条标记"已审阅",顶部有整体审阅进度条;
- 勾选"需要重新分类",标记那些分类结果不满意、需要改规则重新跑一遍的单元;
- 导出已审阅计划(txt,供 move_plan.py 使用)/ 导出 CSV;
- 进度保存在浏览器 localStorage(按数据集名称隔离,多个审阅页面互不干扰),刷新不丢失。
纯 file:// 方式打开也能正常审阅、编辑、导出;只有"点击源文件打开"和"提交待重分类"
这两个功能需要下一步的本地后端配合。
PYTHONIOENCODING=utf-8 python scripts/review_server.py ./review.htmlreview_server.py 是一个只绑定 127.0.0.1 的极简 HTTP 服务,
自动读取 HTML 里内嵌的 __META__(源目录根、数据集名),启动后自动打开浏览器。有它之后:
- 点击某一行的源路径,会用系统默认程序只读打开对应的文件/文件夹(
GET /open, 服务端会校验解析后的真实路径必须仍在源目录根之内,防止路径穿越访问到源目录之外的内容); - 点击"提交待重分类",把勾选的单元 POST 到
/reclassify-queue,写成<name>_reclassify_queue.json,供下一步消费。
审阅中发现某批单元分类得不对时的闭环流程:
-
页面上勾选这些行的"需要重新分类",点击"提交待重分类";
-
去
run.py里改进classify()规则(方法和第一次分类完全一样); -
运行 reclassify.py:
PYTHONIOENCODING=utf-8 python scripts/reclassify.py
它只对队列里的单元重新跑一遍
classify()(OVERRIDES字典还支持对个别单元手动 指定目标,不走规则),更新units.json里这些行的leaf/status/note,并把rev版本号加 1,然后重建 HTML;处理完的队列文件会被重命名为.done,防止被重复应用。 -
浏览器刷新页面——
rev提升会让页面发现这些单元的本地审阅状态已过期, 自动丢弃旧的"已审阅/已改"标记,展示最新分类结果,重新进入待审阅状态。
只有用户明确要求执行时才做这一步。在审阅页面点击"导出计划(txt)",得到
<name>_文件分类移动计划_已审阅.txt,然后:
PYTHONIOENCODING=utf-8 python scripts/move_plan.py编辑 move_plan.py 顶部常量:
PLAN = "E:/dump/2025-资料整理_文件分类移动计划_已审阅.txt"
MODE = "copy" # "copy"(保留源文件,推荐) 或 "move"
DRY_RUN = True # 先 True 看预览+日志,确认无误后改成 False 才会真的执行务必先 DRY_RUN=True 跑一遍,它会把每一条要执行的操作(COPY/MOVE 哪个源到哪个目标,
是否因为改名冲突而重命名)打印出来并写一份日志,但不改动任何文件;确认无误后再改成
DRY_RUN=False 正式执行。执行时的几个安全保证:
- 整单元操作——一个软件包/资料夹要么整体复制/移动,绝不会被拆散逐文件搬运;
- 冲突安全——目标位置如果已存在同名文件/文件夹,自动加
(2)、(3)后缀, 绝不覆盖或合并已有内容; - 目标校验——计划里的目标路径会重新对照
stable_leaf_dirs()(可信清单)做匹配(忽略大小写/ 分隔符差异),匹配不上的目标默认跳过(CREATE_MISSING_LEAVES=True时会新建该叶子目录, 并自动调用add_leaves()把它登记进可信清单——这样下一轮分类才认得它是"真实类目", 而不是要靠重新扫描目标树去发现); - 执行结束会打印完成/跳过/出错的数量,并把完整日志写到
<plan>_move_log.txt。
| 概念 | 说明 |
|---|---|
| 单元 (unit) | 分类的最小整体处理粒度:一个软件包、一个产品资料夹、或一个散落文件。整体归类,不拆散。 |
| 叶子目录 (leaf) | 目标分类树中没有子目录的目录,是唯一合法的文件归档位置。判断时应该用 stable_leaf_dirs()(可信清单),不是每次实时重新扫描。 |
可信叶子清单 (stable_leaf_dirs/.archive_classifier_leaves.json) |
持久化保存在目标树根目录下的隐藏文件,记录"目标树本来的分类骨架长什么样",不受后续移动进去的内容干扰;新增真实类目要用 add_leaves() 显式登记。 |
单元深度 (eff_depth) |
某个顶层目录分支下,第几层目录被当作一个完整单元(由项目自己决定,体现分类粒度)。写它之前应该用 preview_depth 先看看真实形状,而不是凭名字猜。 |
深度形状预览 (preview_depth) |
按深度分层展示某分支下的目录样本 + 是否还有子目录,帮助决定 eff_depth 该设几层。 |
覆盖率验收 (verify) |
源目录里"每个文件都被计入且只被计入一次"、"每个目标都是真实叶子"、"没有重复单元"三项检查。 |
rev 版本号 |
每个单元的分类版本;重新分类后 +1,驱动审阅页面自动重置该单元的本地审阅状态。 |
status: uncertain |
规则没能确定分类、落到兜底叶子的单元,会带一句原因,提示人工重点复核。 |
本项目主要在 Windows + 中文路径 场景下使用(纯 Linux/macOS 或纯 ASCII 路径可忽略本节):
- 运行任何脚本都建议加上
PYTHONIOENCODING=utf-8,避免中文在控制台输出乱码/报错。 - Git Bash 会破坏命令行参数和 here-doc 里的中文字符——所以所有中文路径都应该写在
每个脚本顶部标好的 EDIT-ME 常量里(用文本编辑器/Write 工具写一个 UTF-8 的文件),
然后直接
python run.py运行整个文件,而不是把中文路径当命令行参数传入, 也不要放进bash <<'PY' ... PY这样的 here-doc。 - 路径统一使用 posix 风格的正斜杠
/作为唯一"规范分隔符"(展示和内部匹配都用它), 它在 Windows 上同样合法;审阅页面在匹配目标时会自动兼容用户粘贴内容里的\和大小写差异。
verify()报invalid_leaves不为空:classify()里写的目标路径和可信清单 (stable_leaf_dirs(DST))里的叶子对不上(打错字、目标树里其实是中间节点没有叶子、 或者目标树本来就缺这个类目,又或者是刚建的新叶子忘了调用add_leaves()登记)。 打印units_lib.stable_leaf_dirs(DST)核对拼写和登记情况,而不是leaf_dirs(DST)(后者是实时扫描,目标树里如果已经混了内容,打印出来的东西本身就可能是错的)。coverage_ok为False:一般是eff_depth写得不对,导致某些目录被判定为单元后, 它的子文件又被算进了别的单元(或者反过来漏算)。先看enumerate_units返回的单元列表, 对照实际目录结构确认深度设置是否符合预期。- 怀疑某个分支的单元被分得太粗或太细(比如好几个不相关的产品被捆进了同一个单元,
或者一个完整的包被拆成了好几个单元):用
units_lib.preview_depth(SRC, 该顶层分支)看一眼真实形状,确认该深度上的目录是不是已经是没有子目录的完整包;同一分支内形状不一致时, 在eff_depth(parts)里按parts[1](甚至更深)针对性处理,而不是整个分支套用同一个深度。 - 目标树里明明已经建好了新叶子目录,但分类/审阅页面里还是找不到它:新建的叶子目录必须
显式调用
units_lib.add_leaves(DST, "新叶子路径")登记进可信清单才会生效——stable_leaf_dirs()不会自动重新扫描目标树去发现它,这是刻意设计(见"发现目标叶子目录"一节的⚠️ 说明)。 - 怀疑可信清单本身有问题(比如第一次生成清单时目标树就已经不干净了):清单就是
目标树根目录/.archive_classifier_leaves.json,一份普通的 JSON 数组,可以直接打开 编辑核对;确认目标树恢复干净状态后,删掉这个文件,下次调用stable_leaf_dirs()/verify()会用当前实时结构重新引导一份新的。 - 审阅页面里目标输入框一直是红框:说明当前填的目标不在
stable_leaf_dirs(DST)列表里, 从自动补全下拉里选一个真实存在的叶子;如果这是一个刚建好但还没登记的新叶子, 按上面说的用add_leaves()登记后重新生成审阅页面。 - "点击打开源文件"/"提交待重分类"没反应:这两个功能需要通过
scripts/review_server.py提供服务打开页面(http://127.0.0.1:端口/), 纯file://双击打开的页面会自动降级(提示需要后端 / 改为下载 JSON)。 move_plan.py大量SKIP invalid leaf:说明审阅导出的计划文件里,有目标路径 在可信清单里找不到——通常是目标树在生成计划之后新增过类目但没调用add_leaves()登记; 打印stable_leaf_dirs()核对,或者按需打开CREATE_MISSING_LEAVES=True(谨慎使用, 会自动登记新建的叶子)。
