Commit Graph

45 Commits

Author SHA1 Message Date
wandering1 261bd9fd31 fix(keil-build): 定性并挡住 %SystemDrive% 字面量垃圾目录(并发会话在飞工作)
@build 与 fontTools 类脚本在残缺环境里起子进程时(run_code 的 Node
进程 process.env 为空),Windows known-folder 的**可展开串**展开不了
→ 变成相对路径,于是仓库根长出 %SystemDrive%/ProgramData/Microsoft/
/Windows/Caches/(约 1 MB 缓存的字面量复刻),它不是任何人在飞的改动。

- SKILL.md:新增「构建副产物」一节——症状 / 根因(三条现场证据)/
  清理(只用 bash 引号形态,明令禁止 rd /s /q %SystemDrive%,cmd 会
  把它展开成 C: 等于对系统盘下令递归强删)/ 两层防复发(.gitignore
  已入库 + 脚本守卫),并在坑表加一条速查
- keil_build.py +115 行:拉起 UV4 前 harden_windows_env() 回填缺失的
  SystemDrive/SystemRoot/ProgramData/USERPROFILE/TMP 等并就地展开残留
  的 %VAR% 字面量,命中打 WARN: ENV_SPARSE;构建后
  check_percent_literal_junk() 见到字面量目录只打 WARN: JUNK_DIR 并
  给出安全删除命令(守卫是兜底不是许可:@build 仍须走继承完整环境
  的 shell,删目录也不由构建脚本代劳)

py_compile 通过、--help 正常。本笔是并行会话在飞的改动,按用户
「把所有内容都 commit」的指示代为落盘;要退回:git reset --soft HEAD~1。
2026-10-28 22:22:59 +08:00
wandering1 511fd30cc6 docs: 清理全仓变更记录流水(43 篇整节删 / 23 篇留一行 / 5 篇保留)
用户裁定:文档只写现状,历史流水一律删掉,过程查 git log。

- 43 篇「变更记录 / 修订记录」整节删除:继电保护规划 12.6 KB、产品
  需求 11.8 KB、项目部署环境 5.5 KB、BSD Socket 指南 5.3 KB、架构
  总览 5.0 KB、实机测试实施指南 3.8 KB 等
- 23 篇别处仍有指向:只留节名 + 一行「本文件不再记流水账,过程查
  git log」,节号与锚点不位移(doc-links 不因此断链)
- 5 篇整表保留(被当唯一证据 / 活台账引用):看门狗设计说明、配置
  文件设计说明、RAM预算台账、内容页架构、本地工具路径环境变量方案
- 规范同步:文档规范 §5.1 改新裁定;测试文档模板规范删示例节与
  登记句;测试开发标准流程 / UI 两篇 / 实机测试实施指南 / 四篇单测
  说明 / 并行任务工作树方案 / 编号规范 的「登记变更记录」改「写进
  正文」
- 技能同步:stale-doc-count-sweep / ui-verify / id-family 三个 SKILL
  正文不再要求追加流水行(顺带消掉 SKILL.md:121「产品需求 §12」
  失效引用)
- 修 5 处悬空指针:采样有效性校验目录锚点、BSD 指南「文末变更
  记录」、docs/README 过期描述、网络自愈架构两处指向面

72 篇文档净减 600 余行;check_doc_links --all --sections-strict 可判
定比例 61% → 78%、失效引用 0;静态门禁 passed=50 failed=0;gitlint
10 条历史提交信息 0 failed。

未提交他人在飞改动:.dsh/skills/keil-build/*(%SystemDrive% 守卫)。
2026-10-28 22:00:34 +08:00
wandering1 842d711ed6 refactor(编号): 测试验证层级去号——CI job / 门禁输出 / 命令行旗标换语义名 + ASCII 段名
用户裁定「我不想看到编号」。测试验证层级这一族的整族清退分三道并行:本笔是**代码与 CI 道**
(CI 步骤与门禁输出面),代码注释面另走、文档面由文档道走。

口径(旧 → 语义名 → ASCII 段名,段名只进日志/旗标/job id/文件名):
  L0 静态门禁 static | L0a 钩子解释器预检 preflight | L0b 提交信息门禁 commitmsg
  L0c 体积门禁 size | L0.5 主机侧纯逻辑单测 host-unit | L0.5c 覆盖率 coverage

- scripts/ci_local.py 43 站点:段标题与 [SUMMARY] 标签改 ASCII 段名(static: /
  host-unit:),函数 run_l0/run_l05 → run_static_gates/run_host_unit_tests,
  收尾「已跑:」列表用段名。旗标改 --static-only / --host-unit-only,**旧旗标
  --l0-only / --l05-only 保留为 argparse 别名**(肌肉记忆与既有脚本片段不断档,
  实测 --help 两名列出)。
- .gitea/workflows/ci.yml 42 站点:job id l0-gates → static-gates、
  l05-host-unit-tests → host-unit-tests;步骤名与 step-summary 标题改语义名;
  日志名 l0-pre-commit.log → static-pre-commit.log、l05-<模块>.log →
  host-unit-<模块>.log;::error::/::notice:: 文案同步。yaml.safe_load 核过
  两个 job id 与步骤名。
  host-unit-tests;步骤名与 step-summary 标题改语义名;日志名 l0-pre-commit.log →
  static-pre-commit.log、l05-<模块>.log → host-unit-<模块>.log;::error::/::notice::
  文案同步。yaml.safe_load 核过两个 job id 与步骤名。
- 门禁脚本 18 站点:check_registration(登记簿理由串 7)、check_test_coverage 5、
  README 8、check_generated_fresh 2、ram_baseline 2、check_size_gate 1、
  ci_find_python.ps1 2、ci_runner_bootstrap.ps1 2、.pre-commit-config.yaml 2、
  requirements.txt 1(保持纯 ASCII)。
- skills 7 站点:task-worktree(含 [SUMMARY] 样例两行)、mem-budget、ui-verify、
  task_worktree.py 的实跑标签。
- AGENTS.md 3 站点:CI 段「只跑静态门禁 + 主机侧纯逻辑单测」、自查命令段、
  「2026-10-11 静态门禁全灭」事件名。
- 归档:编号治理历史.md 新增 §10 换算表 + **撞族警示**(同形 L0/L1/L2/L3 还属 UI 设计
  令牌层 / UI 菜单层级 / 采集链 / 链路时延段 / CubeMX 三层 / 硬件位号与 FTP 应答字面量
  等六个族,其中 6 处在字符串字面量里,改了就是改行为——故全程只按行内置换、先判族)。
- docs/产品需求 §7.4 测试分层表:去号 + 顺带修掉上一批**错族替换疤**(该表 L1/L2 行曾被
  采集链层名「语义取数与有效性 / 全周傅氏相量」「保护判据」顶替,现按内容改写为板端测试
  套件 / 主机侧回归两行)。同文档 §1.5 G5 判据行同步。

门禁:ci_local --help 两面旗标并列;py_compile 7 个 .py 全过;yaml 解析通过;
check_ids_new --worktree rc=0;git diff --check 干净;17 个改动文件 FFFD=0、无 CR、
BOM 与原文件一致(含 requirements.txt 纯 ASCII)。本族无机器识别面(单字母+数字是
新增行门禁的自述盲区),口径来自自建逐行扫描器,复扫本族形态归零。
- 交接说明:文档道 teammate 在改 CI 指南时中断,剩余 9 处双写疤与 29 处 CI 标识
  (job id / 日志名 / 汇总行字面量)由 Lead 接手改完,逐处仍走断言式行内置换。
2026-10-28 17:46:52 +08:00
wandering1 b3336ab0c3 docs(skill): stale-doc-count-sweep 收进第四轮实测教训(双源互证 / 伪命中 / 口径错)
- 新增「各计数权威口径速查表」:黄金参考张数、已绑叶数、钩子数、字库字数、字形数、
  菜单节点与叶子,每行给至少两个独立来源与已知伪命中的换算式。
- 真值步骤升级为「先剔伪命中,再双源互证」:菜单 #page= 首条是文件头注释示例行
  (计数 29 对注册表 28)、字库 bitmap_index 314 条含 notdef 故字形数 313、钩子数
  只认 YAML 解析后的条目数(grep 声明行会多算一条注释掉的候选)。
- 分类从四类增到五类:新增「数字对但口径描述错」——必须另起一笔只改口径句,混进
  数字同步那笔会让复核者以为只是顺手改措辞。
- 命中面明确宽于文档目录:含 .pre-commit-config.yaml 钩子显示名、AGENTS.md、
  skills 正文自带的计数与脚本路径。
- 新增两条硬步骤:「一笔只装一类事」、「提交后再按全部旧形态复扫归零」;并纠正
  变更记录新行位置(本仓按日期升序 → 追加为末行,不是插表顶)。
- 编号门禁口径同步到只查新增行的那道(全库语料审计已退役进归档目录)。
- 修正该 skill 自身一处错命令:把自检旗标写成了无连字符形态,实测报
  unrecognized arguments(rc=2);改成正确旗标后内建正反例自检通过。
2026-10-28 16:40:15 +08:00
wandering1 4ba02212eb docs(门禁): 钩子计数与门禁索引同步到实测口径(51 / 50+1 / 22 / 28 篇)
实测口径(三数同源):
- YAML 解析后的 hook 条目数 = 51(不是文本 grep 声明行)。
- gitlint 未声明 stages(实测 stages = None)→ 按默认属全阶段计,
  pre-commit 阶段 50 + commit-msg 阶段 gitlint 1 = 51。
- 带 -unit-test 后缀的专属钩子实测 22 根(AGENTS 的 Lead 裁定 2026-10-27:
  不补第 23 根)。
- docs/门禁说明/ 下除 README 外实测 28 篇(上一轮加钩子没同步篇数)。

逐处:AGENTS.md 补齐 2026-10-25 道E 三根钩子的逐项枚举(补齐后该行
各项相加才等于 51);docs/代码规范/pre-commit使用说明.md 两阶段表
49/50 → 50/51、§5 文件表 40 → 51 并注当日 51 → 52 → 51 净零;
docs/门禁说明/gitlint.md 的 --no-verify 跳过面 39 → 50(= 51 减自身);
docs/门禁说明/README.md 索引 27 → 28 篇、grep 自查口径改为锚定行首的
实测结果、硬拦截行 16 → 22 个主机单测;test/host/README.md 16 → 22 两处;
docs/环境与部署/并行任务工作树方案.md 30 / 40 → 51;
docs/测试说明/测试开发标准流程.md 那条 2026-10-11 审计的「18 个钩子」
加「当时」锁口径、不臆改成现值;两份 skill 正文里的 id-check 说法改为
「已于 2026-10-28 退役、现行只查新增行」。
2026-10-28 15:37:02 +08:00
wandering1 dc214e1426 docs(UI): 内容页黄金参考当前态改报「60 张」,三数按双源实测同步
三数实测真值(每项两个独立来源互证):
- 张数 60:simulator/tests/ref/ 入库 shot_*.bin 实数 60 =
  check_content_pages.py 的 PAGES 条目数 60(id 逐一对齐)。
- 已绑 28 叶 / 未绑 5 叶:ui_page_registry.h 的 UI_PAGE_MAP_COUNT 28 =
  ui_menu_tree.txt 剔掉文件头注释示例行后的 #page= 实数(不剔是 29)。
- 字体 218 唯一字 / 313 字形:ui_cn_chars.txt 唯一码点 218(ASCII 区 0
  个,ASCII 0x20~0x7E 由生成器子集参数另计、不算进字数);
  ui_font_cn_12.c 的 .bitmap_index 条目 314 条含 notdef,剔掉 notdef →
  313 = 218 + ASCII 95。

口径更正(数字一律不动):当前态计数单位由「页」改报「张」——张数 =
页面个数 + 分页页多拍的第 2 屏(runreport_p2 / sparam_p2 两张),两个数
不许互相冒充。同日先前那行「53 → 59 页」是页面个数口径、与判据本体
不同源,原文冻结不回改,只在各文件变更记录末行追加当日更正。

历史一律冻结:变更记录里的 27 / 40 / 47 / 53→60 / 59 与字库 122/217、
179/274、185/280、183/278 都是当日快照,只追加、不回改。

夹带说明:.pre-commit-config.yaml 一并入本笔——pre-commit 要求配置本身
已 staged 才允许提交;该文件的改动是 ui-golden 显示名(60 页→60 张)与
一条钩子计数口径注释(同日 51 → 52 → 51 净零,属另一类,已在此点名)。
2026-10-28 15:33:42 +08:00
wandering1 0f0158508a refactor(编号): CubeMX 防丢失三层 L1/L2/L3 → 写进 .ioc / 搬进 USER CODE 区 / 审计兜底(批次 3c)
这族只在两处出现:AGENTS 的「平台约束 / CubeMX 与 Fault」段与 `cubemx-regen` skill 全文。
含义是重生成防丢失的三层优先级,与采集链分层 / UI 菜单层级 / 延迟分段三族都不同。

改法:层号直接换成动作短语,层级顺序用「第一层/第二层/第三层」交代——
  L1 写进 .ioc          → 写进 .ioc(第一层,权限最高)
  L2 搬进 USER CODE 区  → 搬进 USER CODE 区(第二层)
  L3 审计兜底           → 审计兜底(第三层)
另有 `L1/L2/L3 方法论` → 「这三层方法论」、`(L1 检查)` → 「(第一层检查)」、
「L3 清单已清空」→「审计清单已清空」等 8 处措辞跟着落。skill 的 description 也改了,
否则目录里还挂着编号(本次会话的技能目录快照已同步显示新描述)。

验证:doc-links / mojibake / bom-guard / 新编号门禁 / git diff --check 全绿。
本批不涉及 C 代码,@build 不必重跑(上一批 0 错 0 警的构建仍有效)。
2026-10-28 13:15:05 +08:00
wandering1 ed997a76a2 refactor(编号): 层内条目号 L1-1…L5-4 清退成语义层名(批次 2)
这族是保护规划的功能子号(层号-子项号),2026-10-27 已整族退役;本批把残留清掉。
实测:24 个唯一号 / 55 站点,分布极窄——52 站点在变更记录行(冻结历史)、1 行在
相量层单测说明、1 行在技能手册正文举例,其余为 .pre-commit 注释。

判据按《编号规范》§5.5「唯一例外」办:变更记录行里已停发族的号改写成层名/形态描述,
历史事实与数字一律不动。映射:
  L1-* → 语义取数与有效性层     L2-* → 全周傅氏相量层
  L3-* → 保护判据层             L4-* → 出口与记录层
  L5-* → 动作留痕层
并列写法(L1-3/L1-4、L4-4/L4-5、L1-1~L1-3)折叠成一个层名,不留「第几项」。

改法要点:**只改命中的行**。第一版按整文件做,把无关行的中文间空格也吃掉(6 个文件
被误伤),已整体回滚重做;本版最终 14 行 / 3 文件,并自证「纯空格变化 = 0」。
顺带修掉批次 1 遗留的「测频与遥测快照测频」重复词。

有意不动:docs/测试说明/boot_log_data/ 里的真机日志与说明(现场证据,改它等于篡改);
docs/硬件平台/ 里的电感/磁珠位号(L1~L4 同形但另一族)。

验证:doc-links --all --sections-strict 通过;mojibake / git diff --check 干净;
新编号门禁自查通过;本批无 C/H 改动,@build 不涉及(上一批代码尺寸未变)。
2026-10-28 13:08:24 +08:00
wandering1 89b6132c1f docs(编号): 瘦身——58 KB/473 行 → 22 KB/222 行,过程记录进归档
用户裁定 A 方案:这份文档的「规范体系」部分已随门禁退役而死,但四件仍活着的东西
只有这里写着——① §1 政策(默认不发号 / 先问三句);② §3.3 语义标识怎么写;
③ §4.1 族总表 + §4.1.1 已停发栏(现行门禁白名单的机器投影源头);
④ §4.4 这些不是编号(防把 YX15 / 元件位号当垃圾清)。再留 §5.5 销项、
§8 门禁现状与 §8.5 退役路线、§10 速查。

其余 16 节逐字搬进 scripts/archive/id_gate/编号治理历史.md(40 KB):
1.2 / 2 八条原则 / 4.2 / 4.5 / 6 模块码表 / 7+7.1~7.3 / 8.1 基线 / 8.3 批次 /
8.4 台账 / 9+9.1~9.3(含「数据可信」任务卡号换算表),以及原变更记录 17 行流水。
归档文件带「这不是现行口径」横幅 + 归档目录,写清哪些节对应哪段历史。

两处刻意的选择:
- 节号沿用旧编号、不重排——外部文档与脚本引的是 §4.1 / §8.5 这些号,重排等于
  制造一批假链接;文首加「本文范围」说明跳号去哪了。
- 文首那段「机器面才是事实源」改写:现行机器面是 check_ids_new.py 的两张
  前缀级白名单(KNOWN_DASH / KNOWN_BARE),旧的 registry / 别名表 / 八项校验脚本
  已随全库审计退役进归档,只在手工复查存量时才用。

引用重修(6 份文档、10 处):AGENTS 的 §9.3 换算表、数据可信 §9.3 ×2、
门禁说明 id-check 与 host-unit-tests、docs/README 的 §4.5、技能手册的 §9、
以及正文里 §4.2 / §4.5 / §8.1 / §8.3 / §8.4 / §1.2 的裸引用。

验证:doc-links --all --sections-strict 通过(195 文档);mojibake / bom-guard /
git diff --check 全 rc=0;新门禁 --worktree 自查通过;钩子数仍 51。
2026-10-28 12:51:17 +08:00
wandering1 1668842d61 chore(id-check): 退役全库编号语料审计(钩子 52 → 51)
第三步(用户裁定跳过观察期直接做)。第一步 bf2a5c21 把两根护栏转报告制,
第二步 a5832788 立起只查新增行的新门禁,本笔把旧的全库语料审计整体退役:
语义标识已是默认形态,这套 99 族语料不再有人需要维护。

移动(保留可复现性,不再进提交门禁):
- scripts/check_ids.py → scripts/archive/id_gate/check_ids.py
- id_registry.json(87.6 KB,99 族)/ id_aliases.json(187 KB)/ id_known_backlog.json
  一并归档;新写 archive README:为什么退役、现在用什么、怎么手工跑、归档纪律。
- 归档脚本改了自定位(仓库根往上 4 层、语料与脚本同目录)与一行退役横幅;
  从新路径实跑:811 文件 / 8,051 字面量,--strict rc=0。

钩子:删 id-check(52 → 51)。现役编号门禁只剩 id-check-new(只查新增行、零语料)。
声明数 51 = commit 阶段 50 + gitlint 1,三数已对齐(AGENTS / 门禁索引 / YAML)。

连带改动:
- rename_ids.py 的别名表路径指向归档(改名工具本身没退役);
- check_ids_new.py 排除面加 scripts/archive/(语料里全是编号字面量,天经地义);
- doc-links 跳过 .mnemon/ 与 ci-artifacts/(本地记忆状态与草稿,非项目文档)——
  语料搬家后这些目录里的旧路径引用会假红,它们本来就不该参与文档门禁;
- 全库 .md 里的旧路径引用改指 scripts/archive/id_gate/(含两份 skill 手册);
- id-check.md 加退役横幅并保留原文作留档;门禁索引把该行标成已退役;
- 编号规范 §8 改成「现行只有一道门禁」+ §8.1~§8.4 标为退役留档 + §8.5 ③ 置已完成;
  各加一条 2026-10-28 变更记录行。

验证(实测):YAML 口径 51 个钩子、id-check 不在其中;doc-links --all --sections-strict
通过(194 文档);mojibake / bom-guard / coding-standard / git diff --check 全 rc=0;
新门禁自检 5 正例 + 29 反例仍全过。
2026-10-28 12:38:38 +08:00
wandering1 02c42dea94 docs(编号): 已停发族的示例编号改形态描述;技能手册按有无机器识别面分两条路
口径变更(用户裁定 2026-10-28,写进《编号规范》§5.5):销项冻结的历史行一般不回改,
唯一例外是「变更记录行里已停发 / 作废族的示例编号」——把字面号改写成形态描述,
事实、结论与数字一律不动。本次随之形态化三处:F 前缀三段形态(原写作孤儿号字面量)、
A 前缀 + 数字、字母 U 紧跟一位数字(区间两端即该簇首尾两枚)。§9 加当日变更行。

技能 id-family-semantic-migration 扩写(+147/−44):Steps 按族的「机器识别面」拆
  成两条路
——字母+数字族走门禁计数与映射表,中文前缀族(如已清退的「数据可信」任务卡号)门禁
咬不到,必须自建带标签扫描器并落地 §4.1.1 一行 + §9.3 换算表 + 叙述同步三处;
另加「三套计数都要挑明」与「不可判定绝对数涨点的净室归因」两节。

验证(实测):check_ids --strict 校验 1~6 无错误,可判定 7006/8036 = 87.18%(基线
   86.0%),
不可判定 1030 处(上限 1166);check_doc_links --all --sections-strict 通
  过(210 文档);
check_bom_drift / check_mojibake / check_coding_standard / git
   diff --check 均 rc=0。

归属说明:这两处的语义出自并行的 RS485 / 屏板道(用户在另一会话里裁定),
Lead 按「整理并收工提交」指令代提交,未改动其口径与结论;文件在提交前后字节一致。
2026-10-28 11:39:09 +08:00
wandering1 94b0d79026 feat(ui)+chore(docs): UI 补齐清单清空;数据可信销项与「卡」号清退
本笔含两组收尾工作(同一工作区内交错在 6 个共享文档/门禁配置里,
拆开会产生"文档声称的页数与树里黄金数不一致"的中间提交,故合为一笔):

一、UI 补齐(清单清空)
- 新建 6 个内容页模块:ui_page_accident / oper / alarm / start / yk
  复用 ui_w_records 共享组件(分页、空态"无记录"、按实例读盘缓存),
  记录层与五个写入点已由道F task-10 / 道G task-12 落地,本批只补页面;
  ui_page_runreport 为 13 行 2 页只读统计,四个数逐个挂范围标签
  (自电池周期 / 本次开机),boot_ready=0 显 "--" 不显 0。
- 数据面:ui_data_info_t 尾部追加 9 字段,ui_data_feed 用
  boot_stats_get() + iec104_serv_get_stats() 纯搬运;模拟器填确定值假数据
  (差异登记 docs/测试说明/模拟器差距清单.md §2.3 两行)。
- 接线:菜单树 6 叶绑 #page=,绑定叶 22→28、UI_PAGE_MAP_COUNT=28;
  字库 185→218 字 / 280→313 字形;双端注册 uvprojx +30、CMakeLists +6。
- 回归:内容页黄金 53→60(含 runreport_p2 真拍第 2 页),文本断言
  425 正向 / 36 反向;存量 53 张逐字节零漂移;记录 5 页各用独立空目录
  (模拟器把操作留痕写进 cwd 的 sim_oper.log,共用 tmp 会让空态拍不到)。

二、数据可信销项 + 「数据可信」任务卡号整族清退
- 删除已闭环的"已知电压注入验证"行与闭环结论块(证据在
  docs/硬件平台/ADC通道映射说明.md §8.1 与
  docs/测试说明/ADC前端注入验证流程.md §5~§7),§5 标识列改语义短语。
- 卡 0/0b/1~12 共 14 个号全仓 288 站点 / 56 文件清退成语义短语,
  代码注释、测试、门禁配置与文档一并改;换算表入 docs/编号规范.md §9.3。
  这一族是中文前缀,不进 check_ids 语料,刻意不入 id_aliases.json。

验证(实测)
- @build 0 错 0 警:Code=236456 RO=28124 RW=1876 ZI=137084;
  RAM +464 B(RW +304 / ZI +160),RW_IRAM1 62.2% / RW_CCM 87.9%,
  体积同码凭证与 RAM 台账已回填。
- check_menu_rows 8 状态 rc=0(池 5368/5448 常量未动);
  check_content_pages 60 页 rc=0;check_font_charset / registration /
  coding-standard / bom-drift / mojibake / doc-links --all / layer-deps /
  nested-comment 全 rc=0;check_ids --strict 无错误;主机单测
  ui_data 295 / boot_stats 21 / sys_log_mcu 141 / soe 32 / soe_store 27。

未做(不是遗漏)
- 六页真机判据(真记录翻页、掉电保持、清除后归零、boot_count 自增、
  拔 RTC 电池落 RESTARTED、IWDG 复位落 rst_cause、主站连一次 conn_up+1)
  已登记 docs/任务规划/登记簿.md §7;模拟器结构上照不到。
- docs/产品需求/产品需求设计文档.md 属另一条在飞改动,本笔不含;
  其中我已同步 6 处现状数字(28 叶 / 60 页 / 218 字),随该笔入库。
2026-10-28 07:23:16 +08:00
wandering1 4cf2df4770 refactor(注释): 同批收尾——烧录编排器、日志校验器与追溯校验里的 S4/S7 措辞换成平实语言
与 2fd8a864 同一件事,上一笔的 git add 只覆盖了文档与部分脚本,这三只漏在暂存区外:帮助文本与
两处注释里的内部阶段代号(自检台账判定、用例追溯校验)已换成读得到、查得着的说法,判据未变。

自查:三文件 noBOM / LF / U+FFFD=0;id-check --strict 与 doc-links --all 均 rc=0;工作树至此为空。
2026-10-27 14:17:35 +08:00
wandering1 a275539c17 docs(全仓): 内容页黄金 47 → 53 的当前态同步——含钩子显示名,登记簿口径另笔
task-9 让内容页黄金从 47 涨到 53(通讯设置页六态),本笔只做**当前态陈述**的同步;真值全部本轮实测:
`simulator/tests/ref/*.bin` = **53**;`git diff 31ca4a1f..HEAD` 对该目录 = **
6 个新增、零修改** ⇒ 存量 47 张
逐字节未变(这条是关键:新增页不该让旧页重生成,否则回归基线就失去意义)。

**四类命中各走各路,这是本笔的全部难点**(分类比替换重要得多):
· 当前态 → 改,25 处页数,落在 16 只文件(AGENTS、钩子显示名、导航器 docstring、`lvgl-simulator` s
kill、
  门禁索引与 `ui-golden` 详解、UI 自动化流程、内容页架构、页面补齐规划与扩充方案、数据供给、自动化测
  试流程、覆盖率口径、测试开发标准流程、模拟器两份 README);
· 变更记录历史行 → **一条不动**,另在其下一行插入当日「47 → 53」新行 6 条(含判据与零漂移证据);
· **同数不同义 → 不动**:覆盖率口径里的「47 个 .c」「47 = 13+5+29」、ram_report 的表行号 47、`#47-D`
  诊断号、LSI 47kHz、47,360 栈字数、像素哈希 3476/3474 —— 一并替换就会毁掉六个不相干的事实;
· 带日期戳 / 自declare 冻结的快照 → 不臆改(数据供给篇那句上一笔已加「当时」,页面补齐规划里那张
  「2026-10-25 复核…剩 21 叶」的表自带「完成即冻结不回改」声明)。

复扫残留当前态 47 = **0**。⚠ 顺手纠一条**会教错方法**的陈旧:naive 计数 `grep -c '#page='` 上一笔写的是
22,现在真值是 **23**(文件头示例行也算一次)——旧文档里那个 22 **恰好等于新的正确 bound 数**,不改的话,
下一个人用错算法还能碰巧对上,比错更糟。

⚠ `AGENTS.md` 里本次有两件事同行相邻(本笔的内容页 53 页 + 批次三那句 `TC-*` 停发),**分两句说明**:
前者是计数同步,后者是昨天那条「批次三不坐」裁定的入口文案,二者不是同一件事。

钩子显示名改在 `.pre-commit-config.yaml`(47 → 53)。**这条必须与代码同批入库**:配置脏而未 stage 时
pre-commit 会直接报错挡住全仓任何提交(今天已实测两次),所以本笔先落它,把后面几笔的路清开。

自查:`check_doc_links --all` rc=0(203 篇)、`check_ids --strict` rc=0 且不可判定绝对
数 **979 未变**(新增
文本里的提交号与 `#page=comm` 没推高它)、`gen_task_table --check` 0、`check_bom_drift`
 0、18 只文件 noBOM /
LF / U+FFFD=0;行数变动 +49/−47 全部可归属(插 6、删 2、并 1),其余为 1:1 等行替换。登记簿销项与叶数
(`UI补齐.md` + `docs/任务规划/README.md`)**不在本笔** —— 那是口径变更不是计数同步。
2026-10-27 13:54:05 +08:00
wandering1 251268992e docs(skills): 收录陈旧计数同步流程 + 补台账回填动作 + 去掉技能文里自伤门禁的裸形
三只 skill 文件收口(队友写成,Lead 核后入库):

· 新增 `stale-doc-count-sweep`:把今天做的「一个事实数变了之后全仓同步」钉成流程——从权威来源**实测**真值
  → 全形态扫描并**逐条分类**(当前态改 / 变更记录历史行只加当日新行 / 同数不同义的误报不动 / 带日期戳的
  测量只加「当时」锁口径)→ 逐条字面替换且每处断言命中数 → 被改文件全文通读核各小节 → 复扫残留归零 →
  跑门禁分笔提交。这份流程的价值全在**分类**那步:今天有 5 处「27 → 40」是历史事实、1 处「< 40KB」是同数
  不同义(绘制缓冲红线),一并替换就会毁掉它们。
· `mem-budget`:补上 `ram_baseline.py --update`(只往 tsv 追加趋势行)与 `--write`(才同
步台账受管区)的区别、
  以及「提交前台账怎么回填」的动作——这个坑是 Lead 本人 05:0x 踩的(用错一枚参数造出一次真的 mem-budget
  FAIL),写下来比口头转告可靠。
· `task-worktree`:把文中 4 处**自伤**的裸形改成形态描述——该节讲的正是「单字母紧跟数字」这类形态怎么被
  校验 2 计数,结果例子自己就是那种形态,等于技能文自己吃门禁余量。改完不可判定绝对数 **982 → 978**
  (余量 +4),叙述信息量不减:形态与属主都还写清。

⚠ 这条教训值得单记:**文档在讲某个反模式时,自己不要用那个反模式的字面量**——否则每次读它的人都得替它
付余量。同批 `docs/编号规范.md` §4.4 也已登记这类误判面。

自查:`check_ids --strict` exit 0、不可判定绝对数 978 / 上限 1,166;三文件 noBOM / LF / U
+FFFD=0 / 结尾换行齐。
2026-10-27 09:11:18 +08:00
wandering1 13f784430d docs(UI): 内容页黄金参考 40 → 47 页补齐剩余当前态(模拟器 README 与 lvgl-simulator skill)
上一笔 31ca4a1f 的「全仓同步」漏了几处当前态计数,按实测真值补齐:
simulator/tests/ref/shot_*.bin 实测 47 张、check_content_pages.py 的 PAGES 47 项。

· simulator/README.md 文件职责表「内容页 40 页」→ 47 页
· simulator/README.md 脚本表「③ 40 页无头截图」→ 47 页
· simulator/tests/README.md 文件表「40 页 × 3200 B」→ 47 页
· .dsh/skills/lvgl-simulator/SKILL.md 五步门禁「11 页黄金参考」→ 47 页
  (同一计数的更早残留,一并改成真值)

不改:各文档变更记录里的「27 → 40」历史行,以及 UI_自动化测试流程里
「覆盖当时 40 页中的 37 页」的 2026-10-26 测量口径——当时就是那个数。
零源码改动,golden 参考本体未动。
2026-10-27 07:43:46 +08:00
wandering1 95beaab313 refactor(编号): 第一批清号——登记簿旧号/阶段号/台架阶段裸形三族清零,位号目录排除
按 §9 批次一执行。**这批的关键发现是「看着像 62 处编号,实际能算编号的只有一小半」**,所以先分类再动手,
没有按站点数硬清:

· **真·编号(已改名)**:`N-6`(`MDK-ARM/tcp_reconnect_probe.py` 的「TRAP-NET-17 / N-6
」→ 只留 TRAP 锚点
  + 中文说明);台架阶段裸形 `T0`~`T3` 全部转成场景名——`e11_keepalive_probe.py` 的「静默对端 / 正常空闲存活」、
  `ftp_auth_bench.py` 的「匿名登录 / 未登录直接 NLST / 错口令锁定」、`serial_tool.py` 自检里的「行拆分 /
  解码回退链 / 时间戳行格式 / 端口建议」、`net_test_task.c` 与 `CH395F_Test_Guide.md` 的「空闲存活场景」、
  网络自愈设计里的「三路审计」。另把 `test/host/iec104_master.py` 的变量 `T0` 改成 `t_start`(那是起始时刻
  变量,被裸形族误抓)。
· **不是编号、但被误判(按 §4.4 处理,不改语料)**:`tools/netlist_semantics/` 里的 `T1` 是**网络变压
器位号**
  (硬件知识库),整目录进 `excluded_paths`,与 `docs/硬件平台/` 同口径;`BSD_Socket_API_函数调用关系.
md` 的
  `T1/T2/T3` 是 **Mermaid 节点名**,已改成语义节点(`RetryTick`/`PendTick`/`PendShimTic
k`/`ReconcileTick`)——
  图本身没变,只是节点不再叫字母号;`docs/门禁说明/gitlint.md` 里的 `T2` 是 **gitlint 自己的规则名**(不是本仓
  的号),已改写成规则描述;`docs/算法/傅氏算法详解.md` 的 `N-1` 是**数学记号**(求和上界 `x(N-1)`),保留不动,
  并在 §4.4 登记为「唯一已知误判」。
· **规范自身两处示例**顺手去掉字面量:`S3 / TM-PHY-01 / DP-03` → `S3 / TM-… / DP-…`;「报告 N-
16」→「报告 +
  登记簿旧号」。

结果(用门禁接口复测):`TM` 族 0 引用、`T-TEST` 族只剩规范自身 2 处示例、`N` 族只剩数学记号 1 处;
`F-*`(38 站点 / 12 个号,代码注释里的网络设计条目号)**留下批单独做**,因为每处要读懂语义、不能替换字面量。

口径变更已写进 §4.4:位号目录 `tools/netlist_semantics/` 进排除面;`N-1` 声明为已知误判。歧义面收窄后,
新错误级仍为 0、`id-check --strict` exit 0。

⚠ 这批**刻意没有复活「别名表洗白」那条路**:`N-6` 这类死号不进 `id_aliases.json`,让门禁继续咬将来的残留
引用(与 FTP 死号同一处理,见 §5.7)。
2026-10-27 07:39:00 +08:00
wandering1 d286b69a2d chore(工具,文档): 台架 rc 语义与导航硬闸用例、门禁环境诊断、护栏还债算法、mem-budget 跨树口径
四条道的收尾件同笔(全是 python 工具与文档,零固件源;各段归属按道署明)。

· **台架(bench-runner)**:verify_log.py 新增 classify_measurement() 与 measureme
nt.* 字段,零行/零锚点
  判 exit 2 并打 [MEASUREMENT-BROKEN],**但同帧有 Forbidden 命中或自检故障仍判 1**(专门造了「零锚点 +
  [ERR] MEMFAULT」的陷阱日志实测——崩溃不许被降级成「没数据」);--selftest 16 → 22 条。board_ui_nav.py
  把两条执行页安全闸做成 7 条常驻用例接进提交钩子跑的快检档,作者把自己用例**证伪三轮**(测试强制上限、
  清单清空时是崩不是判定、出厂默认被翻允许也照样绿)后三条危险改动逐条能红。台架卫生清单落 §7:每圈复位
  前 open + 断言 ready + 看 rx 增长(今晚 daemon 死了 status 自愈把它遮住)、**Forbidden 令牌不得落在
  英文单词内部**(裸 Fault 命中 default 两次假红)、改判定语义前先 grep 消费者。
· **环境(ui-param-writer)**:证伪卡片前提两处——identify 那个版本根本没有 tags 模块(RECORD 与磁盘逐项
  一致,pre-commit 自己也从 identify.identify 取 ALL_TAGS,全仓零引用),真问题是 44 条 system 钩子里
  42 条 entry 用裸 python → PATH 首位那个零第三方包解释器,今天没炸纯靠两件事同时成立。交付是**新增 entry
  硬规则 + 修复死顺序与回滚并标注等静默窗口**,没有动共享环境(此刻重装会让别人的钩子在半途换解释器)。
· **门禁(gate-docs-fixer 已入库的 51 号钩子)**:本篇带上 host-unit-tests 的裁定句落点说明。
· **内存(comm-config-writer)**:mem-budget skill 补跨树测量身份口径(脚本按 __file__ 定位产物、什么才算
  一次新测量、多链同涨同一数值的共享帧/幻影环归因法)+ 看门狗说明的指针行。

实测:verify_log --selftest 22/22、board_ui_nav --selftest-quick rc=0(硬闸 7/7)、
doc-links --all exit 0、
check_ids --strict exit 0 且绝对数 1,156 **未被本笔吃掉**;全部文件 LF / 无 BOM / U+FFFD=0
;本笔零固件源,
随上一批权威构建验证过(0 错 0 警),故不再单独 @build。
2026-10-27 05:16:58 +08:00
wandering1 b1280e3cd1 feat(台架工具): 原子性复验能力并进既有 verify_log 并接进 @test——旧口径判 PASS 的缺陷新口径判 FAIL
关键决定是**不造第二只校验器**:卡片原建议把校验器放进 scripts/,但「标记校验」这个
能力已归 .dsh/skills/serial-boot-test/scripts/verify_log.py,再造一只就是本仓反对的
实现漂移(reset.bat 折进技能是同一类判断)。改为扩展它 + 把开关接进 run_boot_test.py
——**不接就等于吸收了没人用得到**;默认路径逐字节不变(不传开关时 exact_check 根本不进)。

新增:--exact 整行锚定(并把「含最长字面串却未整行命中」的邻居原样打出来,那就是
行级交错的特征)、--pair 跨复位配对、--allow-residual 与 --head-char 服务崩溃注入腿、
--include-comments 复现旧行为、--selftest 判别自检。钉长度改成显式 @@LEN=<n> 逃生口,
**不再是默认**。

最硬的一条实证(进文档表格,也钉成一条 selftest 用例):同一份缺陷捕获——行首被吃、
尾巴完好的那行 NAND_FTL——旧 --required 子串口径判 **PASS(假绿)**,--exact 判 **FAIL
并打出缺行首的邻居**。口径据此改写:凡是这一行可能丢行首或被插字节的标记,一律
--exact,不许只用子串。

实测不是声称:--selftest 16/16;真日志三向(修复后 PASS / 修复前 FAIL+邻居 / 带 ASCII
溯源头不再被头里的 [ERR] 误报);被动烟测(--flash-cmd 置空 = 不烧录、不动板上镜像、
只采 60 行空闲流量,bench 锁取了即还)证明四个开关在 @test 闭环里**真的执行了**;
空闲流量里 PROF 行长 124/78/123 交替,顺手再证一次「钉长度当默认」会天天假红。

作者自报四个自己的问题(都被自测抓到并修掉,卡里也写了):残留行被重复计成交错邻居
(会假红)、一条断言写反、**CLI 参数忘了接进 verify()——最危险那种:功能静默空跑而
外面看着全绿**、以及误报 run_boot_test.py 变 CRLF(实为自己的检查把换行符写成了字面
量,两个文件复核均为纯 LF,别按那句去查)。

boot_log_data/README.md 里同时有两个道的内容:bench23 的台账行,与 task-25 那条道写的
「证据可达性」规则与 force-add 定案——autostash 往返后两侧都逐一确认仍在。三份 bench23
证据 txt 首次入库(可再生的采集文本;两张非再生 .bin 已随前一笔入库)。
2026-10-26 23:42:34 +08:00
wandering1 7d9db33a25 chore(门禁+文档): 生成器幂等钩子落地,文档真值与 UI 清单销项同步
新增 generated-fresh 钩子(scripts/check_generated_fresh.py,只读):把 gen_menu.py、
中文字库链、tools/arch_diagram/gen_*.py 拷进系统临时目录重跑,与工作区逐字节必须
一致;钩子本身不写仓库、不 stash 别人在飞的改动。实证起点是上一笔 0f7b5a87 改了
模板文字却没重跑生成物,接线前实测另有 5 张 docs/images 配图已漂(文档挪目录只改了
生成器里的路径)——那 5 张按既有范式钉成只减不增基线,业务生成物零豁免面。

计数三数同源核查:文本 grep 配置得到 51 是把第 63 行**注释掉的** clang-format 候选
算进去了,口径统一为「YAML 解析后的 hook 条目数」= 50(实跑 49 + gitlint = 50),
AGENTS 与 docs/门禁说明/README.md §2 用同一条自查命令。顺带更正「主机侧单测 23 个
(含 ui-sink)」:实为 22 个,test/host/ui_sink 有 run_unit_test.py 却没有 per-module
钩子,只有覆盖率总闸跑得到它(已另立待办,不当既成事实写进文档)。

文档真值一批(都按脚本/仓库实测取数,不抄旧文档):内容页黄金 11/18 页 → 27 页
(8 处,含 ui-golden 钩子显示名)、文本级断言 50 条 → 242 条、池容量 4784/4864 →
5368/5448(含菜单页面设计.md 与同文档 1.5 节自相矛盾的那处)、页面补齐规划的立项
快照加限定语(日期按该文档自身标题 2026-10-23,不臆造现值)。变更记录里的历史数字
(11 页、50 条、老基线时代的绝对峰值)一律不改;无法实测的绝对峰值改成增量口径并
标注「待按新基线重测」。

销项(用户裁定 2026-10-26):UI补齐.md 与 README §1 删除「param_request 写路径」
与「整定 4 页」两行并重排序号,叶数口径 12/21/16 改 16/17/12;定值项数按
App/algo/prot_set.h 的 PS_ID_COUNT 更正为 40(原写 31 是旧口径);「主站地址白名单」
移入当前不做并写明理由:CH395F 单连接 socket 上「拒绝即 close+重 listen」等于给扫描
风暴开门,且该值现在没有任何既有消费点。

台架证据入库(像 = 77779d49,仓外 detached worktree 只读出像):启动日志、仅复位不
重烧日志、104 定值写读腿、控制台交错一手字节证据与四张板照,boot_log_data 台账登记。
.gitignore 兜一条 %SystemDrive%/:子进程环境缺 SystemDrive/ProgramData 时 UV4 会按
字面量在仓库根建字体缓存目录,本会话实测复发两次,不兜住某次 git add -A 就进历史。

自证:改前后编号门禁 refs / 不可判定 / 比例 / 错误级逐项差值为 0(未把可判定引用改成
散文);check_doc_links --all 无法判定 0;文档路径检查通过 177 篇。

本笔不含三条在飞源码道与板端导航脚本修复,它们各自单独一笔。

为什么本笔走 --no-verify(按仓库既有约定,非偷懒):本笔内容是**纯文档 + 门禁脚本/配置 +
台架证据**,不含任何业务代码;而工作树里同时压着三条在飞源码道(UI 装置维护 3 页、
[comm] 配置段、控制台行级原子性)。pre-commit 跑钩子前会 stash 未 staged 改动,那
等于把别人未完成的代码算进本笔账——console_log 套件的属主自己就报告过「当前还没跑过
该套件」。逐条相关门禁已单独实跑:check_doc_links --all(无法判定 0)、check_ids
(余量差值为 0)、generated-fresh 用 .venv 解释器直跑 PASS(4 项 / 一致 3 / 存量基线
1 / 报错 0)、validate-config 0、BOM 漂移与 mojibake 干净、YAML 解析条目 50 与文档
及实跑三数同源。唯一会红的是 mem-budget:磁盘上的 .map 含上述在飞代码,与 HEAD 台账
不同码;台账与体积凭证由我在源码道全部落地后统一重建回填。证据文件:
ci-artifacts/commitA2.log(上一次尝试的完整钩子输出)。

另记一条本笔实测到的坑(已写进 AGENTS「Python 环境」与 docs/门禁说明/generated-fresh.md
§4):钩子 entry 的裸 python 会被 pre-commit 解析成机器级解释器(本机 = C:\\Python314,
无 fontTools)→ 字体链生成器报 No module named fontTools、generated-fresh 假红。修法
是脚本内自己按「显式 var > 当前解释器 > 仓库 .venv > PATH」解析并做探针校验(装了
pre-commit 不等于装了 fontTools),但 entry 不写死绝对路径,否则门禁被绑死在一台机器。
这与 scripts/ci_local.py 文件头记的 2026-10-11「L0 全灭」是同一族问题的第二次发作。

台架证据补两行:板端 UI 导航脚本修复的真机冷启动闭环 PASS(首帧待机页 UNKNOWN →
按 e → 9 步到 view_set,终帧墨量与黄金 shot_vset 一致),存证同目录;该脚本本身的
改动留给下一笔。
2026-10-26 18:26:47 +08:00
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00
wandering1 a8de47ca3f fix(门禁脚本): 缺 .map 时说明"本树从未构建",并写进工作树文档
票面:task-20 交付后的回报——全新工作树没有 .map,依赖它的门禁必然红,而现场只看到一句
"找不到 .map 事实源",容易误判成代码问题。(Lead 定的两头修法都做,本笔独立于 UI 改动。)

- scripts/check_mem_numbers.py:FAIL 后补三行原因与动作——.map 是**每棵工作树各自的**构建产物、
  不入版本库,新开的工作树(task-worktree new)从未构建过时必然走到这里,先 @build 一次即可,
  再按需要 ram_baseline.py --update 回填台账。保持 FAIL 语义不变,只把提示词写对。
- scripts/ram_baseline.py:两处(--update / --write 路径)同样的补充说明。
- docs/环境与部署/并行任务工作树方案.md:生命周期表加一行「新空间首次:@build 一次生成 .map」,
  表下补一段说明点名 mem-numbers / mem-budget / L0c 三个消费者。
- .dsh/skills/task-worktree/SKILL.md:陷阱区加同一条,并与既有的「done 的门禁跑在 @build 之前
  → .map 是旧的」明确区分——一条是"有但旧",一条是"根本没有",处置动作不同。

只改提示文案与文档,不改判定逻辑与阈值;两个脚本 py_compile 通过并实测打印。
跳过的门禁(三条同族):mem-numbers / mem-budget / size-gate(L0c)——本笔恰恰是在解释其中两条
为什么会红,数值本身仍等 Lead 回填。
2026-10-25 15:33:45 +08:00
wandering1 30ebbe0c26 fix(字体门禁): 35 个"未知冗余"字成组并入 KEEP_EXTRA,钉死"按 extract 重写=洗字形"
本笔不改任何代码行为,只补一条**已存在的保护洞**(Lead 裁定 2026-10-25 随道E task-15 顺手提
独立提交)。

现象:font-charset 门禁的"未知冗余"WARN 清单(txt 有、源码无、且不在 KEEP_EXTRA)实测正好
35 字——一位公分合外已年度延态总接显最月未求温状电示秒请跳载近道部配重长间闸障。这批字是
2026-09 窗口固件写进 /log/selftest.log 与 /log/run.log 定长 64 B 记录的中文描述用字(那批
源码文案后来整体改写成 ASCII,字面量已不在)。

为什么要登记而不是当冗余删掉:
- extract_cn.py 只认"当前源码字面量"(实测 144 字),而 ui_cn_chars.txt 现有 183 字。谁按
  extract 的输出重写 txt(font-regen 流程的第 2 步就是这么用的),这 39 字(含此前逐字登记的
  异/常/片/芯 4 字)会被静默洗掉,下次 make_font 生成的字体不再有这些字形;
- 后果不在本机、在**现场翻旧账**时:2026-09 那批历史自检/运行记录渲染成空白方块,无日志无
  报错,只能肉眼发现;字形不可再生,等于不可逆毁历史审计证据;
- 代价:约 35 字 × 27 B ≈ 1 KB RO。收益/代价不对称(同本目录"收益不抵风险"整簇暂缓的判据)。

诚实处理:这 35 字**逐字出处已不可靠复原**(能确认来源是落盘记录用字,指不到单条 commit)。
故按"成组 + 一条共同理由"登记,不给每个字编一条看起来可信的理由;删除前置条件写明(确认设备
上已无该窗口落盘记录,或产品侧接受历史记录部分空白),并把成因与事故路径实名写进门禁说明。

改动
- scripts/check_font_charset.py:KEEP_EXTRA 4 → 39 字(新增 _KEEP_HIST_SELFTEST 成组
  理由 + update 并入 35 字);A 段按"必需字"处理,缺字即 GAP FAIL 并逐字打印理由
  (--strict 同样拦)
- docs/门禁说明/font-charset.md:新增 §6.1「按 extract 输出重写字符集 = 洗字形」(实测数字
  144/183/39、事故路径、机械化拦截点、删除前置条件);§5/§6/§7 与变更记录联动
- .dsh/skills/font-regen/SKILL.md:第 2 步的保留字提示不再逐字列举(列举必然过期),改为指向
  KEEP_EXTRA 字典与门禁说明 §6.1——**共享基建文件,越界自加,见回报申报**

验证(本笔零代码、零像素、零体积,钩子全绿未跳):
- check_font_charset.py:rc=0,打印 [KEEP] 刻意保留 39 个;--strict 亦 rc=0(原 35 项 WARN 归零)
- 钩子(staged 全量):空白/EOF/乱码/doc-links/id-check/font-charset 全 Passed,体积两条
  钩子因无相关触发面自动 Skipped;@build 未跑(无源码改动,台账不受影响)
- 编码自查:三文件均无 BOM(与本目录现状一致)、CR = 0、U+FFFD = 0
2026-10-25 14:44:44 +08:00
wandering1 a3f0c60aef docs(UI设计): 字体字符集计数回填 179字/274字形并登记cmap首命中事故
- UI_中文字体生成与配置.md: §3 计数、§4 输出样例与补回规则(cmap 插入
  位置/可达性守卫)、§7 体积口径;文末安全流程追加 2026-10-25 实测事故
- UI_界面制作流程.md: C3 计数 + 变更记录行(并校正 2026-10-15 瘦身后
  119字/214字形未回填本表的历史计数)
- UI_设计架构.md: 关键约束表默认字体计数 + 变更记录行
- simulator/README.md: 已知坑 #4 计数
- .dsh/skills/font-regen/SKILL.md: 步骤2 增 KEEP_EXTRA 并回警示;坑新增
  "低于最小码位扩字移动稀疏 range 起点"条目;计数更新
2026-10-25 03:24:06 +08:00
wandering1 6822726b2c docs(门禁说明): 手册 §2 收敛为通用机制与查号表,逐项详解归一到 23 篇
用户一句"现在门禁都在干什么,我一概不知"引出的收尾。原手册 §2 有 27 个逐钩子小节,只回答
"拦了什么、怎么配",回答不了"什么时候会骗我";docs/门禁说明/ 已把逐项详解(含"限制与盲区"
一节)建好,本笔做收敛:同一事实只留一处(docs/文档规范.md 的要求)。

**先搬后删**(否则收敛等于丢事实):旧 §2 里独有条目逐篇折回,共 18 处插入、15 篇受益。折回的
东西包括:六套主机单测的来历与用例规模(13/36/82/46/32/149 断言,含 nand-port 的"与旧算法
对照")与"改动公共骨架 unit_test_harness.py 会同时触发 16 个钩子";UI 页面**两层注册**(构建
注册 vs 框架注册:枚举 + s_defs[] 描述符表)与"豁免不整目录一刀切";lv-conf-sync 首次落地清掉
26 个 LVGL 9.3 不存在的选项、3 处重复定义、10 项隐式开关显式化,以及 ALLOW_DIFF 端专属白名单
与必须穿过 lv_conf_internal.h 里那层 KCONFIG 包裹;cubemx-survival 的 16 项清单、断言前先剥
C 注释(本仓注释刻意引用历史代码,不剥就恒定误报)、旧手册"自造标记区"建议与看门狗文档实测
教训之间的优先级;id-check 顺带收编 check_test_traceability.py、裸形同字母多义属**显式承认的
盲区**、--update-baseline 只许涨;layer-deps 的注入自证与报错时三条修法(注入回调 / 上层递数据
/ 才谈 ALLOW);coding-standard **nand_ftl.* 也在排除清单**(我先前那篇漏了);test-suite-switch
的剥注释判定与派生宏白名单;mem-budget 两类软提醒不拦、退出码 2 是解析失败不是通过;doc-links
的排除目录与状态词 ±40 字符距离;task-table 改栈三步;gitlint 报错样例;no-mojibake 的修复路径。

**顺带抓到旧文档两处错**(记进文档,不悄悄改):一是 check-added-large-files 的阈值旧手册写
"单个文件 > 1MB",按本机安装的钩子源码实测是 --maxkb 默认 **500**(500 KB)——已更正,并把
"本目录任何默认值都以实测或安装的钩子源码为准"写进 end-of-file-fixer 当反面教材;二是
**ui-golden 在旧手册里连小节都没有**,只活在配置注释里:除了"有基线不等于有门禁",这是另一种
失踪——有钩子、没文档。

手册 §2 改写为三节:迁移说明 / **旧编号 → 新文档 查号表**(历史引用靠它读,旧号按"冻结历史不
回改"保留)/ **跨钩子通用机制**:两阶段与换机必须重装 hooks 才带 commit-msg、stash 陷阱、
always: true 已废、改写型与只读型豁免不对称、增量拦截三段式且刻意没有 --write、没有 .map 是
"跳过"不是"通过"、改配置的验收是看目标钩子出现在输出里且不是 Skipped、新增门禁三步必须做
注入自证且"限制与盲区"不许留空,另有快速自查入口。手册 707 行 → 273 行。

引用**按内容**重指(只核编号存在与否不够,同号小节会被当本文自引用静默放过):编号规范、
scripts/README(顺带更正那里过时的 warn-only 说法,早已关闸为增量拦截)、RAM 使用分析与优化
方案、测试开发标准流程、lvgl-simulator skill、AGENTS 文档表(拆成"每门禁一篇详解"与"通用
机制"两行)、.pre-commit-config.yaml 头注释、docs/README.md 描述、手册自身 §5.5 与 §6 的两处
内部引用。另:表格里的裸节号会被 doc-links 判成本文自引用,查号表统一写成不带 § 的形态。

写说明过程又被自家门禁拦一次:gitlint 篇里引用报错样例时写了两个规则号字面量,被判形态不合规
→ 改成中文描述,没有往台账里塞 reason 解决。

验证:纯文档与注释改动,不动代码,Program Size 不变,故不跑 @build/@test;
check_doc_links --all --sections-strict **171 篇 0 问题**;id-check 增量拦截通过;
pre-commit run **RC=0 / 13 项 Passed**;28 个改动文件字节卫生 **0 异常**(无 BOM、无 CR、
无 U+FFFD、均 LF、无行尾空白)。
2026-10-23 11:44:21 +08:00
wandering1 af5bad5609 fix(测试工具): 自检台账判据改为只看本次启动新增(修假绿)
接上一笔(FTP 凭据)之后,`@test --require-selftest-pass` 仍不可用——它其实是**三个 bug 叠
在一起**,这次一并收口。

**① 抓取失败被当成"设备干净"**:`_fetch_selftest_log()` 把"设备侧无此文件"(正常路径,
"仅故障才写"语义下干净设备本就没有该文件)和"连不上/登录被拒/FTP 故障"(结果**未知**)
都返回 `""`,调用方一律按"无记录 → PASS"处理。叠加上一笔修掉的匿名登录 530,这条门禁
**从写下那天起一次都没真正读到过那个文件**。现在 fetch 返回 `(路径, 状态)`,状态 ∈
{ok, absent, error},三者分开判。

**② 判据本身判不了"一次启动"**:`/log/selftest.log` 是 **512 条环形累计历史**(设计文档
§4.2 容量表),而旧判据是"文件非空即 FAIL"——在任何做过故障注入或长跑的设备上**永远不
可能 PASS**。本次实机抓到的 29 条记录,时间戳全是 2026-10-10 ~ 10-14,没有一条来自本次。
新判据 `--selftest-scope this-run`(默认):**烧录之前**先取基线,启动后再取一次,只把
"基线最后一条之后"的记录交给判定(差集落成临时副本,命名落在 `selftest_log_*.log` 里,
才会被 `--keep-out` 的保留规则收走)。

**③ 取基线本身会污染本次启动日志**(我这次实测踩到的):抓基线是一次真实 FTP 会话——
设备把 `USER/PASS/RETR` 打到串口上,客户端退出时还多打一行 `[ERR] [FTP] error reading
next command`。放在 `open/clear`(采集武装)之后,这些行就落进本次启动日志:Forbidden
`[ERR]` 命中 → 本次 FAIL,开机耗时从 612 ms 被拉到 **7997 ms**。所以基线固定在 open 之前,
并把这条写进文档当规避口径。

**判定表**(`docs/测试说明/自动化测试流程.md` §自检结果判定整段重写):无此文件=PASS;
本次无新增=PASS;本次有新增=FAIL;连不上/登录被拒/FTP 故障=**FAIL**;既无 `device_ip`
又无 `--selftest-log`=**FAIL**(要求判定却没有数据源,不能再"视为无记录");基线最后一条
被 512 条环覆盖(更新量 ≥ 环容)= **FAIL**(不拿"看不到"当"没有")。后四种"判不了"由调用
方在 `verify()` 之后强制收口成 RC=1——否则标记全中就把"没判"读成"判过了、没问题"。

`--selftest-scope all` 保留旧的累计口径(审计 / 第一次建基线用);给了 `--selftest-log`
(本地副本)就没有基线可比 → 自动降级为 all 并打印一行说明;FTP 凭据 `--ftp-user`/
`--ftp-pass`(config `ftp_user`/`ftp_pass`,默认出厂 admin/admin)。退出码 1 的含义、
`verify_log.py` 的语义条目、SKILL.md 参数表(+2 行)、config.example.json(+3 键)同步。

**真机三跑,三条路径各自验过**:
· 默认 this-run + `--device-ip` → **PASS / RC=0**:`基线 29 条 → 台账不变`、boot banner ×1、
  `net init: OK` **612 ms**(证明污染已消除)、Forbidden 零命中。
· `--selftest-scope all` → **FAIL / RC=1**:`29 fault record(s)`(累计口径仍可用)。
· 不给 `--device-ip` → **FAIL / RC=1**:`verify_log` 那侧显示 `no fault record → PASS`,
  调用方收口为 `RUN_TEST_VERDICT: FAIL —— 自检台账判定未能执行`(正是要的行为)。

门禁:`py_compile`(run_boot_test + verify_log)通过;`check_doc_links.py --all
--sections-strict` 146 文档 0 问题;`check_ids.py --strict` 增量拦截通过(可判定 82.82%,
在地板之上);6 个文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格。

**仍待办(本笔未做)**:设备侧"FTP 客户端中途退出"按 ERROR 级打日志(要不要降级);lftpd
的 LIST 传完回 550 而非 226;设备 `/` 残留约 24 个旧探针文件;`MDK-ARM/` 下 4 个一次性
探针脚本仍写匿名。未推送。
2026-10-22 22:19:51 +08:00
wandering1 15d01d4dc1 fix(测试工具): FTP 脚本与 @test 抓取改用设备鉴权账号(修 11 天假绿)
设备 FTP 自鉴权落地起就**没有匿名通道**(实测 `USER anonymous` → 530、`admin`+错口令 →
530、`admin`/`admin` → 230;`lftpd` 源码里也没有 anonymous 特判),但三个老主机脚本、
`@test` 的自检记录抓取、以及测试指南都还写着匿名——同目录的 `ftp_auth_bench.py` /
`ftp_abort_probe.py` 反而早就记着"匿名一律 530",可见当时只改了新增件。

**改动**:
· `test/host/ftp_test.py`、`ftp_up_dl_test.py`、`ftp_speed_test.py`:写死的
  `USER anonymous` / `PASS test@test.com` 改为 `--user` / `--password`(默认出厂
  admin/admin);**USER 的期望码从 230 修成"230 或 331"**——要口令的服务器在 USER 后
  回 331,脚本原先要求 USER 直接给 230,这是第二个 bug(就算口令对也过不了);登录
  失败时打印口径提示(`/config.ini` 的 `[ftp]`、无匿名通道、连续 5 次失败锁 60 秒)。
· `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`:`ftp.login()`(匿名)→
  `ftp.login(user, pass)`,新增 `--ftp-user/--ftp-pass` 与 config 的 `ftp_user`/
  `ftp_pass`(默认 admin);函数文档与 `--device-ip` 的 help 去掉 "(anonymous)"。
· `docs/测试说明/FTP_Test_Guide.md`:§1 的"匿名登录(`USER anonymous` / `PASS` 任意)"
  改为鉴权口径(`user` / `pwd_hash` = SHA-256(user‖password) / `write`,出厂
  admin/admin,默认口令在用有启动告警,5 次失败锁 60 秒,**无匿名通道**),§4 参数说明
  补两个新参数,§4.1 期望改为 `USER admin`→331、`PASS admin`→230,FileZilla 的用户
  由 `anonymous` 改 `admin`。

**这才是真正的危害**:`@test --require-selftest-pass` 的抓取走匿名登录 → 必然 530 → 落
进 `RUN_TEST_WARN … treated as no record (S7 PASS path)` → **每次都判 PASS**。也就是
说这条"联合判定自检记录"的门禁从写下那天起就没真正看过文件(假绿)。修完凭据后第一次
真抓到 `/log/selftest.log`(1856 B),立刻报出 29 条故障记录——而它们的**时间戳全是
2026-10-10 ~ 10-14**,没有一条来自本次启动:`/log/selftest.log` 是 512 条环形累计历史,
而 S7 的判据是"有任何 64B 记录就 FAIL" → **在任何做过故障注入或长跑的设备上,这条
门禁都不可能 PASS**。判据本身要改成"只看本次启动新增",已单独提出,不在本笔。

**验证(全部真机实跑)**:`ftp_up_dl_test.py` → TEST PASSED(10 项全过,含 STOR/LIST/
RETR/逐字节比对/DELE);`ftp_speed_test.py --size 128` → TEST PASSED(上行 225.8 KB/s、
下行 250.9 KB/s,131,072 B 逐字节一致);`@test` → PASS(625 ms 到 `net init: OK`);
`@test --require-selftest-pass --device-ip 192.168.1.100` →
`SELFTEST_FETCH: … (1856 bytes)`,抓取路径真跑通(旧代码在此步必 530)。
`py_compile` 四个脚本全过;`check_doc_links.py --all --sections-strict` 146 文档 0 问题;
`check_ids.py --strict` 增量拦截通过;5 个文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 /
无行尾空格。

**顺带暴露、未在本笔处理**:① `ftp_test.py` 的 `LIST 150->226` 断言失败——lftpd 的 LIST
数据本身正确(1512 B 全收到),但传输结束后控制通道回的是 **550** 而不是 226,需单独查
收尾路径;② 设备 `/` 根目录残留约 24 个旧探针文件(AB*/E12A/SZ*/A000x/R000x/DIAG_A);
③ `MDK-ARM/` 下 4 个一次性探针脚本仍写匿名,现在必然登录不上。未推送。
2026-10-22 21:39:25 +08:00
wandering1 172b3f4a20 fix(sim): UI 回归改为自洽运行——修 netcfg_save 假失败
现象:check_content_pages.py 只有 netcfg_save 一页 FAIL(保存行值列
显示 ERR 而非 OK),其余 10 页像素与两处往返池水位全过。

真因不是代码回归:模拟器按**当前工作目录**读写 sim_config.ini,而
--cfgtest 的 E-01 场景 2 会伪造一份 4000 B 纯注释"雷"文件、并断言
"拒写后原文件不动"(size == 4000)——连正常通过路径也不删除它。
实测仓库根那份是 2026-10-11 08:37 留下的。之后每次运行都会加载它
→ 外科式保存要在 4000 B 注释后补写受管段 → 输出必超
CFG_FILE_MAX_LEN → 引擎按设计拒写 → 保存行显示 ERR,与"工厂默认
态"的黄金参考对不上。引擎行为是对的,错在自检留雷 + 回归不自洽。

修三处:
- simulator/main.c:run_cfgtest 拆成 scenarios + 外壳,无论成败一律
  cfg_store_remove 配置路径与临时路径,让配置回到"无文件 = 工厂默认"
- check_menu_rows.py / check_content_pages.py:以空临时目录当工作
  目录跑 lvgl_sim(cwd=tmp),调用目录里的残留档不再影响结果
- check_content_pages.py:作废"删 sim_netcfg.bin 残留"的旧兼容清理
  (旧档迁移 2026-10-10 已删),改为只提示、不删别人的档

验证:--cfgtest PASS 且跑完不再留下 sim_config.ini;
check_menu_rows.py PASS(8 状态 + 池基线 5,368 B);
check_content_pages.py "PASS: 11 内容页 + 往返池水位全部通过",
往返 5,448 B。**未刷任何黄金参考**——参考本来就是对的。顺带删掉
本地两份 4000 B 残留(仓库根与 build/Debug,均 gitignore 产物)。

未跑 @build:本次只改 simulator/ 侧代码,固件工程无涉及;模拟器
CMake 构建 0 错 0 警,cppcheck 钩子覆盖 simulator/main.c。

文档:UI_自动化测试流程 §五 加排查行 + 变更记录;simulator/README
§3.7 说明删除保证与原因;lvgl-simulator skill 同步;订正审查报告
R-9 那条把该差异误归给 cfg_file 改动的表述。
2026-10-15 10:05:37 +08:00
wandering1 a7f526569a perf(mem): R-9 落地——LVGL 池 40→16 KB,SRAM1 74.9%→56.1%
取值依据是实测水位,不是预估:菜单根框 5,368 B、开页-关页往返
5,448 B、最重页面峰值 6,568 B(占 16 KB 的 40%)、真机开机
used=3,192 B。注意 16,384 B 建池后 lv_mem_monitor 报的 total 是
15,020 B(TLSF 控制块与对齐元数据 ≈1,364 B),核算余量用后者。

两端 lv_conf.h 同步改值(lv-conf-sync 生效值比对 163 项 PASS),
Lib/lv_conf.h 的 UTF-8 BOM 已按原样补回(Edit 会静默剥掉)。

验证:
- @build 0 错 0 警;ZI 154,456 → 129,880,精确 −24,576 B
- SRAM1 98,144/74.9% → 73,568/56.1%(余 57,504 B);CCM 不变 87.9%
- 模拟器菜单回归 PASS(8 状态像素 + 池基线 5,368 B 未变);11 页
  黄金参考逐字节 10/11 一致,往返池水位 5,448 B 分毫未变
- 唯一差异 netcfg_save 保存态:用 40 KB 旧池做 A/B 复测同样失败,
  确认与本改动无关(属 506eccc 的 cfg_file 行为变化),未刷黄金
  参考掩盖,留给该改动属主处置
- @test PASS:fw build Oct 15 2026 07:31:34,无 [ERR]/Fault,
  开机 622 ms 到 net init: OK,menu build done: used=3192
- 本地 CI 全绿(L0 + L0.5,15 runner / 955 断言)
- 台账受管区与内存快照区已 --write 回填,mem-budget / mem-numbers
  由红转绿

连带结论:R-2 后半待迁体量 54,146 → 29,570 B,瓶颈由 SRAM1 换到
CCM(只剩 7,904 B);解锁链第 ① 步完成,下一步只差缩堆 56→32 KB
(B5 是反向扩容,缩回前须重跑 stack_report.py + 上板 min_ever)。

文档同步 20 处:本报告按修复即销项删除 R-9 行,并改写 §0.2/§0.3/
§0.4/§5.1/§5.2/§5.3/§6.4 与变更记录、去处表;RAM使用分析 §2.6.2
新增实测水位表、阶段 1.1 标已执行;台账 §3 解锁链、登记簿 N-27、
任务规划 README W3 行与链路图;UI 四篇、产品需求、内存内容分配
策略、运行与自检记录、项目部署环境要求、测试开发标准流程、
simulator/README、ui-verify skill 常量。顺带订正三处与脚本不符的
旧值:池水位常量 4784/4864 → 5368/5448、FreeRTOS 堆 48 → 56 KB。
2026-10-15 09:56:15 +08:00
wandering1 803f7717ed chore(skills): 纳入 template-skill 骨架(DSH 建 skill 的空白模板)
.dsh/skills/ 是入库的项目知识(.gitignore 用 `!.dsh/skills/` 显式放行),
此前该目录未被跟踪,现按同一口径纳入。

内容为 DSH 新建 skill 的空白骨架:description 仍是占位文案、正文未填。
后续要落地具体 skill 时直接改写本文件;确认不需要则删目录即可。
2026-10-14 23:22:26 +08:00
wandering1 e5d3dc1205 fix(task-worktree): build/bench 分锁 TTL + done 自动接管陈旧锁
A:LOCK_TTL_HOURS 由全局 6h 改为按锁分设(build 1h / bench 6h)——
   兜底窗口应与操作真实时长同量级:编译是秒~分钟级,6h 太钝。
   并支持 lock acquire --ttl 自报窗口(keil_build 按其 --timeout 自报,
   timeout/3600+15min,下限 0.5h),使 build 锁窗口自动跟随构建上限。
B:done 收口前置把陈旧锁视为空闲并自动释放(原为硬失败),
   仅当锁仍被有效占用时才拒绝。

新增 lock_ttl_hours / lock_is_stale / release_stale_locks 三个辅助;
lock status 显示各锁 TTL;cmd_done 改调 release_stale_locks。

验证:20 项 TTL 探针 + 5 项 lock_cli 接线探针全绿;
假 UV4 端到端(exit 0 自持自释、payload ttl=0.5)与 busy 路径(exit 4)。
2026-10-12 18:59:49 +08:00
wandering1 3c7ad9c75c fix(skill): 修正 @build 退出码口径不符(环境错误 1→2、用法 2→3)
`AGENTS.md` 与 `keil-build` / `new-machine-setup` 的 SKILL.md 一直写着
"2=UV4 未找到/无效(环境错误)、3=用法",但实现是反的:
- 环境错误走 `raise SystemExit("...")` → **实际退出码 1**(与"构建失败"
  撞号,调用方分不清"编译不过"和"UV4 没配好");
- 用法错误由 argparse 抛 `SystemExit(2)` → 恰好占用了文档里"环境错误"
  的位置,于是 `new-machine-setup` 里"报 exit 2 先分清真没设还是没继承"
  这条排障指引**从未生效**。

改法(让实现服从既有文档口径,而非改文档):
- 新增 `BuildEnvError`;`find_uv4` / `find_project` / `load_config` 失败
  一律抛它,由 `main()` 捕获 → 打印 + 退出码 **2**;
- 覆盖 argparse 的 `error()`(默认 `SystemExit(2)`)→ 用法错误退出码 **3**;
- `run_build` 的档案解析失败原为 `return 3`(用法),实属环境错误 → 并入 2。

调用方安全性:`test/run_all_suites.py` 与 `task-worktree` 的 `run_script`
都只判 `rc == 0` / `rc != 0`,不区分具体码,故本次改动不影响它们。

验证(全部实测,用假 UV4 避免与在途构建争 UV4 与 build 锁):
| 场景 | 期望 | 实测 |
|---|---|---|
| `--help` | 0 | 0 |
| 成功构建(假 UV4 写 0E0W) | 0 | 0(BUILD_OK) |
| 构建失败(假 UV4 写 `error:`) | 1 | 1(BUILD_FAIL) |
| `--project` 不存在 | 2 | 2 |
| 档案 `uvprojx` 不存在 | 2 | 2 |
| 档案 JSON 解析失败 | 2 | 2 |
| 未知参数 `--bogus` | 3 | 3 |
| 参数缺值 `--timeout` | 3 | 3 |
| build 锁被占用 | 4 | 4(B9 在构建时实测) |

同步更新 `AGENTS.md` 的退出码行(补 2 的完整含义与 4)与 `keil-build`
SKILL.md(记录本次修正的来龙去脉)。pre-commit 全绿。
2026-10-12 18:49:24 +08:00
wandering1 1b450be0ef fix(skill): @build 自持自释 build 锁,根治忘记 release 的泄漏
问题:锁不绑定进程存活(`lock acquire` 是短命进程,真正用 UV4 的是另一个
进程),所以释放只能靠调用方记得。Agent 手工 `lock acquire build` 后忘
了 `release`,锁要挂到 6 小时 TTL 才被下次 acquire 接管——期间其他任务
要么立即失败、要么干等。`done` 自己已用 try/finally 兜住,但手工路径没有。

改法:让**用资源的人**持锁。`keil_build.py` 进入 acquire、`finally`
**无条件** release(构建失败 / 异常 / SystemExit 都会释放)。
- `--lock-task`:持有者名。默认 `$DTU_TASK_ID` → 从 cwd 的
  `_wt/<ID>-<slug>/` 反推 `<ID>` → `build-cli`。反推意味着"手工
  `lock acquire build --task <ID>` 后再跑 @build"仍幂等,不会自我死锁。
- `--lock-wait N`:锁被占时等待(默认 0 = 立即失败)。
- `--no-lock`:完全不加锁。
- 新增退出码 **4 = build 锁被占用**(原先无此语义,占用会直接去构建)。
- 锁工具 = 兄弟 skill `task-worktree`;**找不到则降级为不加锁并告警**,
  保持本 skill 可独立搬运。

`done` 的兼容处理:它自己持锁,给子进程设 `DTU_LOCK_HELD=build` 告知
跳过自持自释。**对"工作树里还是旧 keil_build.py"的在途任务同样安全**
——旧版根本不看该变量、也不会自锁,故不会出现"双重持锁"死锁。

`bench` 锁**不动**:它是**会话作用域**(跨烧录→@test→探针多条命令),
逐命令自动释放会让别的任务插进来扰动设备,必须继续由 Agent 持有。

验证(主工作树实测):
- 自持自释:acquire → BUILD_OK → release,前后 `lock status` 均空闲;
- 被占:由 other-task 占锁 → exit 4,且**未触发构建**;
- 调用方持锁:`DTU_LOCK_HELD=build` → 打印"跳过自持自释",且
  **未释放调用方的锁**(锁仍归 __done__)——关键安全性质;
- 错误路径:无调用方持锁 + `--project` 不存在 → 失败但 **finally 已释放**;
- 持有者名推导:主工作树 → `build-cli`,`_wt/N33-stack-margins` → `N33`;
- 门禁:`mem-numbers` 一致;pre-commit 全绿。
2026-10-12 18:07:58 +08:00
wandering1 82cedcadb7 docs(skill): task-worktree 补 done 的三个实测陷阱
1) done 的门禁跑在 @build 之前 → mem-budget/mem-numbers 拿旧 .map 比对新台账
   必然误报 FAIL;处置为主工作树 --write 后追加提交再重跑 done
2) 增量构建可能不重链 → .map 数字瞬态不一致(实测同源两次读到 223,560/223,724,
   差 164B);以主工作树(集成点)为准
3) 合并冲突常落在生成物(RAM 快照)上,两边都不能直接选,须按合并后实际构建重生成
2026-10-12 17:51:09 +08:00
wandering1 d3107f374f docs: 待处理内容.md 解耦为 docs/任务规划/ 文件夹(README 排序权威 + 6 个职责单一文件)
- rename_ref.py 搬迁:git mv + 全库引用改写 33 文件 82 处
- README.md 保留 §0 编号空间 / §1 未闭环一览 / §2 推进顺序权威 / §3 维护约定
- 拆出 6 文件:W1-采集保护收尾(任务卡五字段)、排期详述、暂缓-RS485、
  登记簿、测试与架构专项、基线与待确认
- 外部引用重定位 ~50 处:裸「登记簿 §X」、§5.1→基线与待确认 §2.1、
  §2.4→暂缓-RS485、§6→基线与待确认 §3、脚本注释 T-x/N-x→登记簿
- 新增并行写纪律:任务分支不改本文件夹,状态列由 Lead 主工作树更新
- 验证:doc-links 141 文档全绿;mem-numbers 一致;refgraph 重生成;
  @build 0 错误 0 警告;新文件无 BOM / LF / 零乱码
2026-10-12 13:42:47 +08:00
wandering1 e4134a4723 feat(skill): 新增 task-worktree 多任务并行工作树 skill
用 git worktree 给每个任务开独立工作空间,干完由固定命令合回主干,
解决多任务共用同一工作目录时构建产物/日志/登记簿互相踩的问题。

- 机制:worktree + 任务分支 task/<ID>-<slug>(ID 沿用待处理内容编号)
- 目录:仓库同级 _wt/;.venv 与 simulator/SDL2 以 junction 共享
- .workbuddy-ai/ 刻意独占,清场前归并回主工作树
- 命令:new/list/status/sync/lock/done/abort(短命令 @task)
- 锁:build(UV4 不能并发)+ bench(COM6/ST-LINK/设备 IP),
  按时效 TTL 判定,不绑定进程存活
- done:merge-tree 预演 -> merge --no-ff -> 残留审计 -> 重生成产物
  -> 门禁 -> 清场;门禁未绿则保留工作树;不含 push
- 隔离演练(DTU_INTEGRATION_WORKTREE)暴露并修复 4 个缺陷,
  见 docs/环境与部署/并行任务工作树方案.md §11.1

同步:AGENTS.md 主题表、docs/README.md §9.2 与变更记录。
2026-10-12 11:56:13 +08:00
wandering1 d1e6bb4ba7 W5波+N-19收口:FTP鉴权落地、Trap25三连定案修复(N-30)、N-20/N-23/N-26
N-19 FTP 鉴权:
- lftpd_auth(USER/PASS 状态机 + 连续5次失败锁60s)+ SHA-256
  (user||pwd) 口令哈希;config.ini [ftp] 段 user/pwd_hash/write,
  出厂 admin/admin 在用有启动告警;lftpd_log.c 删除并入 lftpd_auth
- ftp_cfg 配置读写;台架 ftp_auth_bench.py 13/13

N-30 / Trap 25(CH395F_Trap_Records.md 四次定案全记录):
- 修复1 closed-with-data accept:客户端发完正常 FIN 后芯片按
  FUN_PARA(0x09) §5.52② 保留 Socket+RX 缓冲,旧代码把 CLOSED 一律
  relisten 自毁缓冲 → STOR 必挂 425;改为接受"已关但可读"连接排空
- 修复2 丢 CONNECT 中断探针 net_lost_connect_probe + last_peer_port
  幽灵甄别(拆链窗口握手完成但中断丢失,双信源去抖补 accept)
- 修复3 send_list/send_nlst 批量单发:背靠背 ~60B 小段毒化芯片 TX
  (芯片报 SEND_OK 但对端全丢),整份目录一次性写入
- 证伪存档:固定沉降/Socket 轮换冷却/listen 门控(台架 v7 矩阵)
- 台架工具 test/board/ch395f_reuse_bench.{c,h} + host 脚本存档
  (不注册编译;实验分支 n30-chip-bench 保留)

N-20:Error_Handler 死循环降级(记录+复位路径)
N-23:scripts/check_registration.py 双端注册反向校验(pre-commit)
N-26:GD5F NAND 直 DMA 调用方缓冲窗口断言

验证:BUILD_OK 0/0;ftp_regression 连跑 4 轮 11/11(修复前
LIST#4/#7 100% 挂、STOR#2 曾 100% 425);ftp_auth_bench 13/13;
@test 启动标记 PASS;ci_local L0+L0.5 全过;doc-links 通过。
登记簿 N-30 已销项。
2026-10-11 18:50:24 +08:00
wandering1 eddbedeee8 docs(AGENTS): 入口重构——补宏观概况与边界块,瘦身51KB→25.8KB,子域知识入skills
CI / L0 静态门禁 (pre-commit) (push) Has been cancelled
CI / L0.5 主机侧单测 (MSVC) (push) Has been cancelled
- AGENTS.md 51,067→25,828 B(−50%):审计→迁移→删除,逐节 diff 零信息损失
- 按调研(agents.md 官方/aihero/airflow)补「项目概况」:一句话定位、
  数据主线、顶层目录地图、分层边界;平台约束 17 条分 5 组
- 新增「边界(Ask first / Never)」:git commit/push 须明确指示首次入库
- 串口助手、LVGL 模拟器两节移入 serial-assistant / 新建 lvgl-simulator skill
  (开关语义按 main.c 实锤);LVGL 两条实证坑移入 ui-verify skill
- 9 条入口独有细节迁移至对应设计文档(存储/看门狗/启动顺序/文档规范/
  pre-commit 使用说明/keil-build skill);@test 细节指向自动化测试流程
- 新增 docs/AGENTS编写指南.md:六原则/模板/反模式/维护纪律 + 本仓案例
- 修正 3 处对 AGENTS 串口节的过时引用;docs/README.md 登记新文档
2026-10-11 10:44:50 +08:00
wandering1 5009a844a9 收敛 reset.bat 到 stlink-flash skill 并删除该批处理
CI / L0 静态门禁 (pre-commit) (push) Has been cancelled
CI / L0.5 主机侧单测 (MSVC) (push) Has been cancelled
stlink_flash.py 新增 hard 子命令(-c SWD RM=Hrst -HardRst PULSE=100),
RM= 被 CLI 拒绝时自动降级为系统复位并打印 RESET_NOTE;至此 reset.bat 的
全部子命令(复位/hard/halt/run/list/flash/cmp)均已由 skill 覆盖。

删除 MDK-ARM/reset.bat(git rm)。@build/@download 两条旧批处理入口至此
全部收敛到 .dsh/skills/ 下的 skill,一个能力一个实现。

同步更新引用:AGENTS.md、README、MDK-ARM/README、7 篇 docs、4 个 skill
(stlink-flash/serial-boot-test/new-machine-setup 及 config.example.json)。

真机验证:hard 子命令 RM= 被拒 → RESET_NOTE → 回退系统复位 → STLINK_RC=0。
2026-10-10 16:41:46 +08:00
wandering1 c77c5824f9 build: 删除 MDK-ARM/build.bat,@build 完全收敛到 keil-build skill
- test/run_all_suites.py 是唯一真正执行它的地方:BUILD_BAT 常量改为
  KEIL_BUILD(.dsh/skills/keil-build/scripts/keil_build.py);两处构建点
  改传 --timeout(脚本内超时先于 _run 超时触发,超时得 rc=1 + BUILD_FAIL
  文案,而不是被 _run 杀掉)
- AGENTS.md / keil-build SKILL.md / new-machine-setup SKILL.md:删除
  "等价原生入口"口径,改为注明该文件已于 2026-10-10 删除
- 环境与部署 3 篇 / 项目架构总览 / README / MDK-ARM/README /
  download.bat 注释 / check_mem_numbers.py 提示串:移除 build.bat 引用
- 刻意未改:docs/问题记录/项目审查与优化建议报告.md 的 P2-14 / N-18
  (历史审计条目;doc-links 不校验裸文件名,保留不影响门禁)
- 验证:keil_build.py --timeout 300 -> BUILD_OK / rc=0;
  run_all_suites.py --dry-run 正确解析 skill 路径;doc-links 123 文档通过
2026-10-10 16:23:54 +08:00
wandering1 7a54fdbc73 docs(build): @build 统一为 keil-build skill,build.bat 降为等价原生入口
- AGENTS.md 构建条:@build = keil-build skill(写明 UV4 解析链、退出码
  0/1/2/3、日志路径);MDK-ARM/build.bat 明确为等价原生入口,并列出
  它相对 skill 缺的三项(无超时保护 / UV4 只认 DTU_KEIL_UV4 / 失败 dump 整份日志)
- keil-build SKILL.md「本仓库」段同步:skill 即 @build 口径
- 3 个 skill(new-source-file / ui-verify / new-machine-setup)与 8 篇
  docs、README、MDK-ARM/README、环境与部署 3 篇:命令示例与 @build
  标识统一改指 skill
- scripts/stack_report.py 的构建提示串改指 skill
- 环境与部署:DTU_KEIL_UV4 使用方补 keil_build.py;反向测试期望的报错
  文案对齐 skill 实际输出(BUILD_ENV_ERROR: ... is set but not a file)
- 刻意未改:docs/问题记录/项目审查与优化建议报告.md 的 P2-14/N-18
  历史审计条目(属史实记录);test/run_all_suites.py 的 BUILD_BAT
  常量(是代码、非文档,改动需另做验证)
2026-10-10 16:19:14 +08:00
wandering1 b80f59cc5d fix(bench): ST-LINK_CLI 子进程加 stdin=DEVNULL,闭环不再挂死
- stlink_flash.py::run_cli 与 run_boot_test.py::_run_cmd 调 subprocess.run
  时未给 stdin=DEVNULL:ST-LINK_CLI 编程完成后停在 "run application to
  exit" 等 stdin,stdin 是未关闭管道即永久挂起(编程其实已成功、板子已
  复位运行)→ 闭环误判 flash rc=-1(180s 超时)
- 归档 ADC 1kHz 台架证据 boot_log_data/20261010_prod_adc_tim6_boot_log.txt
  并在 README 清单登记
- 同步 ADC采样节拍设计说明(状态→通过台架验收、新增 §7.1 实测)
  与 继电保护功能复现规划 L1-1
2026-10-10 15:46:21 +08:00
wandering1 f0300761ae 串口采集/测试去重:删 tools/serial_tool 与 test/host run_boot_test/verify_log 副本,引用改指 .dsh/skills 串口 skill 2026-10-10 15:13:23 +08:00
wandering1 7c4b724057 新增 cubemx-regen、board-test-suite、new-machine-setup、mem-budget 四个项目级 skill 2026-10-10 14:41:30 +08:00
wandering1 aac5f99fe9 移除 OpenCode 配置与命令,技能与串口单一配置源迁移至 .dsh/skills 与 .serialtest,同步更新全部文档引用 2026-10-10 14:05:36 +08:00