feat(skills): 两包资源索引机制与双向对账脚本

- product-planning:新建 references/manifest.md 作资源唯一登记处(资源/落点/引用点/口径);
  顶层 SKILL 加两条硬规矩,design-capture、stage-delivery、execution-runbook、stage-requirements
  四处把散落路径改为「落点见 references/manifest.md」
- session-mechanism:01-文档索引 新增 §四「包内资源索引」,并说明不放 manifest.md 的理由
  (那份由 install.py 本机生成、被 .gitignore 挡着)
- 新增两包共用的 scripts/check_resource_index.py(双向对账)与 product-planning/scripts/check_figures.py(图片机检),
  selftest 补对应用例,06-技能优化规程 写入写法规矩
- .gitignore:新增 _留痕/ 不入库(改造前整包快照,180K/688K,库内从无先例)
This commit is contained in:
admin committed 2026-10-18 18:07:40 +08:00
1 parent a9d2adaf72
commit acfb252bee
14 files changed
+626 -39

No files matched your search

+6
View File
@@ -107,5 +107,11 @@ node_modules/
# 🔴 本行必须留在文件末尾:白名单是「先 /* 全挡、再逐个放行」,否则 `!/product-planning/` 会把它放回来。
/product-planning/references/stage-delivery/assets/design-systems/design-system-tiaoyue/
# 🔴 2026-10-18 加:`_留痕/` 是改造前的**整包快照备份**,⛔ 不入库。
# 判据:本仓历史 `git ls-files | grep _留痕` **0 条**(从无先例)+ 体积(本次两包 180 K/688 K);
# 留痕属「本机可回查的历史」,不是可复制资产(同「备份不是资产,是历史」那条)。
# ⚠️ 放行行在前、本行在后 ⇒ 后者覆盖前者,`git add -A` 不会再把它捎进来。
_留痕/
# 运行期存档(`element-picker` 拾取器每次点「确认」都会往这里追加;服务自己会建目录,不入库)
/product-planning/assets/element-picker/marks/
+8 -1
View File
@@ -227,6 +227,8 @@ DESIGN.md 说明:`@google/design.md` 提供 `lint` / `diff` / `export` / `spec
- 禁止长文:单份产出物默认一页纸(≤120 行),超长必须拆分。
- ⭐ 落盘产出的排版(2026-10-14 用户令,原话「标题周围写小字的怪毛病 彻底改掉」):元信息(条数、口径、读法、状态)写进表格的列、条目的字段或表头;标题旁边、标题下面、标题后面都只放标题本身。④段的 `references/doc-language-spec.md` 另有一份更细的,冲突以那份为准。
- 🔴 路径写相对或占位(2026-10-14 用户令):本包内一切文字(`SKILL.md`、`references/**`、脚本与注释、模板)引用**本技能自己或同根技能**的位置时,写相对形态 —— 同根技能写 `<技能根>/<技能名>/`,本包自己的文件写包内相对路径(如 `references/stage-delivery/`)。只有**跨根引用**(工作区根、文档库根、本机软件安装位这类从本包推不出来的别的根)才落到具体位置,且优先读环境变量(`DSH_WS_ROOT` / `DSH_DOCS_ROOT` / `WORKBUDDY_BIN_ROOT`)。原因:技能根的位置随环境变(全局技能根还是工作区技能根、用户名与盘符各不相同),写死了换台机器就是死路径,跨环境会整片报错。
- 🔴 **资源路径只在资源清单写一次(2026-10-16 用户令)**:本包有什么资源、落在哪、被谁引用,**唯一登记处**是 `references/manifest.md` 的「资源索引」表;别的文字里**只写资源名**(如「样式套目录」「工具箱·design-capture」),⛔ 不把路径抄进散文、段文档、脚本注释与模板。理由(用户原话:「为什么这么多地方引用一个样式库,是不是技能中没有统一索引机制」):路径一抄成 N 处,改一次目录就要改 N 个地方,**漏一处还不报错**。清单里标 `只写一次` 的资源,其路径字面量只许出现在清单本身与清单 `许可字面量` 列登记的文件里。
- 🔴 **改完本包跑一次资源索引对账**(同一道令):`python "<技能根>/session-mechanism/scripts/check_resource_index.py" --pkg "<本包根>"`(两个技能包共用这一份实现)。它报四类:落点不存在 / 登记的引用点不是真的 / 有人绕开清单在别处写了路径字面量 / 登记了却没人用的资源。⛔ 有 🔴 不算改完,不静默放过。
## 征询外部模型(GPT)的提问口径(单一真源)
@@ -247,11 +249,16 @@ DESIGN.md 说明:`@google/design.md` 提供 `lint` / `diff` / `export` / `spec
- 图要**表达结构或流程**(二选一或兼具)—— 一张图看完就知道「由哪几块组成、怎么连起来」或「一件事分几步走」;⛔ 不做纯装饰性配图。
- 图表达这份文档**覆盖的东西之间的关系**,按段定画什么:
· ①段(`research/` 五份)—— 画对象与流向:为谁解决什么、信息从哪来到哪去。
· ②段(文档根 `产品规划/prd/` 两份)—— 画页面骨架:有哪几页、每页几个板块、跨页关系怎么走。
· ①段 `research/1b-独立分析/<竞品名>.md` —— 每个候选一份,图要画该候选管哪个环节、缺哪个环节(通常是「环节覆盖条」)。
· ②段 `prd/2a-产品功能.md` —— 画**功能归组**:功能组有几组、每组覆盖哪几段、各多少条(页面此时还没定,⛔ 不画页面骨架)。
· ②段 `prd/2b-界面布局.md` —— 画**页面骨架**:有哪几页、每页几个板块、跨页关系怎么走。
· ③段(`ui/` 下的产物与 `DESIGN.md`)—— 画页面结构与操作动线:用户办成一件事实际经过哪几页、每页做什么、哪里要来回跳。
· ④段(产品文档)—— 画页面结构图(各页面与其下的功能编号)+ 用户路径:各场景怎么串起来。
· 跨段 `专题/` —— 按该篇讲什么定:讲方法画方法的分层或步骤,讲盘点画对比矩阵,讲落地画数据流或结构。
- **一份文档一张自己的图**:图要对着这份文档的内容画,⛔ 不借用兄弟文档的图(借用必然出现「图说别的、文说这个」)。
- 画图用 `diagram-design`,⛔ 不手写 Mermaid(判据见「工具选择规则」那张表)。
- 图里每格写清名字、线标方向;图与正文互相对得上 —— 正文新增或改了结构,图要跟着改,两处不许各说各的。
- **收口跑机检**:`python scripts/check_figures.py --ws <工作区或产品根>`。它逐份扫 `产品规划/` 下除 `归档-过期版/` 的 md,判定三件事:开头 8 行内有 `![…](…)`、引用的图文件在位、同一张图没有被两份文档共用。报 FAIL 就不算收口。
## 每段的标准收尾格式
@@ -6,12 +6,12 @@ agent_created: true
# design-capture · 网站设计风格抓取(③段独立工具)
把「一个网址」变成「一套可选样式模板」:抓源站 → 落原始设计系统 → 生成 `design-system-<名>/` 模板包(与 `assets/design-systems/design-system-tiaoyue/` 同构),随后可被 D1 定调当普通样式套用。
把「一个网址」变成「一套可选样式模板」:抓源站 → 落原始设计系统 → 生成 `design-system-<名>/` 模板包(与③段**样式套目录**里那套 `design-system-tiaoyue` 同构),随后可被 D1 定调当普通样式套用。
## 一、什么时候用
- 用户说「**获取 XXX 网站的设计风格**」「抓这个网站的配色/字号/组件」「按这个网站新建一套样式」。
- ③段 D1 定调时,现有 `assets/design-systems/` 里没有合适的套 → 从某个标杆网站**现场新建一套**。
- ③段 D1 定调时,现有**样式套目录**(落点见本技能 `references/manifest.md` 的「样式套目录」行)里没有合适的套 → 从某个标杆网站**现场新建一套**。
## 二、怎么用(两步)
@@ -31,7 +31,7 @@ python scripts/build_style_package.py --raw raw.json --out ./design-system-<名>
- 产出:`SKILL.md` + `assets/tokens.css`(+`tokens-dark.css`)+ `assets/brand.json` + `references/{components.md,library.json,design-system.html,capture-notes.md}`。
第三步 · 装进样式库(供 D1 选用)
把生成目录整体放进 `assets/design-systems/design-system-<名>/`,即成为可选样式;③段 D1 定调时与 open-design 的套**同权,每次只选一套**。
把生成目录整体放进**样式套目录**(落点见本技能 `references/manifest.md` 的「样式套目录」行)下的 `design-system-<名>/`,即成为可选样式;③段 D1 定调时与 open-design 的套**同权,每次只选一套**。
## 三、边界(如实登记)
@@ -4,7 +4,7 @@
design-capture · 设计系统模板生成器(③段工具)
做什么:把抓取 raw JSON(capture_design_system.mjs 的产出)转成一套「可选样式」模板包,
与 skills/.../assets/design-systems/design-system-tiaoyue/ 同构,可直接被 D1 定调选用。
与③段**样式套目录**里那套 design-system-tiaoyue 同构(落点见技能内 references/manifest.md),可直接被 D1 定调选用。
输出(默认写入 --out 目录):
SKILL.md 规范总纲(含七语义色、排版、几何、组件用法、生成步骤)
+58
View File
@@ -0,0 +1,58 @@
# manifest · 资源索引(product-planning)
> **这一份管什么**:本包「有哪些资源、落在哪、被谁引用」的**唯一登记处**。
> 散文与段文档里**只写资源名**(本表第一列),路径**只在本表写一次** —— 目录一搬,只改这一行。
> **机检**:改完包跑
> `python "<技能根>/session-mechanism/scripts/check_resource_index.py" --pkg "<本包根>"`
> (两包共用一份实现,⛔ 不各写一份)。它做双向对账:落点是否存在 / 登记的引用点是否为真 /
> 是否有人绕开本表在别处写路径字面量 / 有没有登记了却没人用的资源。
>
> **`口径` 列**:`只写一次` = 该资源的路径字面量只许出现在本表与 `许可字面量` 列登记的文件里,
> 出现在别处机检直接报 🔴(这就是「避免资源到处引用」的硬闸门);
> `可多次` = 只登记「谁在用、在哪」,不限字面量(代码路径、规程路径这类必须精确到文件)。
>
> **`被谁引用` 与 `许可字面量` 别混**:前者是「谁在用」,可由机检 `--sync-refs` 自动回写;
> 后者是「谁被允许写路径字面量」,**人工开的口子**,⛔ 自动回写会把它冲成「谁都许可」、G3 就变空闸门。
>
> **加新资源**:在本表加一行即可。引用它的地方写资源名,⛔ 不要把路径抄进散文 ——
> 抄进去就会成为下一处「改目录要改 N 个地方、漏一处还不报错」。
| 资源 | 落点 | 干什么 | 被谁引用 | 许可字面量 | 口径 | 入仓 |
|---|---|---|---|---|---|---|
| 样式套目录 | `references/stage-delivery/assets/design-systems/` | ③段自带设计套(每个项目只选一套;tiaoyue 那套在此) | SKILL.md、assets/design-capture/SKILL.md、assets/design-capture/scripts/build_style_package.py、references/stage-delivery/SKILL.md、references/stage-delivery/references/execution-runbook.md、references/stage-requirements/SKILL.md | `references/stage-delivery/references/execution-runbook.md` | 只写一次 | 否 |
| 工具箱·diagram-design | `assets/diagram-design/` | ②④段画图(41 种图型,MIT) | SKILL.md、assets/diagram-design/SKILL.md、assets/diagram-design/assets/example-loop-terminal.html、assets/diagram-design/references/doctor.md、assets/diagram-design/references/export-registry.md、assets/diagram-design/references/export.md、assets/diagram-design/references/import-drawio.md、assets/diagram-design/references/import-excalidraw.md、assets/diagram-design/references/import-mermaid.md、assets/diagram-design/references/onboarding.md、assets/diagram-design/references/primitive-terminal.md、assets/diagram-design/references/profiles.md(共 17 个) | — | 可多次 | 是 |
| 工具箱·design-capture | `assets/design-capture/` | 抓网站设计风格(色彩 / 排版 / 组件) | SKILL.md、assets/design-capture/ATTRIBUTION.md、assets/design-capture/SKILL.md、assets/design-capture/scripts/build_style_package.py、assets/design-capture/scripts/capture_design_system.mjs、references/stage-delivery/SKILL.md、references/stage-delivery/assets/design-systems/design-system-tiaoyue/assets/component-library/components.css、references/stage-delivery/references/execution-runbook.md | — | 可多次 | 是 |
| 工具箱·element-picker | `assets/element-picker/` | 浏览器里点选元素,取出 CSS 选择器与所属网页 | SKILL.md、assets/element-picker/SKILL.md、references/stage-delivery/SKILL.md | — | 可多次 | 是 |
| 工具箱·video-capture | `assets/video-capture/` | 只剩交接说明;实体是全局技能 `video-capture` | SKILL.md、assets/video-capture/ATTRIBUTION.md、assets/video-capture/SKILL.md、references/stage-delivery/SKILL.md | — | 可多次 | 是 |
| ②段模板·产品规划模板 | `references/stage-requirements/assets/产品规划模板.md` | ②段两张表的表头唯一定义处 | references/stage-requirements/SKILL.md | — | 可多次 | 是 |
| ④段模板·产品文档模板 | `references/stage-proto-doc/assets/产品文档模板.html` | ④段产品文档模板 | SKILL.md、references/stage-proto-doc/SKILL.md、references/stage-proto-doc/references/doc-area-spec.md | — | 可多次 | 是 |
| 同根技能·oil-ui-pro | `<技能根>/oil-ui-pro/` | ③段方法主线(基础层设计系统 / 评分循环) | SKILL.md、references/stage-delivery/SKILL.md、references/stage-delivery/references/execution-runbook.md、references/stage-delivery/references/layouts-tooling.md | — | 可多次 | 是 |
| 同根技能·open-design | `<技能根>/open-design/` | ③段模板与 craft 判据 | assets/design-capture/ATTRIBUTION.md、assets/design-capture/SKILL.md、assets/design-capture/scripts/build_style_package.py、assets/design-capture/scripts/capture_design_system.mjs、references/stage-delivery/SKILL.md、references/stage-delivery/assets/design-systems/design-system-tiaoyue/assets/brand.json、references/stage-delivery/assets/design-systems/design-system-tiaoyue/assets/tokens.css、references/stage-delivery/assets/design-systems/design-system-tiaoyue/references/capture-notes.md、references/stage-delivery/assets/design-systems/design-system-tiaoyue/references/known-conflicts.md、references/stage-delivery/references/execution-runbook.md、references/stage-delivery/references/layouts-tooling.md | — | 可多次 | 是 |
| 同根技能·humanizer | `<技能根>/humanizer/` | 文案判据的唯一来源 | SKILL.md、references/stage-discovery/SKILL.md | — | 可多次 | 是 |
| 同根技能·browser-harness | `<技能根>/browser-harness/` | 浏览器实测 | references/stage-delivery/SKILL.md、references/stage-delivery/references/execution-runbook.md、references/stage-delivery/scripts/_common.py、references/stage-delivery/scripts/grab_selftest.py、references/stage-delivery/scripts/run_viewports.py | — | 可多次 | 是 |
| 全局技能·video-capture | `<技能根>/video-capture/` | 取视频 + 抽帧(实体件) | SKILL.md、assets/video-capture/ATTRIBUTION.md、assets/video-capture/SKILL.md、references/stage-delivery/SKILL.md | — | 可多次 | 是 |
| 机检·资源索引对账 | `<技能根>/session-mechanism/scripts/check_resource_index.py` | 本表的双向对账机检(两个技能包共用) | SKILL.md | — | 可多次 | 是 |
| ①段规程 | `references/stage-discovery/` | ①段(调研方案)流程规程 | SKILL.md、references/stage-discovery/SKILL.md、references/stage-discovery/references/grill-me.md、references/stage-requirements/SKILL.md、scripts/check_naming.py | — | 可多次 | 是 |
| ②段规程 | `references/stage-requirements/` | ②段(需求结构)流程规程 | SKILL.md、references/stage-delivery/SKILL.md、references/stage-discovery/SKILL.md、references/stage-requirements/SKILL.md、scripts/check_naming.py | — | 可多次 | 是 |
| ③段规程 | `references/stage-delivery/` | ③段(界面交付)流程规程 | SKILL.md、references/stage-delivery/SKILL.md、references/stage-delivery/references/execution-runbook.md、references/stage-delivery/references/layouts-tooling.md、references/stage-requirements/SKILL.md、scripts/check_naming.py、scripts/selftest_product_root.py | — | 可多次 | 是 |
| ④段规程 | `references/stage-proto-doc/` | ④段(说明文档)流程规程 | SKILL.md、references/stage-delivery/SKILL.md、references/stage-proto-doc/SKILL.md、references/stage-proto-doc/references/doc-area-spec.md、references/stage-proto-doc/references/guided-tour.md、scripts/check_naming.py | — | 可多次 | 是 |
| 本包脚本 | `scripts/` | 命名闸门 / 用词闸门 / 产品根自检 | SKILL.md、assets/design-capture/SKILL.md、assets/diagram-design/SKILL.md、assets/diagram-design/assets/example-beeswarm-dark.html、assets/diagram-design/assets/example-beeswarm-full.html、assets/diagram-design/assets/example-beeswarm.html、assets/diagram-design/assets/example-bubble-dark.html、assets/diagram-design/assets/example-bubble-full.html、assets/diagram-design/assets/example-bubble.html、assets/diagram-design/assets/example-bump-dark.html、assets/diagram-design/assets/example-bump-full.html、assets/diagram-design/assets/example-bump.html(共 70 个) | — | 可多次 | 是 |
## 为什么「样式套目录」被单独设成「只写一次」
用户 2026-10-16 原话:**「为什么这么多地方引用 一个样式库,是不是技能中没有统一索引机制」**。
取证(2026-10-16 实测,6 个文件、20 处路径字面量):③段 `SKILL.md` 9 处、③段运行手册 5 处、
`assets/design-capture/SKILL.md` 3 处、②段 `SKILL.md` 1 处、`assets/design-capture/scripts/build_style_package.py` 1 处、缓存 1 处。
其中「供给索引」「怎么读它」「跨段同源路径」三类引用**目的不同,本身合理**;真正的缺陷是
**路径字面量被反复手抄** —— 改一次目录要改 N 个地方,**漏一处不报错**。
⇒ 处置:路径收敛到本表这一行,散文改写资源名「样式套目录」;
**唯一保留字面量的引用点**=`references/stage-delivery/references/execution-runbook.md` 的 D−1「三处供给仍在位」检查
(那一步真的要判目录在不在,已登记在上表 `许可字面量` 列 ⇒ 机检 G3 放行它)。
## 与「技能级 `assets/`」那条口径的分工
入口 `SKILL.md` 那条红线(凡引**工具**一律写「技能级 `assets/<工具名>/`」,2026-10-16 22:57 用户令)
**继续有效**,与本表不冲突:那条规定的是**引用工具时写什么形态**,本表登记的是**这些资源落在哪、谁在用**。
⇒ 工具类资源在上表里一律 `可多次`,机检只登记、不限制字面量。
@@ -24,7 +24,7 @@ description: 产品规划第③段,界面交互。设计工程供给默认来
| 供给位 | 默认来源 | 可选来源 | 管什么 | 用在哪 |
|---|---|---|---|---|
| 方法主线 | `oil-ui-pro`(全局技能,按技能名引用) | 无 | 方向探索 → 建结构 → 取实证 → 独立评审打分 → 验证交付 | 全段;3a / 3d 最重 |
| 定调与令牌 | 自带样式库 `assets/design-systems/<选定一套>/`(随段携带,现 1 套 `design-system-tiaoyue`) | `assets/open-design/design-systems/<选定一套>/`(153 套) | 调性、全套令牌、组件形态。⚠️ **能力边界(2026-10-13 订正)**:自带样式库**不止调色板级** —— `design-system-tiaoyue/assets/component-library/`(采集于 2026-10-12)含 **9 个板块的整页快照**(`snapshot/<板块>.html`,去脚本、图片 URL 绝对化,7 个板块与线上逐像素一致率 ≥99.3%)、**345 个自有组件族**(`gallery.html` 是可视化总库,带搜索与一键复制 HTML)与**整站 CSS 原样合并**(`tiaoyue-all.css`,53 张 sheet / 1.11 MB)。⇒ **有目标产品整页快照就直接改快照当原型页**:打开对应板块的 snapshot(`snapshot/<板块>.html`)、改文案、删模块,立刻得到像素级同款的整页;要单个组件就走组件库的「复制 HTML + 引一次 tiaoyue-all.css」。⛔ 仍不许把「供给在位」读成「不必核对」:快照只反映抓取时刻,JS 运行时状态(轮播帧、滚动位置、动态 class)还原不了,那部分要在 3d 里声明为无来源。(本节 2026-10-08 的原表述「只到调色板级…不含源站的界面骨架」写在 component-library 采集之前,已过期作废。) | 3a 的 D1 |
| 定调与令牌 | 自带**样式套目录**(`<选定一套>/`,随段携带,现 1 套 `design-system-tiaoyue`;落点见 `references/manifest.md`) | `<技能根>/open-design/design-systems/<选定一套>/`(153 套) | 调性、全套令牌、组件形态。⚠️ **能力边界(2026-10-13 订正)**:自带样式库**不止调色板级** —— `design-system-tiaoyue/assets/component-library/`(采集于 2026-10-12)含 **9 个板块的整页快照**(`snapshot/<板块>.html`,去脚本、图片 URL 绝对化,7 个板块与线上逐像素一致率 ≥99.3%)、**345 个自有组件族**(`gallery.html` 是可视化总库,带搜索与一键复制 HTML)与**整站 CSS 原样合并**(`tiaoyue-all.css`,53 张 sheet / 1.11 MB)。⇒ **有目标产品整页快照就直接改快照当原型页**:打开对应板块的 snapshot(`snapshot/<板块>.html`)、改文案、删模块,立刻得到像素级同款的整页;要单个组件就走组件库的「复制 HTML + 引一次 tiaoyue-all.css」。⛔ 仍不许把「供给在位」读成「不必核对」:快照只反映抓取时刻,JS 运行时状态(轮播帧、滚动位置、动态 class)还原不了,那部分要在 3d 里声明为无来源。(本节 2026-10-08 的原表述「只到调色板级…不含源站的界面骨架」写在 component-library 采集之前,已过期作废。) | 3a 的 D1 |
| 版式骨架 | `references/layouts-tooling.md`(工具型页面用) | `<技能根>/open-design/design-templates/web-prototype/`(营销页向;工具型⛔ 不许套) | 工具型九档的名单与判读见 `references/layouts-tooling.md` §0(⛔ 本块不复述名单;2026-10-08 追加「导航栏」一档 = §4.9) | 3b |
| 工艺数值判据 | `references/execution-runbook.md` §3.4 + `references/layouts-tooling.md` §5.5 | `<技能根>/open-design/craft/`(13 份) | 字阶 / 配色 / 状态齐备 / 动效纪律 / 反 AI 味 / 无障碍。⚠️ **未选可选档时,后四项(反 AI 味 / 无障碍 / 动效纪律 / 表单校验)没有供给来源** ⇒ 必须在 3d 审查的留痕件(`产品原型/designs/3d-审查报告.md`,或用 `DESIGN.md` 的〈审查/核对〉节替代;两者都见 §3d「审计报告落盘」)里逐项显式记为**本段证据缺口**(写「无供给来源,本项未判」),⛔ 不许默不作声地跳过、也⛔ 不许拿别的判据冒充它过了 | 3a 判据、3d 终检 |
| 独立评审协议 | `oil-ui-pro` 的 `references/visual-review.md` + `references/tools.md` | 无 | 没看过制作过程的评审打分 + 截图取证 | 3d |
@@ -46,12 +46,12 @@ description: 产品规划第③段,界面交互。设计工程供给默认来
| 要什么 | 套内路径 |
|---|---|
| 入口(规范总纲、生成时的参考范围) | `assets/design-systems/design-system-tiaoyue/SKILL.md` |
| 入口(规范总纲、生成时的参考范围) | 样式套目录/`design-system-tiaoyue/SKILL.md` |
| 令牌(含暗色一套) | 同目录 `assets/tokens.css` + `assets/tokens-dark.css`(色值原始记录 `assets/brand.json`) |
| 组件形态 | 同目录 `references/components.md` + `references/design-system.html`(组件族清单 `references/library.json`) |
| 已知冲突 / 取舍记录 | 同目录 `references/known-conflicts.md` + `references/capture-notes.md` |
> ⚠️ 路径怎么找,分三类写:**技能级工具箱**(`assets/design-capture/`、`assets/element-picker/`、`assets/diagram-design/`、`assets/video-capture/`,相对**本技能包根**,各段共用);**本段自带资产**(`assets/design-systems/`,相对本段 `SKILL.md`);**同根技能**写 `<技能根>/<技能名>/`(`open-design` 用 `<技能根>/open-design/...`,`oil-ui-pro` 按技能名引用)。⛔ 不按绝对路径爬、⛔ 不写死 `../` 层数(位置一变就失效且不报错)。
> ⚠️ 路径怎么找,分三类写:**技能级工具箱**(`assets/design-capture/`、`assets/element-picker/`、`assets/diagram-design/`、`assets/video-capture/`,相对**本技能包根**,各段共用);**本段自带资产**(如**样式套目录**,相对本段 `SKILL.md`,落点见 `references/manifest.md`);**同根技能**写 `<技能根>/<技能名>/`(`open-design` 用 `<技能根>/open-design/...`,`oil-ui-pro` 按技能名引用)。⛔ 不按绝对路径爬、⛔ 不写死 `../` 层数(位置一变就失效且不报错)。
> 🔴 定调来源唯一:每次设计只选一套,默认与可选**不混用**(混用 = 没有调性)。套名与选择理由写进 `DESIGN.md`。
> ⛔ 判据正文仍只在本段(`references/execution-runbook.md` 与 `references/layouts-tooling.md`)。⚠️ 供给是分层的,分层定义见 §供给:`oil-ui-pro` 是**基础层**,本段自有判据是**叠加层**;同一条判据两处都有且口径不同时,⛔ 本段不许放宽基础层,只许更严、更具体,并在 runbook §3.4 标注「本项目追加」。
> ⛔ 不许把 `oil-ui-pro` 的评分循环当成 D 阶段的替代:它是评审方法,Gate-1/2/3 该过还得过。
@@ -122,9 +122,9 @@ description: 产品规划第③段,界面交互。设计工程供给默认来
> ⚠️ 3b 只取 §供给·版式骨架 的默认档。可选档那套(`web-prototype`)是营销落地页向,只在判「要不要套它的流程」时读。
> ⚠️ 本表只写「哪一步取哪个供给位」,⛔ 不写路径、不写默认判断 —— 那些只在 §供给 定义一次。
> 本段 `references/` 只放流程规则,⛔ 不放风格;随段携带的样式风格一律放 `assets/design-systems/<套>/`(🔴 2026-10-07 用户令两条:「把 `design-system-tiaoyue` 整合到 `product-planning` 作为一种样式风格」+「后续可以添加多种风格,每次只能选择一种进行设计」⇒ 只把「不放风格」这条放宽到 `assets/`,`references/` 仍不放)。
> ⚠️ `assets/design-systems/` 下的套**不入技能仓**(🔴 2026-10-16 用户令:`design-system-tiaoyue` 整个文件夹不提交,理由=体积 14 MB 且可 CDP 重采)—— 新机器取仓后这一层是空的,属正常;要用自带套就按本文件 §供给 的说明另取,或按套内 `references/capture-notes.md` 重采。
> ⚠️ 续加新风格时的硬要求:目录名与可选档同形(`assets/design-systems/<套名>/`),套内自带入口文件(`SKILL.md` 或 `DESIGN.md`);⛔ 不许改已入套的文件内容,原样搬入、只在本文件登记去向。
> 本段 `references/` 只放流程规则,⛔ 不放风格;随段携带的样式风格一律放**样式套目录**下的 `<套>/`(落点见 `references/manifest.md`)(🔴 2026-10-07 用户令两条:「把 `design-system-tiaoyue` 整合到 `product-planning` 作为一种样式风格」+「后续可以添加多种风格,每次只能选择一种进行设计」⇒ 只把「不放风格」这条放宽到承载资产的 `assets/`,`references/` 仍不放)。
> ⚠️ **样式套目录**下的套**不入技能仓**(🔴 2026-10-16 用户令:`design-system-tiaoyue` 整个文件夹不提交,理由=体积 14 MB 且可 CDP 重采)—— 新机器取仓后这一层是空的,属正常;要用自带套就按本文件 §供给 的说明另取,或按套内 `references/capture-notes.md` 重采。
> ⚠️ 续加新风格时的硬要求:目录名与可选档同形(`<套名>/`,落在**样式套目录**下),套内自带入口文件(`SKILL.md` 或 `DESIGN.md`);⛔ 不许改已入套的文件内容,原样搬入、只在本文件登记去向。
> 🔴 定调来源唯一(两档见 §供给·定调与令牌):每次设计只选一套,⛔ 不许混用两档(混用 = 没有调性)。套名与选择理由写进 `DESIGN.md`。
> 既有项目中凡引用旧风格来源(`minimalist-ui` / 皮肤族 / `ui-ux-pro-max` 检索结果)的 `DESIGN.md` 行,重跑 3a 时一律改写为选定套 + 留痕对照。
@@ -195,7 +195,7 @@ Gate-1 方向门(硬闸,不可跳)——按本表逐项自检(字段定
⚠️ 两份工具型文件的分工(2026-10-05 补):本技能 `references/layouts-tooling.md` 给可粘贴 HTML 骨架 + 逐条判据正文(§5.5),是默认档;选了可选档时,它那份 `tooling.md` 另给四形态判定句 + 通用红线纲要,只作指路。⛔ 判据只在 `layouts-tooling.md` 展开,`tooling.md` 只指路,两处展开必然漂。
形态判定一句话:页面上能数出「并列的同类记录」⇒ 列表型(工具型默认首选);数不出、页面围绕单个对象展开 ⇒ 详情型;主操作要在两个工作面之间来回 ⇒ 侧区型。
⛔ 不许拿营销页骨架顶替工具型,那是「没做形态判定」的默认行为,等于退回 18 轮前的病根。判不出形态就去改 D0 的 `结构骨架` 字段,⛔ 不许随手挑一个。
- ⭐ 先看供给里有没有**目标产品整页快照**(`assets/design-systems/<选定套>/assets/component-library/snapshot/<板块>.html`):有 ⇒ 复制快照当原型页,改文案、删模块;没有 ⇒ 才判页型挑骨架。⛔ 不许从零手写 `<section>`。用了可选档时,可选档那套的 `layouts.md` 开头的类清单(`section` / `container` / `card` / `btn-primary` / `grid-3` …)必须在种子 `template.html` 的 `<style>` 里存在;缺哪个就补进 `<style>`,⛔ 不许在标签上内联一堆临时样式。
- ⭐ 先看供给里有没有**目标产品整页快照**(**样式套目录**/`<选定套>/assets/component-library/snapshot/<板块>.html`,落点见 `references/manifest.md`):有 ⇒ 复制快照当原型页,改文案、删模块;没有 ⇒ 才判页型挑骨架。⛔ 不许从零手写 `<section>`。用了可选档时,可选档那套的 `layouts.md` 开头的类清单(`section` / `container` / `card` / `btn-primary` / `grid-3` …)必须在种子 `template.html` 的 `<style>` 里存在;缺哪个就补进 `<style>`,⛔ 不许在标签上内联一堆临时样式。
- 输出形态分两种,按当前项目的产出契约选:
· **往目标产品里加功能**(本项目=这种)⇒ 产出契约=**可移植目录**:目录内全部相对引用、双击可开、不依赖外部服务;明确允许引目标产品的整站样式表(如 `tiaoyue-all.css`)与同目录 `assets/`,整目录同层交付,⛔ 不许只交单个 html。
· **无目标产品、独立新做** ⇒ 单文件档:单文件 HTML、自包含、双击可开(与 `web-prototype` 的产出契约一致:只有 `index.html` 一个文件)。
@@ -35,7 +35,7 @@
> ⚠️ 2026-10-02 已重接必读源。原必读表指向 `ui-page-design/references/02_~05_*.md` 与 `vendor/*`,该目录实测已不在磁盘上(工作区与全局技能根两处都查过),照旧表走 D0 读不到任何一份必读文档。现改为引同根技能 `<技能根>/open-design/`与技能 `oil-ui-pro`(按技能名)。
> 改动原则:判据类全部交出去,只保留 craft 没有对应的东西。
开工前必读供给的位置与分工:默认档是 `oil-ui-pro`(入口 `SKILL.md`)+ 本段自带样式库 `assets/design-systems/`;选了可选档时,另读它的 `craft/README.md`(读一份就知道有哪些规约、怎么按需取)。定义与行名见本段 `SKILL.md` §供给。
开工前必读供给的位置与分工:默认档是 `oil-ui-pro`(入口 `SKILL.md`)+ 本段自带**样式套目录**(落点见 `references/manifest.md`);选了可选档时,另读它的 `craft/README.md`(读一份就知道有哪些规约、怎么按需取)。定义与行名见本段 `SKILL.md` §供给。
其余不许一次全读。同一时刻只读 1~2 份,用完释放;`design-systems/` 153 套、`craft/` 13 份全读必然互相稀释。
@@ -80,7 +80,7 @@
| 步 | 动作 | 验收判据 |
|---|---|---|
| −1 | 确认三处供给仍在位:`design-systems/`(并记录本项目选定套名)、`craft/`、`design-templates/web-prototype/` 都存在 | 三处都在位;`DESIGN.md` 记录的上次套名已找到 |
| −1 | 确认三处供给仍在位:**样式套目录**(`references/stage-delivery/assets/design-systems/`,并记录本项目选定套名)、`craft/`、`design-templates/web-prototype/` 都存在 | 三处都在位;`DESIGN.md` 记录的上次套名已找到 |
| −1b | 套名一致 → 直接进 D0 | 开工声明里写一句「选定套 `<套名>`,与项目记录一致」 |
| −1c | 套名不一致或供给缺失 → 停下报告,不许拿别的套顶上,也不许凭记忆写风格 | 产出供给变更清单(逐条:变了什么 → 本项目现状 → 要改什么),在会话里呈现并获用户确认后才改 `DESIGN.md` |
@@ -146,7 +146,7 @@
> 2. **版式结构**:选定套里记「外壳怎么分(侧栏/顶栏/主区)」「内容区用列表还是卡片网格」的那份。
> 3. **组件族**:选定套里列组件族与样张的那份。
>
> ⛔ **文件名随档而异,按该套目录里实际有的读**:本包默认档那套(`assets/design-systems/design-system-tiaoyue/`)
> ⛔ **文件名随档而异,按该套目录里实际有的读**:本包默认档那套(**样式套目录**/`design-system-tiaoyue/`)
> 给组件的是 `references/components.md`(组件族)+ `references/design-system.html`(样张),
> 它**没有** `components.html` —— 那是**可选档 153 套**的命名。
> 🔴 2026-10-08 实测事故:D1 步骤原写 `components.html` ⇒ 在默认档里**按名字找不到** ⇒ 那一次只取到了
@@ -112,7 +112,7 @@ description: 产品规划第②段,产品功能。只做一件事:把①段
本份专管界面骨架,⛔ 不写功能条目的有无与优先级(那是 `2a-产品功能.md`)。一句话分工:`2a` 管「做哪些事」,`2b` 管「这些事落在哪几页、每页长什么结构」。
🔴 **骨架怎么来(2026-10-16 补)**:现读**目标产品整页快照**再挑板块 —— 路径与③段同源:`references/stage-delivery/assets/design-systems/<选定套>/assets/component-library/snapshot/<板块>.html`。②段从快照的板块集里**挑**(挑哪几块、怎么排),③段按挑好的骨架**做**;板块名一律照抄快照,⛔ 不许自造(第 1 轮返工的病根就是自造 `nav-item` × 21)。本项目真正用到的快照是 `snapshot/script.html`:选不到对应板块时,写明用的是哪一份快照、为什么。
🔴 **骨架怎么来(2026-10-16 补)**:现读**目标产品整页快照**再挑板块 —— 路径与③段同源:③段**样式套目录**/`<选定套>/assets/component-library/snapshot/<板块>.html`(落点见 `references/manifest.md`)。②段从快照的板块集里**挑**(挑哪几块、怎么排),③段按挑好的骨架**做**;板块名一律照抄快照,⛔ 不许自造(第 1 轮返工的病根就是自造 `nav-item` × 21)。本项目真正用到的快照是 `snapshot/script.html`:选不到对应板块时,写明用的是哪一份快照、为什么。
| 问 | 回答 | 内容 |
|---|---|---|
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python
"""产品规划文档的「开头必须有图」机检(单一真源:SKILL.md §产出文档开头必须有图)。
用法:
python scripts/check_figures.py --ws <工作区或产品根>
判定三件事(任一不过即 FAIL):
① 每份落盘 md(`产品规划/` 下,排除 `归档-过期版/`)开头 8 行内有 `![…](…)`
② 引用的图文件在位(相对该 md 所在目录解析)
③ 同一张图没有被两份文档共用(一份文档一张自己的图)
输出:逐份一行;末尾给 PASS/FAIL 与计数。退出码 0=全过,1=有 FAIL。
"""
import argparse
import re
import sys
from pathlib import Path
SKIP_DIRS = {"归档-过期版", "node_modules", ".git"}
FIG_RE = re.compile(r"!\[[^\]]*\]\(([^)\s]+)")
def fmt(p: Path) -> str:
try:
return str(p.relative_to(p.anchor and Path(p.anchor) or Path(".")))
except Exception:
return str(p)
def resolve_product_root(ws: Path) -> Path | None:
"""认产品根:自身含 产品规划/ 即产品根;否则在 项目/<名>/ 或下一层里找唯一一个。"""
if (ws / "产品规划").is_dir():
return ws
cands = [d for d in (ws / "项目").glob("*") if (d / "产品规划").is_dir()] if (ws / "项目").is_dir() else []
if not cands:
cands = [d for d in ws.glob("*/产品规划") if d.is_dir()]
cands = [d.parent for d in cands]
if len(cands) == 1:
return cands[0]
if not cands:
return None
print("⚠️ 找到多个候选产品根,请直接传产品根路径:")
for c in cands:
print(" ", c)
return None
def head_lines(p: Path, n: int = 8) -> list[str]:
out = []
try:
with p.open(encoding="utf-8", errors="replace") as f:
for line in f:
if line.strip():
out.append(line.rstrip("\n"))
if len(out) >= n:
break
except Exception as e:
print(" 读取失败:%s" % e)
return out
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--ws", required=True, help="工作区根或产品根")
a = ap.parse_args()
root = resolve_product_root(Path(a.ws).resolve())
if root is None:
print("FAIL 认不出产品根:%s" % a.ws)
return 1
base = root / "产品规划"
print("产品根 = %s" % root)
docs = [p for p in base.rglob("*.md")
if not (set(p.parts) & SKIP_DIRS)
and not p.name.startswith(("索引", "本轮回报"))]
docs.sort()
if not docs:
print("FAIL 产品规划下没有 md 文档")
return 1
used: dict[str, list[Path]] = {}
fails = 0
for d in docs:
rel = d.relative_to(root)
hit = None
for line in head_lines(d):
m = FIG_RE.search(line)
if m:
hit = m.group(1)
break
if not hit:
print(" ❌ 无图 %s(开头 8 行没有 ![..](..))" % rel)
fails += 1
continue
target = (d.parent / hit).resolve()
if not target.exists():
print(" ❌ 图不在位 %s → %s" % (rel, hit))
fails += 1
continue
key = str(target).lower()
used.setdefault(key, []).append(rel)
print(" ✅ %s → %s" % (rel, hit))
shared = {k: v for k, v in used.items() if len(v) > 1}
for k, v in shared.items():
names = "、".join(str(x) for x in v)
print(" ❌ 图被共用 %s 被 %d 份文档共用(%s)" % (Path(k).name, len(v), names))
fails += len(v) - 1
print("—" * 40)
print("文档 %d 份,图 %d 张,问题 %d 处" % (len(docs), len(used), fails))
if fails:
print("FAIL")
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main())
+2 -2
View File
@@ -476,12 +476,12 @@ python .workbuddy/collab/collabd.py --set-life 进行中 --by "主会话"
**⑨ 我要优化技能本身**(用户说「**优化技能**」「**技能怎么改更好**」「**梳理技能**」「**梳理一下技能的要求和描述**」「**技能太长**」「**这条规则写在哪**」「**这个技能能不能拷到别的机器用**」——任一句即触发)⇒ 读 **`references/06-技能优化规程.md`**(2026-10-15 立)。
 **一句话判据**:**写技能时,假设读者是"另一台机器上的另一个会话"** —— 拿它回放一遍,毛病自己浮出来。
 **三层,按 B → C → D 这个顺序做**(🔴 本规程**只收通用要求**;`env/` 怎么放、路径怎么写、什么不入仓这类**某个技能包专有的配置规则不在其中**):
 · **B 结构层**(结构清晰·重点突出·无冲突·边界清晰)—— #1 单一真源 #2 注入面与真源同源(🔴 每轮注入的面**改文字必须量长度**)#3 命名一致 #4 正文不自相矛盾。
 · **B 结构层**(结构清晰·重点突出·无冲突·边界清晰)—— #1 单一真源 #2 注入面与真源同源(🔴 每轮注入的面**改文字必须量长度**)#3 命名一致 #4 正文不自相矛盾 #5 要求落在生成时看得见的面上 #6 资源路径只在资源清单写一次。
 · **C 流程层**(流程顺畅·功能正常)—— #5 步骤顺序正确 #6 编号连续 #7 无跳步 #8 步骤间职责不越界 #9 字段归属正确 #10 上下游不断裂 #11 引用路径正确 #12 字段不缺失 #13 产物链完整(**P0**)。
 · **D 表述层**(简洁·高效·实用·无歧义)—— #14 肯定表述(正本 ⇒ `references/rules.md §9`)#15 说人话 #16 一页纸。
 🔴 **优先级**:B 结构 > C 流程 > D 表述 —— 前两层出毛病**机制失效或断链**,表述只是**效果差**。
 🔴 **目标(用户 2026-10-15 原话)**:让技能描述**结构清晰·重点突出·简洁·高效·实用·无歧义·流程顺畅·功能正常·规则边界清晰·无冲突**。
 🔴 **文末附「技能优化描述 · 总表」17 条三层**;**验法按层不同**:B ⇒ 同源比对 md5/C ⇒ 画数据流看断点/D ⇒ 人读(⛔ 别假装它有自动结论)。
 🔴 **文末附「技能优化描述 · 总表」18 条三层**;**验法按层不同**:B ⇒ 同源比对 md5/C ⇒ 画数据流看断点/D ⇒ 人读(⛔ 别假装它有自动结论)。
---
@@ -39,6 +39,7 @@
| **判断「要不要做/怎么做/能不能先凑合」** | `04-决策方法论.md`(判据正文)+ `dsh-decision-method/`(U/A/X 三份素材库) | — |
| **写语气/去 AI 味** | `作业规矩/04-去AI味与说话方式.md`(**会话场景加固版**,含交付前清单与质量评分)+ `humanizer-en/SKILL.md`(**英文硬核版**:55 模式/5 语气档/0–100 打分) | — |
| **作业总规矩/工作区纪律/多棒接力** | `作业规矩/00-作业总规矩(原 agent-operating-rules).md` + `作业规矩/02-工作区纪律.md` + `作业规矩/03-多棒接力编排.md` | — |
| **包内有哪些资源、谁在用、在哪**(脚本 / 看板 / 资产 / 子规程 · 要改路径前先看) | 本文件 §四「包内资源索引」 | — |
> 📌 **上表三行 2026-10-10 补齐**:`04-决策方法论.md`、`dsh-decision-method/`、`作业规矩/`、`humanizer-en/` 原先只被正文引用、没进本表
> ⇒ 换会话时会被当成孤儿(本包 §二 规则 1 要求「新增文档必须在这张表里登记」)。
@@ -70,3 +71,46 @@
⚠️ **10-04 的教训**:一条红线立在对的地方(工作区日志 197 KB),仍然等于没立
⇒ **凡是「下次必须做到」的事,必须落在 `references/` 或 `SKILL.md`,⛔ 不能只写日志。**
判据:`selftest.py::t_no_invented_consequence` 量的就是这个(红线在动手层**第一段**)。
## 四、🔴 包内资源索引(有哪些资源 · 谁在用 · 在哪)
> **这一节管什么**(2026-10-16 新增,用户原话:「产品规划 和 会话技能 都检查如何解决 资源索引的问题,避免资源到处引用」):
> 回答「**某个资源被谁引用、引用点在哪**」,⛔ 不必靠 `grep` 全包找。
> **盘面分工(三层,⛔ 别混)**:`05-技能库盘点规程.md` 管**库层面**(技能根下有哪些技能、该不该入仓)|
> `06-技能优化规程.md` 管**写法**(描述 / 结构 / 流程怎么写,其 §B #6 就是本节的规矩)|**本节管包内资源索引**。
>
> 🔴 **本节为什么在这里、不在 `manifest.md`**:`references/manifest.md` 是 `install.py --manifest` 在本机生成的
> 逐文件 md5 表,**被仓根 `.gitignore` 挡着(不入库)** ⇒ 索引写那里=别台机器取仓后**看不到**,机制静默失效。
> 本节放在**入库**的这份索引里;`product-planning` 没有同类入库载体,它的索引新建成 `references/manifest.md`。
>
> **机检**:`python scripts/check_resource_index.py --pkg "<本包根>" --index references/01-文档索引.md`
> (同一份实现也给 `product-planning` 用:`--pkg "<技能根>/product-planning"`,那边用默认 `--index`)。
> 四个闸门:落点是否存在 / 登记的引用点是否为真 / `口径=只写一次` 的资源有没有人绕开清单在别处抄路径 /
> 有没有登记了却没人用的资源。
>
> **`口径` 列**:`只写一次` = 该资源路径字面量只许出现在本清单与 `许可字面量` 列登记的文件里;
> `可多次` = 只登记「谁在用、在哪」,不限字面量。🔴 **本包资源一律 `可多次`** —— 这里的资源是**代码与规程**,
> 引用必须精确到文件(写个模糊名字反而没法用);「只写一次」那道闸门是给**资产 / 样式套**这类资源用的,
> 目前 `product-planning` 的「样式套目录」在用。
>
> 🔴 `被谁引用` 与 `许可字面量` 是两件事:前者「谁在用」(可由 `--sync-refs` 自动回写,超过 12 个只记个数);
> 后者「谁被允许写路径字面量」(人工开的口子 —— ⛔ 自动回写会把它冲成「谁都许可」,闸门就空了)。
| 资源 | 落点 | 干什么 | 被谁引用 | 许可字面量 | 口径 | 入仓 |
|---|---|---|---|---|---|---|
| 机制脚本 | `scripts/` | 常驻与机制脚本(`collabd.py` / `collabctl.py` / `goalctl.py` / `supervise-launch.py` / `guard.py` / `deploy_code.py` 等) | SKILL.md、boards/会话看板/board-launch.py、boards/会话看板/board-launch.py.tpl、boards/会话看板/board.py、boards/项目看板/project-board.py、install.py、references/00-动手前必过.md、references/02-功能优先协作协议.md、references/03-回复排版-核心块.md、references/04-决策方法论.md、references/06-技能优化规程.md、references/architecture.md(共 44 个) | — | 可多次 | 是 |
| 宿主钩子 | `scripts/hooks/` | 挂在宿主事件上的钩子脚本 | SKILL.md、boards/会话看板/board.html、boards/会话看板/board.py、boards/会话看板/board_ext.py、install.py、references/03-回复排版-核心块.md、references/99-速查清单.md、references/architecture.md、references/collab-detail.md、references/collab.md、references/deploy.md、references/humanizer-en/SKILL.md(共 32 个) | — | 可多次 | 是 |
| 锁机制 | `scripts/lock/` | 锁原语与释放工具 | SKILL.md、boards/会话看板/board-render-probe.js、boards/会话看板/board.html、boards/会话看板/board.py、boards/会话看板/tmp/supervise-inbox/board.json、boards/项目看板/project-board.html、boards/项目看板/project-board.py、install.py、references/architecture.md、references/collab-detail.md、references/collab.md、references/deploy.md(共 38 个) | — | 可多次 | 是 |
| 取证工具 | `scripts/forensics/` | 取证 / 诊断工具 | SKILL.md、references/architecture.md、references/collab.md、references/forensics.md、references/manifest.md、references/pitfalls.md、references/作业规矩/00-作业总规矩(原 agent-operating-rules).md、scripts/hooks/wb-result-hook.py | — | 可多次 | 是 |
| 资源索引机检 | `scripts/check_resource_index.py` | 本节与 `product-planning` 资源表的双向对账(两份技能共用这一份实现) | references/06-技能优化规程.md、references/manifest.md、scripts/check_resource_index.py | — | 可多次 | 是 |
| 会话看板 | `boards/会话看板/` | 会话架构看板(端口 20099);页面与脚本同处一包 | SKILL.md、boards/会话看板/board-launch.py、boards/会话看板/board.py、boards/会话看板/board_ext.py、boards/会话看板/tmp/supervise-inbox/board.json、boards/项目看板/project-board.py、references/architecture.md、references/collab-detail.md、references/deploy.md、references/manifest.md、references/pitfalls.md、scripts/collabctl.py(共 14 个) | — | 可多次 | 是 |
| 项目看板 | `boards/项目看板/` | 项目+任务看板(端口 20100);页面与脚本同处一包 | SKILL.md、boards/项目看板/project-board-launch.py、boards/项目看板/project-board.html、boards/项目看板/project-board.py、references/manifest.md | — | 可多次 | 是 |
| 作业规矩 | `references/作业规矩/` | 作业总规矩等写法类规程 | SKILL.md、references/02-功能优先协作协议.md、references/03-回复排版-核心块.md、references/architecture.md、references/collab-detail.md、references/manifest.md、references/rules.md、scripts/check_resource_index.py、scripts/hooks/reply-style-guard.py、scripts/hooks/skill-load-guard.py、scripts/selftest.py | — | 可多次 | 是 |
| 决策方法论 | `references/dsh-decision-method/` | `04-决策方法论.md` 背后的素材库(U / A / X 条目) | references/02-功能优先协作协议.md、references/04-决策方法论.md、references/dsh-decision-method/素材库-A-AI推理.md、references/dsh-decision-method/素材库-U-用户决策.md、references/dsh-decision-method/素材库-反例-X.md、references/manifest.md、references/pitfalls.md、scripts/hooks/decision-rules-hook.py、scripts/hooks/skill-load-guard.py、scripts/session-rules-check.py | — | 可多次 | 是 |
| 英文文案判据 | `references/humanizer-en/` | 英文版文案判据(中文版在 `humanizer` 技能) | SKILL.md、references/collab-detail.md、references/manifest.md、references/作业规矩/04-去AI味与说话方式.md、scripts/selftest.py | — | 可多次 | 是 |
| 输出阶梯 | `references/karpathy-output-ladder/` | 输出阶梯判据 | references/karpathy-output-ladder/SKILL.md、references/manifest.md | — | 可多次 | 是 |
| 规程与背景文档 | `references/` | 顶层 `*.md`:`00`–`06` 规程 + `99-速查清单` + 本文件 + `architecture` / `rules` / `pitfalls` / `taskgraph` / `collab` / `collab-detail` / `deploy` / `forensics` / `supervise-persistence` | SKILL.md、boards/会话看板/board-launch.py.tpl、boards/会话看板/board_ext.py、install.py、references/02-功能优先协作协议.md、references/03-回复排版-核心块.md、references/04-决策方法论.md、references/05-技能库盘点规程.md、references/06-技能优化规程.md、references/99-速查清单.md、references/architecture.md、references/collab-detail.md(共 40 个) | — | 可多次 | 是 |
| 留痕 | `references/_留痕/` | 改动前快照(append-only,⛔ 不进机检扫描范围) | SKILL.md、boards/会话看板/board-launch.py、boards/会话看板/board.html、boards/会话看板/board.py、boards/会话看板/board_ext.py、boards/项目看板/project-board.py、install.py、references/02-功能优先协作协议.md、references/architecture.md、references/collab.md、references/dsh-decision-method/素材库-A-AI推理.md、references/manifest.md(共 31 个) | — | 可多次 | 是 |
| 设计令牌 | `assets/design-tokens.css` | 包内静态样式令牌(看板页面用) | SKILL.md、references/manifest.md | — | 可多次 | 是 |
| 装机脚本 | `install.py` | 一键配置;`--manifest` 重算 `references/manifest.md` 那张逐文件表 | SKILL.md、boards/会话看板/board.py、boards/会话看板/board_ext.py、boards/项目看板/project-board.py、install.py、references/06-技能优化规程.md、references/deploy.md、references/manifest.md、references/pitfalls.md、references/rules.md、scripts/check_resource_index.py、scripts/collabctl.py(共 29 个) | — | 可多次 | 是 |
| 本机配置 | `env/roots.env` | 每台机器一份的工作区 roots(`*.example` 入仓,实体不入仓) | SKILL.md、boards/会话看板/board-launch.py、boards/会话看板/board-launch.py.tpl、boards/会话看板/board.py、boards/会话看板/board_ext.py、boards/项目看板/project-board-launch.py、boards/项目看板/project-board.py、install.py、references/deploy.md、references/manifest.md、references/pitfalls.md、references/rules.md(共 34 个) | — | 可多次 | 否 |
@@ -47,6 +47,21 @@
⚠️ 只写进 `references/` 等着回头查 ⇒ 等于没立;机核脚本只作**兜底**,⛔ 不当主手段。
(用户 2026-10-16 令:「要生成的时候 就按照要求,不是事后再修复」。)
**6 资源路径只在资源清单写一次**
单包资源索引。本包有什么资源(脚本 / 看板 / 资产 / 子规程 / 同根技能依赖)、落在哪、被谁引用,**唯一登记处**是该包里
**入库的那份索引文件**(本包=`references/01-文档索引.md §四`;`product-planning`=`references/manifest.md`);
别的文字里**只写资源名**,⛔ 不把路径抄进散文、段文档、脚本注释与模板。
🔴 **索引文件必须放入库的那份** —— 实测踩过:本包 `references/manifest.md` 被仓根 `.gitignore` 挡着
(`install.py --manifest` 在本机生成、随机器变)⇒ 索引写那里=别台机器取仓后看不到,机制静默失效。
判据:**改一次目录,要改几个文件** —— 一处以上 ⇒ 那是照抄,不是引用。
机检:`python scripts/check_resource_index.py --pkg "<本包根>" --index references/01-文档索引.md`
(`product-planning` 用默认 `--index`)—— 四项(落点是否存在 / 登记的引用点是否为真 /
有没有人绕开清单在别处抄路径 / 有没有登记了却没人用的资源);⛔ 有 🔴 不算改完,不静默放过。
⚠️ 何时把资源设成 `只写一次`:**资产 / 样式套**这类「换个落点也不影响怎么用」的(现例:`product-planning` 的样式套目录);
**代码与规程**设成 `可多次` —— 引用必须精确到文件,写模糊名字反而没法用。
(用户 2026-10-16 原话:「为什么这么多地方引用 一个样式库,是不是技能中没有统一索引机制」+
「产品规划 和 会话技能 都检查如何解决 资源索引的问题,避免资源到处引用」。)
### C 流程层(→ 流程顺畅 · 功能正常)
> 来路:2026-08-10 另一工作区对创作流程做全量审计归纳的 13 项里,9 项属这一层;
@@ -86,10 +101,10 @@
---
## 技能优化描述 · 总表(17 条,三层)
## 技能优化描述 · 总表(18 条,三层)
> **怎么来的**:① B/C/D 三层判据(=本规程正文);② 另一工作区的 `技能审计检查清单.md`(`E:/ProgramData/AIProject/mcn-short-video/技能审计检查清单.md`,2026-08-10 创作流程全量审计的 13 项;跨根引用,仅作来路说明)。
> **用法**:按 **1 → 17** 逐条过。**分级**:**P0 = 会让人出事或断链**(#13 产物链)/**P1 = 不一致或冗余**(B 层 + C 层其余)/**P2 = 效果差**(D 层)。
> **用法**:按 **1 → 18** 逐条过。**分级**:**P0 = 会让人出事或断链**(#13 产物链)/**P1 = 不一致或冗余**(B 层 + C 层其余)/**P2 = 效果差**(D 层)。
> 🔴 **判据正本 = 本规程正文的对应项**,表里只给锚点与反例;⛔ **本表只收通用要求** —— `env/` 怎么放、
> 路径怎么写、什么不入仓这类**专有配置规则不在本表**,见 `01-文档索引.md` 的「技能环境配置规范」。
@@ -112,3 +127,4 @@
| 15 | D 表述 | 说人话 | 见 §D #15 | 四种 AI 味:三段式排比连用/"…着"结尾的肤浅分析/模糊归因/拆成加粗小标题+竖排碎句 |
| 16 | D 表述 | 一页纸 | 见 §D #16 | 一份 300 行的"速查" |
| 17 | B 结构 | 要求落在生成时看得见的面上 | 见 §B #5 | 只写进 `references/` 等着回头查 ⇒ 等于没立 |
| 18 | B 结构 | 资源路径只在资源清单写一次 | 见 §B #6 | 同一资源路径抄在 6 个文件 20 处,改目录漏改不报错 |
@@ -0,0 +1,320 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""资源索引 · 双向对账机检(技能包内通用)
用途
----
每个技能包在 `references/manifest.md` 里维护一张「资源索引」表:
| 资源 | 落点 | 干什么 | 被谁引用 | 许可字面量 | 口径 | 入仓 |
本脚本对这张表做**双向对账**,四个闸门:
G1 落点存在 —— 每行 `落点` 必须在磁盘上(`入仓=否` 的行本机缺只给 ⚠,属正常)
G2 引用为真 —— `被谁引用` 里登记的每个文件必须存在、且真的提到该资源(⛔ 不许登记假引用点;
`许可字面量` 指向的文件也必须存在,⛔ 不许开空头许可)
G3 字面量收敛 —— `口径=只写一次` 的行:该资源的路径字面量只许出现在清单本身与 `许可字面量` 登记的文件里,
出现在别处 ⇒ 🔴。这条就是「避免资源到处引用」的机检(⛔ 漏一处必报,不静默放行)
G4 孤儿 / 未登记 —— `被谁引用` 为空的行 ⇒ 🔴;`assets/` 与 `references/stage-*/assets/` 下
磁盘上有一级子项而表里没有 ⇒ 🔴
🔴 `被谁引用` 与 `许可字面量` 是**两件事,别合并**:前者是「谁在用」(可由 `--sync-refs` 自动回写),
后者是「谁被允许写路径字面量」(人工开的口子,⛔ 自动回写会把它冲成"谁都许可"⇒ G3 变空闸门)。
「被谁引用」怎么认:既认**路径形态**(含该资源路径尾两段),也认**名字形态**(含资源名或路径末段)
—— 因为收敛之后散文里就只剩名字了,只认路径会把收敛好的资源误判成孤儿。
用法
----
python check_resource_index.py --pkg <技能包根>
python check_resource_index.py --pkg <技能包根> --index references/01-文档索引.md
python check_resource_index.py --pkg <技能包根> --sync-refs # 用扫描结果回写「被谁引用」列
🔴 索引文件必须放在该技能包**入库(git 跟踪)**的那份文件里 —— 实测踩过:
`session-mechanism/references/manifest.md` 被 `.gitignore` 挡着(它是 `install.py --manifest`
在本机生成、随机器变)⇒ 索引写那里=别台机器取仓后**看不到**,机制静默失效。
该包的索引改放 `references/01-文档索引.md`(入库),用 `--index` 指过去。
退出码:0 全过 | 1 有 🔴 | 2 用法或清单结构错(找不到表头 ⇒ 停手,⛔ 不猜)
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
try: # 出口一律声明编码(本机铁律)
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
HEADER = "| 资源 |"
COLS = ["资源", "落点", "干什么", "被谁引用", "许可字面量", "口径", "入仓"]
COLLAPSE = "只写一次" # 口径取值:只写一次 / 可多次
SYNC_COL = 3 # 「被谁引用」在第几列(0 起)
SYNC_CAP = 12 # 回写「被谁引用」时最多列几个文件,其余只记个数
TEXT_EXT = {".md", ".py", ".js", ".mjs", ".cjs", ".json", ".html", ".htm",
".css", ".sh", ".bash", ".tpl", ".txt", ".yaml", ".yml"}
SKIP_DIR = {"_留痕", "__pycache__", "logs", "node_modules", ".git", "env",
".obsidian", "marks"}
SKIP_FILE = {"install.log"}
def is_scannable(p: Path, pkg: Path) -> bool:
rel = p.relative_to(pkg)
if any(part in SKIP_DIR for part in rel.parts):
return False
if p.name in SKIP_FILE or p.suffix not in TEXT_EXT:
return False
return True
def scan_files(pkg: Path) -> list[Path]:
out = [p for p in pkg.rglob("*") if p.is_file() and is_scannable(p, pkg)]
return sorted(out, key=lambda q: q.relative_to(pkg).as_posix())
def read_text(p: Path) -> str:
try:
return p.read_text(encoding="utf-8", errors="replace")
except Exception:
return ""
def cell(s: str) -> str:
return s.strip().strip("`").strip()
def split_refs(raw: str) -> list[str]:
"""`被谁引用` / `许可字面量` 列 → 条目清单(按 `、;;,` 分隔)。
🔴 原样保留条目文本,⛔ 不在这一步剥括号 —— 真实文件名里可能就带全角括号
(实测踩过:`references/作业规矩/00-作业总规矩(原 agent-operating-rules).md` 被剥成
`…/00-作业总规矩.md` ⇒ 不存在 ⇒ 报假 🔴)。括号交给 `resolve()` 按「原样 → 去括号」两候选试。
"""
if not raw or raw.strip() in ("—", "-", "(无)", "(无)"):
return []
out: list[str] = []
for it in re.split(r"[;;、,,]", raw):
s = cell(it)
if s:
out.append(s)
return out
def resolve(pkg: Path, item: str) -> Path | None:
"""把一条登记项解析成真实路径:先按原样,再按「剥掉括号说明」的样子各试一次。"""
if item.startswith("<技能根>/"):
cands = [same_root_dir(pkg, item)]
else:
cands = [pkg / item, pkg / re.sub(r"([^)]*)", "", item).strip(),
pkg / re.sub(r"\([^)]*\)", "", item).strip()]
for c in cands:
if c.exists():
return c
return None
class Row:
def __init__(self, raw: str):
c = raw.strip().strip("|").split("|")
c += [""] * (len(COLS) - len(c))
self.name = cell(c[0])
self.loc = cell(c[1])
self.what = cell(c[2])
self.refs_raw = c[3].strip()
self.allow_raw = c[4].strip() # 许可字面量:允许写该资源路径字面量的文件(G3 用)
self.mode = cell(c[5])
self.inrepo = cell(c[6])
segs = [s for s in self.loc.replace("<技能根>/", "").split("/") if s]
self.leaf = segs[-1] if segs else self.loc # 路径末段
self.key = "/".join(segs[-2:]) if len(segs) >= 2 else self.leaf # 路径尾两段
if self.loc.startswith("<技能根>/") and len(segs) == 1:
self.key = segs[0]
def load_table(man: Path) -> tuple[list[Row], int]:
lines = man.read_text(encoding="utf-8").replace("\r\n", "\n").split("\n")
head = next((i for i, ln in enumerate(lines) if ln.startswith(HEADER)), None)
if head is None:
raise SystemExit(f"🔴 {man} 里找不到 `{HEADER}` 表头 ⇒ 停手(⛔ 不猜、不追加)")
rows: list[Row] = []
i = head + 2
while i < len(lines) and lines[i].startswith("|"):
if cell(lines[i].strip().strip("|").split("|")[0]):
rows.append(Row(lines[i]))
i += 1
if not rows:
raise SystemExit(f"🔴 {man} 的表里没有任何数据行 ⇒ 停手")
return rows, head
def path_hits(r: Row, pkg: Path, files: list[Path], man: Path) -> list[tuple[Path, int, str]]:
"""G3 用:找**路径形态**(含资源路径尾两段字面量)的出现。"""
pat = re.compile(re.escape(r.key))
out: list[tuple[Path, int, str]] = []
for f in files:
if f == man:
continue
for n, ln in enumerate(read_text(f).split("\n"), 1):
if r.key in ln and pat.search(ln):
out.append((f, n, ln.strip()[:120]))
return out
def ref_hits(r: Row, pkg: Path, files: list[Path], man: Path) -> list[str]:
"""「被谁引用」用:路径形态 或 名字形态(资源名 / 路径末段)都算。"""
toks = {t for t in (r.key, r.leaf, r.name) if t}
hits: list[str] = []
for f in files:
if f == man:
continue
txt = read_text(f)
if any(t in txt for t in toks):
hits.append(f.relative_to(pkg).as_posix())
return hits
def same_root_dir(pkg: Path, loc: str) -> Path:
name = loc.replace("<技能根>/", "").strip("/").split("/")[0]
return pkg.parent / name
def main() -> int:
ap = argparse.ArgumentParser(description="资源索引 · 双向对账机检")
ap.add_argument("--pkg", required=True, help="技能包根(如 E:/.../skills/product-planning)")
ap.add_argument("--index", default="references/manifest.md",
help="资源索引表所在文件(默认 references/manifest.md;"
"某包的清单若不入库,就把索引放进该包**入库**的那份索引文件,再指过来)")
ap.add_argument("--sync-refs", action="store_true",
help="用扫描结果回写「被谁引用」列(逗号分隔的文件清单)")
ap.add_argument("--max-list", type=int, default=10, help="每个闸门最多列几条明细")
args = ap.parse_args()
pkg = Path(args.pkg).expanduser().resolve()
if not pkg.is_dir():
print(f"🔴 技能包根不存在:{pkg}")
return 2
man = pkg / args.index
if not man.is_file():
print(f"🔴 找不到资源索引文件 {man}(用 --index 指定;默认 references/manifest.md)")
return 2
rows, head = load_table(man)
files = scan_files(pkg)
print(f"═══ 资源索引对账 · {pkg.name} ═══")
print(f"清单:{man.relative_to(pkg).as_posix()} | 表行:{len(rows)} | 扫描文件:{len(files)}\n")
g1: list[str] = []
g2: list[str] = []
g3: list[str] = []
g4: list[str] = []
computed: dict[str, list[str]] = {}
for r in rows:
# ── G1 落点存在 ────────────────────────────────────────────────
if r.loc.startswith("<技能根>/"):
cand = same_root_dir(pkg, r.loc)
if not cand.is_dir() and r.inrepo != "否":
g1.append(f"「{r.name}」落点(同根技能)不存在:{cand}")
else:
cand = pkg / r.loc.rstrip("/")
if not cand.exists():
if r.inrepo == "否":
print(f" ⚠ 「{r.name}」落点本机缺(入仓=否 ⇒ 正常,新机器取仓后这一层为空):{r.loc}")
else:
g1.append(f"「{r.name}」落点不存在:{r.loc}")
# ── 引用扫描(G2 / G3 / --sync-refs 共用)──────────────────────
computed[r.name] = ref_hits(r, pkg, files, man)
declared = split_refs(r.refs_raw)
# ── G2 引用为真 ────────────────────────────────────────────────
for d in declared:
dp = resolve(pkg, d)
if dp is None:
g2.append(f"「{r.name}」登记的引用点不存在:{d}")
elif not dp.is_file():
pass # 同根技能目录:只判存在
elif r.name not in read_text(dp) and r.leaf not in read_text(dp) \
and r.key not in read_text(dp):
g2.append(f"「{r.name}」登记的引用点其实没提到它:{d}")
# ── G3 字面量收敛(仅 `只写一次`)──────────────────────────────
if r.mode == COLLAPSE:
allow: set[str] = set()
for d in split_refs(r.allow_raw):
if resolve(pkg, d) is None:
g2.append(f"「{r.name}」`许可字面量` 指向的文件不存在:{d}") # ⛔ 不许开空头许可
allow.add(d.replace("\\", "/"))
for f, n, t in path_hits(r, pkg, files, man):
rel = f.relative_to(pkg).as_posix()
if rel in allow:
continue
g3.append(f"「{r.name}」字面量出现在未登记处:{rel}:{n} ⇒ {t}")
# ── G4 另一半:孤儿登记 + 磁盘上有而表里没有 ────────────────────
for r in rows:
if not split_refs(r.refs_raw):
g4.append(f"「{r.name}」`被谁引用` 为空 ⇒ 没人引用的登记项")
registered = {r.loc.rstrip("/") for r in rows}
for d in [pkg / "assets"] + sorted(pkg.glob("references/stage-*/assets")):
if not d.is_dir():
continue
for sub in sorted(d.iterdir()):
if sub.name.startswith(".") or sub.name.startswith("_"):
continue
rel = sub.relative_to(pkg).as_posix().rstrip("/")
if not any(rl.endswith(rel) or rel.endswith(rl) for rl in registered):
g4.append(f"磁盘上有、清单里没有:{rel}")
# ── 汇总 ────────────────────────────────────────────────────────────
gates = [("G1 落点存在", g1), ("G2 引用为真", g2),
("G3 字面量收敛", g3), ("G4 孤儿 / 未登记", g4)]
bad = 0
for title, items in gates:
if items:
bad += 1
print(f"🔴 {title}:{len(items)} 处")
for x in items[: args.max_list]:
print(f" · {x}")
if len(items) > args.max_list:
print(f" … 余 {len(items) - args.max_list} 处")
else:
print(f"✅ {title}:0")
if args.sync_refs:
raw = man.read_bytes()
crlf = b"\r\n" in raw
lines = raw.decode("utf-8").replace("\r\n", "\n").split("\n")
for i in range(head + 2, len(lines)):
if not lines[i].startswith("|"):
break
c = lines[i].strip().strip("|").split("|")
if len(c) < len(COLS) or not cell(c[0]):
continue
lst = computed.get(cell(c[0]), [])
shown = lst[:SYNC_CAP]
txt = "、".join(shown)
if len(lst) > SYNC_CAP:
txt += f"(共 {len(lst)} 个)" # 括号里的计数会被 split_refs 剥掉,不影响 G2 校验
c[3] = " " + (txt if txt else "—") + " "
lines[i] = "|" + "|".join(c) + "|"
out = "\n".join(lines)
man.write_bytes((out.replace("\n", "\r\n") if crlf else out).encode("utf-8"))
print(f"\n✍️ 已用扫描结果回写「被谁引用」列:{man.name}")
print()
if bad:
print(f"结论:🔴 不通过 —— {bad} 个闸门有命中(⛔ 漏一处必报,不静默放行)")
return 1
print("结论:✅ 通过 —— 四个闸门全 0")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+34 -19
View File
@@ -7920,14 +7920,16 @@ def t_term_table_single_source():
return rows
@case("🔴 06 的 16 条条目名:SKILL.md §0 ⑨ 与 06 正文/总表三方逐条同名(⛔ 写成简写=对不上)")
@case("🔴 06 条目名:SKILL.md §0 ⑨ 与 06 正文/总表三方逐条同名(⛔ 写成简写=对不上)")
def t_skill06_entries_same_source():
"""🔴 2026-10-15 立(当日实测踩到)。
"""🔴 2026-10-15 立(当日实测踩到);2026-10-16 改比对键。
`SKILL.md §0 ⑨` 是**匹配面**(点开技能就读到),`06` 正文与总表是**真源**。
实测:§0 把 C 层写成简写「职责不越界」,而正文是「步骤间职责不越界」——
两边都不报错,读者按简写去正文里搜就搜不到。
⇒ 判据落在**16 条逐条同名**上(数字 + 名字都要对上)。
⇒ 判据落在**条目名逐条同名**上。
🔴 条数**以 06 总表为准**(⛔ 不再硬编码),所以 06 增删条目时只改 06 与 §0 ⑨,
⛔ 不必回头改闸门 —— 硬编码过「应 16」,2026-10-16 实测因此漏过一整条(B#5)。
"""
import re as _re
SK = (HERE.parent / "SKILL.md").read_text(encoding="utf-8", errors="replace")
@@ -7937,28 +7939,41 @@ def t_skill06_entries_same_source():
i = SK.find("**⑨ 我要优化技能本身**")
j = SK.find("\n\n", i) if i >= 0 else -1
blk = SK[i:j] if i >= 0 and j > i else ""
a = {int(n): nm for n, nm in _re.findall(r"#(\d{1,2}) ([^(\s]+)", blk)}
b = {int(n): nm for n, nm in _re.findall(r"^(?:- )?\*\*(\d{1,2}) ([^(\s]+)\*\*", P6, _re.M)}
c = {int(n): nm for n, nm in _re.findall(
r"^\| (\d{1,2}) \| [A-D] [^|]*\| ([^|\s]+) \|", P6, _re.M)}
# 🔴 2026-10-16 改:**按条目名比,不按编号比**。
# 原写法把三处读成 `{编号: 名字}` 再逐号比对 ⇒ 编号一撞就静默丢条目:
# B 层与 C 层都用 `#5`(B#5 要求落在生成时看得见的面上/C#5 步骤顺序正确),
# 字典后写覆盖先写 ⇒ §0 ⑨ 与 06 正文都**看不到 B#5**,而 06 总表把它编成 17
# ⇒ 报成「首处不一致 #17」,真正缺的那一条反而不点名(2026-10-16 实测)。
# 名字才是判据的本意(原注释:读者按简写去正文里搜就搜不到),编号只是导航。
# 顺带把「应 16」这个硬编码去掉 ⇒ 以 **06 总表为准**,条数增减不必再改闸门。
a = [nm for _n, nm in _re.findall(r"#(\d{1,2}) ([^(\s]+)", blk)]
b = [nm for _n, nm in _re.findall(r"^(?:- )?\*\*(\d{1,2}) ([^(\s]+)\*\*", P6, _re.M)]
c = [nm for _n, nm in _re.findall(
r"^\| (\d{1,2}) \| [A-D] [^|]*\| ([^|\s]+) \|", P6, _re.M)]
# 🔴 句末标点不算差异(§0 是句子、正文是标题、「配置外置 /」后面跟分隔符)——
# 2026-10-15 实测:不清洗时 #5 会因「入仓按白名单。」多一个句号而报红,那是**假红**。
def _clean(d):
return {k: _re.sub(r"[。.,,、;;//]+$", "", v) for k, v in d.items()}
def _names(lst):
out = []
for v in lst:
v = _re.sub(r"[。.,,、;;//]+$", "", v)
if v and v not in out:
out.append(v)
return out
a, b, c = _clean(a), _clean(b), _clean(c)
A, B, C = _names(a), _names(b), _names(c)
rows = [("§0 ⑨ 读到条目名 %d 条(应 16)" % len(a), len(a) == 16),
("06 正文读到条目名 %d 条(应 16)" % len(b), len(b) == 16),
("06 总表读到条目名 %d 条(应 16)" % len(c), len(c) == 16)]
for tag, other in (("06 正文", b), ("06 总表", c)):
bad = sorted(k for k in set(a) | set(other) if a.get(k) != other.get(k))
rows = [("§0 ⑨ 读到条目名 %d 条(应 %d = 06 总表)" % (len(A), len(C)), len(A) == len(C)),
("06 正文读到条目名 %d 条(应 %d = 06 总表)" % (len(B), len(C)), len(B) == len(C))]
for tag, other in (("06 正文", B), ("06 总表", C)):
missing = [x for x in other if x not in A]
extra = [x for x in A if x not in other]
detail = ""
if bad:
k0 = bad[0]
detail = "(⛔ 首处不一致 #%d:§0=%r/%s=%r)" % (k0, a.get(k0), tag, other.get(k0))
rows.append(("§0 ⑨ 与 %s **16 条逐条同名**%s" % (tag, detail), not bad))
if missing:
detail = "(⛔ §0 ⑨ 缺:%s)" % "、".join(missing[:3])
elif extra:
detail = "(⛔ §0 ⑨ 多出:%s)" % "、".join(extra[:3])
rows.append(("§0 ⑨ 与 %s **逐条同名**%s" % (tag, detail), not missing and not extra))
return rows