diff --git a/.dsh/skills/mem-budget/SKILL.md b/.dsh/skills/mem-budget/SKILL.md index 74cbcd38..74d7dc31 100644 --- a/.dsh/skills/mem-budget/SKILL.md +++ b/.dsh/skills/mem-budget/SKILL.md @@ -178,7 +178,7 @@ git add docs/内存管理/RAM预算台账.md docs/内存管理/RAM使用分析 - **`--gate` 无 .map → SKIP,而脚本自己写明「SKIP 不等于 PASS」**(`ram_baseline.py:177`): 新开的 task-worktree 从没在这棵树上构建过时必然走到这里;同一状态下 `--write` 直接 `[FAIL] 找不到 .map 事实源,先 @build`(`ram_baseline.py:250`)——不是代码缺陷,是这棵树还没测过体积。 - 无需 Keil 的强制兜底是 `scripts/check_size_gate.py --local`(= `scripts/ci_local.py` 的 L0c 步)与 CI 的 `--ci`。 + 无需 Keil 的强制兜底是 `scripts/check_size_gate.py --local`(= `scripts/ci_local.py` 的体积门禁步)与 CI 的 `--ci`。 - **`--map ` 只换读、不换写**:`LEDGER`(`ram_baseline.py:45`)与 `TARGETS`(`check_mem_numbers.py:53-54`) 都由 `REPO_ROOT = Path(__file__).resolve().parent.parent` 算出。拿主仓脚本配 `--map <另一棵树>/…map` 去 `--write`, 等于用别的树的数字**改写主仓的台账**——比"半棵树混合数字"更糟,因为它落盘了。回填永远用本树自己的脚本。 @@ -187,7 +187,7 @@ git add docs/内存管理/RAM预算台账.md docs/内存管理/RAM使用分析 (Edit 精确替换;含中文的文档绝不整文件重写)。 - **`--update` 的盖戳排在索引后面**:它顺带调 `check_size_gate.cmd_stamp()`(`ram_baseline.py:345`), 而凭证指纹按 **git 索引内容**生成,有未 staged 的脏改动时**拒盖**,且这里只 `[WARN]`、不改返回值 - → 现象是 tsv 多了一行而凭证仍是旧的,要等 `ci_local` L0c / CI 体积门禁那一侧才报出来。 + → 现象是 tsv 多了一行而凭证仍是旧的,要等 `ci_local` 的体积门禁步 / CI 体积门禁那一侧才报出来。 要么先 `git add` 再 `--update`,要么补跑 `python scripts/check_size_gate.py --stamp`。 - **改写型钩子当场改文件是流程不是故障**:`trailing-whitespace` / `end-of-file-fixer` 报 `files were modified by this hook`(新建的 .c 最常触发)时,先读 `ci-artifacts/cui.log` 的真实报错确认是哪几条红, diff --git a/.dsh/skills/task-worktree/SKILL.md b/.dsh/skills/task-worktree/SKILL.md index e1bf06a7..e95b8862 100644 --- a/.dsh/skills/task-worktree/SKILL.md +++ b/.dsh/skills/task-worktree/SKILL.md @@ -158,7 +158,7 @@ acquire 的陈旧接管,管不到台账缺失。而 `acquire` 侧**不查台 - ✅ 走提示里的 **a) 在主工作树修正后追加提交**; - ❌ **绝不用 b) 的 `git -C "<主树>" reset --hard `**——那是丢弃已入库的合并提交,而 `reset --hard` 本身是项目级 Never(会连未提交工作一起丢)。提示 b 只是列出的可能性,不是建议。 - - 先定位是**哪条钩子**红:门禁输出里的 `[SUMMARY] L0: pre-commit hooks -- passed=N failed=M`, + - 先定位是**哪条钩子**红:门禁输出里的 `[SUMMARY] static: pre-commit hooks -- passed=N failed=M`, 不同根因处置不同(见陷阱「`done` 的门禁 = `pre-commit run --all-files`」)。最常态是 `.map` 滞后 → 第 5 步。 5. 补绿固定链(2026-10-25 实测;顺序即因果,全在**主工作树**跑,逐条不接管道看退出码): @@ -282,14 +282,14 @@ git -C "$MAIN" status --porcelain | sort # 与测量前**逐行一致**( `check_doc_links.py --all` → 提交 → 重跑 `done`(它会检测到「已合入」直接清场)。 此时主干已含合并提交,**绝不能用提示里的 `reset --hard` 回退**。 - **`done` 的门禁 = `ci_local.py` = `pre-commit run --all-files`(50 条钩子全跑)**(2026-10-26):红了先读 - `[SUMMARY] L0: pre-commit hooks -- passed=N failed=M skipped=K`,**按钩子 id 定根因**,别一律当「`.map` 滞后」 + `[SUMMARY] static: pre-commit hooks -- passed=N failed=M skipped=K`,**按钩子 id 定根因**,别一律当「`.map` 滞后」 去跑补基线链。典型例外:`generated-fresh`(生成器幂等性,触发面 `tools/**`、`docs/images/*.svg`、UI 生成文件)—— 合进来的分支改了生成器却没重跑产物时,补基线链救不了,处置是在**主工作树重跑那个生成器**、脚本与产物同一笔提交, 再重跑 `done`。另注意 `pre-commit run --all-files` 只跑 pre-commit stage,个别只在 commit-msg / 手工阶段跑的守卫不在此列 (口径见 `scripts/ci_local.py` 头部说明)。 - **⚠ 新建工作树从未构建过 → 体积类门禁必然红**(2026-10-25 实测):`.map` 是每棵树各自的 构建产物、不入版本库,所以 `new` 出来的空间里 `mem-numbers` / `mem-budget` / `ci_local.py` - 的 L0c 都拿不到事实源,报 `[FAIL] 找不到 .map 事实源`(脚本提示已写明原因,不是代码缺陷)。 + 的体积门禁步都拿不到事实源,报 `[FAIL] 找不到 .map 事实源`(脚本提示已写明原因,不是代码缺陷)。 同理 `done` 的重生成步骤会 `[skip] 未找到 .map,跳过内存快照同步(需先 @build)`。 **处置**:新空间第一次提交前先 `@build` 一次(`build` 锁自动排队,与别的树串行)。与上一条 的区别要分清:上一条是「有 `.map` 但是旧的」,本条是「根本没有 `.map`」,处置动作不同。 diff --git a/.dsh/skills/task-worktree/scripts/task_worktree.py b/.dsh/skills/task-worktree/scripts/task_worktree.py index 6f5ab81d..03834381 100644 --- a/.dsh/skills/task-worktree/scripts/task_worktree.py +++ b/.dsh/skills/task-worktree/scripts/task_worktree.py @@ -402,7 +402,7 @@ def regen(cwd: Path) -> None: def run_gates(cwd: Path, skip_build: bool) -> bool: - ok = run_script("scripts/ci_local.py", [], cwd, "L0 + L0.5 本地等价 CI") + ok = run_script("scripts/ci_local.py", [], cwd, "静态门禁 + 主机侧纯逻辑单测 本地等价 CI") if skip_build: print("[skip] 按 --skip-build 跳过 @build") return ok diff --git a/.dsh/skills/ui-verify/SKILL.md b/.dsh/skills/ui-verify/SKILL.md index 163afaba..be285fc8 100644 --- a/.dsh/skills/ui-verify/SKILL.md +++ b/.dsh/skills/ui-verify/SKILL.md @@ -47,7 +47,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警 ## 关联注意 -- `.venv` 缺失/换机:`& <本机任一 python ≥3.10> -m venv .venv; & '.venv\Scripts\python.exe' -m pip install -r requirements.txt`(`requirements.txt` 已含 `pre-commit`,重建后 L0 门禁直接可跑) +- `.venv` 缺失/换机:`& <本机任一 python ≥3.10> -m venv .venv; & '.venv\Scripts\python.exe' -m pip install -r requirements.txt`(`requirements.txt` 已含 `pre-commit`,重建后静态门禁直接可跑) - 新增 UI `.c` 文件先走 `new-source-file` skill 双端注册,否则一端静默缺文件 - 改 `ui_menu_tree.txt` 或界面里的中文字符串 → **同一笔**重跑生成器(`tools/menu_gen/gen_menu.py`;字体走 **font-regen** 四步),否则提交门禁 `generated-fresh` 直接拦:它把生成器拷进系统临时目录重跑、与工作区逐字节比对(实证 `0f7b5a87` 改了模板没重跑)→ `docs/门禁说明/generated-fresh.md` - 字体重生成后全页大面积像素差异属预期:确认字形正确后 `--update` 刷参考 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index f5b53ba0..c8396bca 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,14 +1,14 @@ # Gitea Actions CI —— STM32F4-Base 持续集成 # # 覆盖范围(**只做不依赖硬件的层**,见 docs/测试说明/测试开发标准流程.md §1/§9): -# L0 静态门禁 = pre-commit 全部钩子(等价本地 `pre-commit run --all-files`) -# L0.5 主机侧纯逻辑单测 = test/host/**(MSVC 离线编译,零硬件) +# 静态门禁 static = pre-commit 全部钩子(等价本地 `pre-commit run --all-files`) +# 主机侧纯逻辑单测 host-unit = test/host/**(MSVC 离线编译,零硬件) # -# ⛔ L1 板端套件(test/board/,需真机 + ST-LINK)/ L2 @test 闭环 / L3 人工目检 +# ⛔ 板端测试套件(test/board/,需真机 + ST-LINK)/ 主机侧回归(@test 闭环)/ 人工目检 # **不上 CI** —— 需要开发板与烧录器,只能在本地跑。 # # 明确不做的两件事(都是刻意为之): -# 1. `@build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且 L0 的 cppcheck + +# 1. `@build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且静态门禁的 cppcheck + # 代码规范已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(AGENTS.md)。 # 2. `mem-numbers` / `mem-budget` 两根钩子 —— 事实源都是 `MDK-ARM/**/*.map`(Keil # 构建产物、已 gitignore),无 `@build` 即无 `.map`。故本 workflow 在**检测不到 @@ -16,7 +16,7 @@ # # 🔴 体积门禁:盲区已关闭为**强制路径**(2026-10-25 起;原先只做到"声明") # 跳过钩子只解释了"runner 没有 Keil",没解决**"没跑 = 通过"**:钩子在无 .map 时 -# 自适应 SKIP 并返回 0,于是 CI 全绿仍可能被读成"体积也守住了"。现由 L0 作业**最后 +# 自适应 SKIP 并返回 0,于是 CI 全绿仍可能被读成"体积也守住了"。现由静态门禁作业**最后 # 一步** `python scripts/check_size_gate.py --ci` 在**不需要 Keil** 的前提下补两条判据: # · 同码性:`scripts/size_attestation.json` 的源码指纹必须等于本次检出树的指纹, # 而该凭证只能由"新鲜 .map + 当时未超线"的真实链接测量写出(见 --stamp 三道前置) @@ -59,7 +59,7 @@ # · 仓库 `.venv` 已 gitignore,runner 的全新工作区里**根本不存在**。 # 唯一可用的机器级解释器(`C:\ProgramData\anaconda3\python.exe`)不再被探测,而文档 # 要求的机器级 `DTU_PRECOMMIT_PYTHON` 从未设置(Machine 作用域一个 DTU_* 都没有) -# → run 14/15 的 L0 **全部死在"环境自检",一个钩子都没跑**。cppcheck 同理: +# → run 14/15 的静态门禁 **全部死在"环境自检",一个钩子都没跑**。cppcheck 同理: # 旧写法"不在 PATH 就把 C:\Program Files\Cppcheck 加进 PATH"的兜底也被删了。 # **故探测逻辑搬进 `scripts/ci_find_python.ps1`**(两个 job 共用、可离线跑、失败时 # 打印全部候选),并恢复常见机器级安装位置兜底;`DTU_*` 仍是最高优先级、 @@ -86,7 +86,7 @@ # - 该 Gitea 实例已启用 Actions:`[actions] ENABLED = true`(site admin 配置) # - 已注册一个 **Windows** runner(labels 含 `windows`),且装有: # Python 3.x、cppcheck、pre-commit、MSVC(VS2019/2022 的 C++ 工具链 + Windows SDK) -# - 缺 MSVC 时 L0.5 步骤会**降级为提示并跳过**(不硬失败),但会打印醒目 WARNING +# - 缺 MSVC 时主机侧单测步骤会**降级为提示并跳过**(不硬失败),但会打印醒目 WARNING # # 🔧 runner 机器级环境变量(可选项覆盖;runner 以 SYSTEM 服务运行,用户级变量不可见): # DTU_PRECOMMIT_PYTHON / DTU_CPPCHECK / DTU_MSVC_CL —— 未设置时按 PATH / 标准 VS 布局自动发现。 @@ -119,10 +119,10 @@ concurrency: jobs: # ------------------------------------------------------------------ - # L0:静态门禁(pre-commit 钩子全集) + # 静态门禁(pre-commit 钩子全集) # ------------------------------------------------------------------ - l0-gates: - name: L0 静态门禁 (pre-commit) + static-gates: + name: 静态门禁 (pre-commit) runs-on: windows steps: - uses: actions/checkout@v4 @@ -141,7 +141,7 @@ jobs: # * both jobs share ONE implementation, # * it can be run (and its failure list inspected) offline, # * the well-known machine-wide install locations stay probed. - # The previous inline 2-candidate probe is what killed every L0 run + # The previous inline 2-candidate probe is what killed every static-gate run # since 33a4aa4 -- see the file header. # # The script prints diagnostics with Write-Host and emits ONLY the @@ -163,7 +163,7 @@ jobs: # The directory fallback is NOT cosmetic: the runner service runs as # SYSTEM, whose PATH has no Cppcheck entry (a USER-scope PATH entry is # invisible to it), and no machine-level DTU_CPPCHECK is set on a - # stock runner. Dropping this fallback in 33a4aa4 is what made L0 die + # stock runner. Dropping this fallback in 33a4aa4 is what made the static-gate job die # on cppcheck as well, right after the Python probe had been fixed. $found = $null $envCpp = $env:DTU_CPPCHECK @@ -266,14 +266,14 @@ jobs: Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 } else { Write-Host "::warning::no .map found (no Keil build) -- SKIPPING mem-numbers and mem-budget, so absolute Flash/RAM numbers are NOT recomputed here." - Write-Host "::warning::that gap is no longer silent: the final step of this job runs scripts/check_size_gate.py --ci (forced path: attestation parity + 92% budget re-check). Locally: scripts/ci_local.py step L0c." + Write-Host "::warning::that gap is no longer silent: the final step of this job runs scripts/check_size_gate.py --ci (forced path: attestation parity + 92% budget re-check). Locally: scripts/ci_local.py size step." "value=mem-numbers,mem-budget" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii "sizegate=off" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii "> :warning: Volume gates (mem-numbers, mem-budget): **NOT RUN** -- no `.map` (no Keil build); compensated by the size-gate step below." | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 } - - name: 运行 L0 门禁(--all-files) + - name: 运行静态门禁(--all-files) shell: powershell env: SKIP: ${{ steps.skip.outputs.value }} @@ -287,7 +287,7 @@ jobs: # would ABORT the pipeline. Relax the preference around the pipeline and # restore it afterwards (same pattern as the env-check step above). New-Item -ItemType Directory -Force -Path 'ci-artifacts' | Out-Null - $log = 'ci-artifacts\l0-pre-commit.log' + $log = 'ci-artifacts\static-pre-commit.log' if ($env:SKIP) { Write-Host "SKIP=$env:SKIP" } $saved = $ErrorActionPreference $ErrorActionPreference = 'Continue' @@ -301,17 +301,17 @@ jobs: $passed = @($lines | Select-String -Pattern 'Passed$').Count $failed = @($lines | Select-String -Pattern 'Failed$').Count $skipd = @($lines | Select-String -Pattern 'Skipped$').Count - $summary = "L0: pre-commit hooks -- passed=$passed failed=$failed skipped=$skipd (log: $log)" + $summary = "static: pre-commit hooks -- passed=$passed failed=$failed skipped=$skipd (log: $log)" Write-Host "::notice::$summary" - Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### L0 static gates" -Encoding utf8 + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### 静态门禁 (static gates)" -Encoding utf8 Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summary -Encoding utf8 if ($rc -ne 0) { - Write-Host "::error::L0 static gates failed (see output above and $log)" + Write-Host "::error::static gates failed (see output above and $log)" exit $rc } - - name: 归档 L0 日志(供下载/排障) + - name: 归档静态门禁日志(供下载/排障) if: always() shell: powershell run: | @@ -319,17 +319,17 @@ jobs: # actions/upload-artifact (inner networks may not fetch outer actions); # instead we keep the log on the runner AND surface its path + tail in # the step summary, so the UI is self-sufficient for triage. - if (Test-Path 'ci-artifacts\l0-pre-commit.log') { - $size = (Get-Item 'ci-artifacts\l0-pre-commit.log').Length - Write-Host "archived: ci-artifacts/l0-pre-commit.log ($size bytes)" + if (Test-Path 'ci-artifacts\static-pre-commit.log') { + $size = (Get-Item 'ci-artifacts\static-pre-commit.log').Length + Write-Host "archived: ci-artifacts/static-pre-commit.log ($size bytes)" Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "" -Encoding utf8 - Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Archived log: ``ci-artifacts/l0-pre-commit.log`` ($size bytes on runner)" -Encoding utf8 + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Archived log: ``ci-artifacts/static-pre-commit.log`` ($size bytes on runner)" -Encoding utf8 } - Write-Host "::notice::L0 artifacts kept on runner at: ci-artifacts\l0-pre-commit.log" + Write-Host "::notice::static-gate artifacts kept on runner at: ci-artifacts\static-pre-commit.log" # ------------------------------------------------------------ # 强制体积门禁(2026-10-25):不需要 Keil,也不需要 .map - # 放在 L0 作业**最后**且 if: always()——L0 失败时这条结论同样要看得到, + # 放在静态门禁作业**最后**且 if: always()——静态门禁失败时这条结论同样要看得到, # 而它自己失败会让整个作业变红(这正是"强制"的含义)。 - name: 体积门禁(强制路径:同码凭证 + 92% 红线,无需 Keil) if: always() @@ -393,10 +393,10 @@ jobs: } # ------------------------------------------------------------------ - # L0.5:主机侧纯逻辑单测(零硬件;缺 MSVC 则跳过) + # 主机侧纯逻辑单测(零硬件;缺 MSVC 则跳过) # ------------------------------------------------------------------ - l05-host-unit-tests: - name: L0.5 主机侧单测 (MSVC) + host-unit-tests: + name: 主机侧单测 (MSVC) runs-on: windows steps: - uses: actions/checkout@v4 @@ -454,7 +454,7 @@ jobs: if ($found) { "value=yes" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii } else { - Write-Host "::warning::runner has no MSVC (VS C++ toolchain) -- skipping L0.5. Install VS BuildTools and re-run." + Write-Host "::warning::runner has no MSVC (VS C++ toolchain) -- skipping host-side unit tests. Install VS BuildTools and re-run." "value=no" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii } @@ -463,9 +463,9 @@ jobs: if: steps.msvc.outputs.value == 'yes' shell: powershell run: | - # Resolve a working Python with the SAME shared probe as L0 (no + # Resolve a working Python with the SAME shared probe as the static-gate job (no # -NeedPreCommit: these harnesses are stdlib-only). The old inline - # probe here had the same 2-candidate blind spot as L0 -- it only + # probe here had the same 2-candidate blind spot as the static-gate job -- it only # survived by luck, because the stdlib tests also run on the msys64 # python that happens to be on SYSTEM's PATH. $py = & './scripts/ci_find_python.ps1' -WriteEnv @@ -491,12 +491,12 @@ jobs: $sumPass = 0 $sumTotal = 0 # Relax preference around each `2>&1 | Tee-Object` pipeline (native stderr - # becomes ErrorRecord -> 'Stop' would abort). See L0 step for details. + # becomes ErrorRecord -> 'Stop' would abort). See the static-gate steps for details. $savedPref = $ErrorActionPreference foreach ($r in $runners) { $runnerCount++ $name = $r.Directory.Name - $log = "ci-artifacts\l05-$name.log" + $log = "ci-artifacts\host-unit-$name.log" Write-Host "================================" Write-Host "run: $($r.FullName)" Write-Host "================================" @@ -522,19 +522,19 @@ jobs: } } - $agg = "L0.5: $runnerCount runner(s), $sumPass/$sumTotal assertions passed, fail=$fail" + $agg = "host-unit: $runnerCount runner(s), $sumPass/$sumTotal assertions passed, fail=$fail" Write-Host "::notice::$agg" - Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### L0.5 host-side unit tests" -Encoding utf8 + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### 主机侧纯逻辑单测 (host-unit)" -Encoding utf8 Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $agg -Encoding utf8 "count=$runnerCount" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii "agg=$agg" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding ascii exit $fail - - name: 归档 L0.5 日志(供下载/排障) + - name: 归档主机侧单测日志(供下载/排障) if: always() shell: powershell run: | - # Result visibility: same zero-dependency archival as L0. + # Result visibility: same zero-dependency archival as the static-gate job. if (Test-Path 'ci-artifacts') { $logs = Get-ChildItem -Path 'ci-artifacts' -Filter '*.log' -ErrorAction SilentlyContinue foreach ($f in $logs) { @@ -546,15 +546,15 @@ jobs: Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Archived logs on runner: $names" -Encoding utf8 } } else { - Write-Host "no ci-artifacts directory (L0.5 skipped or failed early)" + Write-Host "no ci-artifacts directory (host-side unit tests skipped or failed early)" } - - name: L0.5 跳过说明 + - name: 主机侧单测跳过说明 if: steps.msvc.outputs.value == 'no' shell: powershell run: | - Write-Host "L0.5 skipped: runner has no MSVC." + Write-Host "host-side unit tests skipped: runner has no MSVC." Write-Host "Local equivalent: & '.venv\Scripts\python.exe' scripts/ci_local.py" - Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### L0.5 host-side unit tests" -Encoding utf8 + Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value "### 主机侧纯逻辑单测 (host-unit)" -Encoding utf8 Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value ":warning: **NOT RUN** -- runner has no MSVC (VS C++ toolchain)." -Encoding utf8 exit 0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8ceb891b..c7335372 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -467,7 +467,7 @@ repos: # 7m) IEC 60870-5-104 被控站协议栈主机侧单元测试(2026-10-12 加,B4) # APCI 重组/STARTDT/总召分片/COV/k-w-t1~t3/遥控定值两段式/C_CS/错误 - # 应答——link 层 tick 注入 + pts 数据源 vtable 抽象就是为 L0.5 全离线 + # 应答——link 层 tick 注入 + pts 数据源 vtable 抽象就是为主机侧纯逻辑单测全离线 # 穷举设计(分层约束);板端复现这些分支要真主站+真等待。 # 触发面含数据源依赖(prot_set/prot_elm/phasor/sample_ring)——测试 # 直接 #include 这些生产源,签名变了要立刻在这里暴露。 @@ -770,7 +770,7 @@ repos: # 交实测解到 C:/Python314(无 fontTools)→ 字体链报 No module named fontTools = 假红。所以脚本 # 自己按「显式 DTU_GEN_PYTHON > 当前解释器 > 仓库 .venv > PATH」解析并**逐个探针校验**(要真能 # import fontTools 才算数),整轮统一用同一个解释器;挑不出来打 [ENV]+候选表+修法、退出码 2 不 - # SKIP。不把 entry 写死成 .venv 路径:那是 ci_local 2026-10-11「候选收窄 → runner 上 L0 全灭」 + # SKIP。不把 entry 写死成 .venv 路径:那是 ci_local 2026-10-11「候选收窄 → runner 上静态门禁全灭」 # 的同一条坑(详解 docs/门禁说明/generated-fresh.md §4)。 # ⚠ 字体链外部工具(node + 全局 lv_font_conv + 系统 simsun.ttc / seguisym.ttf)缺失时该项 SKIP 并 # 打印 [SKIP],--strict-env 才升 FAIL——与 test-coverage「缺 mscoverage 必 FAIL」刻意不同: diff --git a/AGENTS.md b/AGENTS.md index dc8e7dbf..6470bdb6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,7 +87,7 @@ ## Python 环境 - 统一入口 `& '.venv\Scripts\python.exe'`(3.12,fontTools/Pillow 已装);PATH 上的 `python` 是 msys64 的,**不要用它**跑依赖第三方库的脚本;纯标准库脚本任意 python3 均可 -- ⚠ **钩子 entry 的裸 `python` 不等于 .venv**:2026-10-26 真实提交实测 pre-commit 把它解析成**机器级**解释器(无 fontTools)→ 字体链生成器报 `No module named fontTools`、`generated-fresh` 假红(同族第二次发作见 `scripts/ci_local.py` 文件头 2026-10-11「L0 全灭」)。凡依赖第三方库的门禁脚本,一律在脚本内自己按「显式 var > 当前解释器 > 仓库 .venv > PATH」解析并**探针校验**(装了 pre-commit ≠ 装了 fontTools),但**别把 entry 写死成某台机器的绝对路径**(那等于把门禁绑死在一台机器上)→ `docs/门禁说明/generated-fresh.md` §4 +- ⚠ **钩子 entry 的裸 `python` 不等于 .venv**:2026-10-26 真实提交实测 pre-commit 把它解析成**机器级**解释器(无 fontTools)→ 字体链生成器报 `No module named fontTools`、`generated-fresh` 假红(同族第二次发作见 `scripts/ci_local.py` 文件头 2026-10-11「静态门禁全灭」)。凡依赖第三方库的门禁脚本,一律在脚本内自己按「显式 var > 当前解释器 > 仓库 .venv > PATH」解析并**探针校验**(装了 pre-commit ≠ 装了 fontTools),但**别把 entry 写死成某台机器的绝对路径**(那等于把门禁绑死在一台机器上)→ `docs/门禁说明/generated-fresh.md` §4 - 重建/换机:`<本机任一 python ≥3.10> -m venv .venv` + `pip install -r requirements.txt`(含 `pre-commit`;`DTU_PRECOMMIT_PYTHON` 可指向别的装了 pre-commit 的解释器);`requirements.txt` 必须**纯 ASCII**(Windows 下 pip 按 GBK 代码页解码) ## 提交门禁(pre-commit) @@ -97,7 +97,7 @@ - ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关 - **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/` - 提交信息:标题 ≤100 字符、**正文每行 ≤80**(`.gitlint` + gitlint 默认)→ 长说明一律用**短标题 + `git commit -F ` 多行正文**(中文长标题极易超限,且 `-m` 会被终端代码页转码破坏) -- **CI**(`.gitea/workflows/ci.yml`)只跑 L0 静态门禁 + L0.5 主机侧单测,**刻意不跑 `@build`**(需 Keil 授权)、不含采集链分层(需真机)。**远程 CI 的全部资料(服务端开关 / runner 部署与保活 / workflow / 排障 / 实测坑)集中在 [`docs/CI/README.md`](docs/CI/README.md),不放本节**——它**只跑在本机一台 runner `win-stm32-ci` 上**,其他机器与项目都不涉及。日常改代码的等价自查是 `& '.venv\Scripts\python.exe' scripts/ci_local.py`(L0 + L0b + L0.5)——**runner 不在线也不影响本地门禁,不能因"已配 CI"放宽本地要求** +- **CI**(`.gitea/workflows/ci.yml`)只跑静态门禁 + 主机侧纯逻辑单测,**刻意不跑 `@build`**(需 Keil 授权)、不含采集链分层(需真机)。**远程 CI 的全部资料(服务端开关 / runner 部署与保活 / workflow / 排障 / 实测坑)集中在 [`docs/CI/README.md`](docs/CI/README.md),不放本节**——它**只跑在本机一台 runner `win-stm32-ci` 上**,其他机器与项目都不涉及。日常改代码的等价自查是 `& '.venv\Scripts\python.exe' scripts/ci_local.py`(静态门禁 + 提交信息门禁 + 主机侧纯逻辑单测)——**runner 不在线也不影响本地门禁,不能因"已配 CI"放宽本地要求** ## 平台约束 diff --git a/docs/CI/README.md b/docs/CI/README.md index 174e4f77..bb76ed7d 100644 --- a/docs/CI/README.md +++ b/docs/CI/README.md @@ -8,7 +8,7 @@ > 日常改代码**不需要读本目录**:提交前的等价自查是本地门禁,runner 在不在线都照样跑—— > > ```powershell -> & '.venv\Scripts\python.exe' scripts/ci_local.py # 静态门禁静态 + 提交信息门禁 提交信息 + 主机侧纯逻辑单测主机单测 +> & '.venv\Scripts\python.exe' scripts/ci_local.py # 静态门禁 + 提交信息门禁 + 主机侧纯逻辑单测 > ``` > > 只有在这三种情况下才需要进来:**① 换机/重装后要重新部署 runner;② CI 不出任务或莫名 diff --git a/docs/CI/远程CI完整指南.md b/docs/CI/远程CI完整指南.md index ad5e3b73..593c89a9 100644 --- a/docs/CI/远程CI完整指南.md +++ b/docs/CI/远程CI完整指南.md @@ -653,10 +653,10 @@ runner 是一台干净的 Windows,**下面这些必须装**,否则 job 会 | 项 | 必需性 | 影响 | 安装 | |---|---|---|---| -| **Python 3.x** | 🔴 必需 | `l0-gates` 环境自检失败 → **job 硬失败** | `winget install Python.Python.3.12` | +| **Python 3.x** | 🔴 必需 | `static-gates` 环境自检失败 → **job 硬失败** | `winget install Python.Python.3.12` | | **cppcheck** | 🔴 必需 | 同上(`cppcheck --version` 探测不过) | `winget install Cppcheck.Cppcheck` | | **pre-commit** | 🔴 必需 | 同上(`python -m pre_commit --version` 探测) | `pip install pre-commit` 或 `uv tool install pre-commit` | -| **MSVC(VS + Windows SDK)** | 🟡 建议 | 缺则 `l05-host-unit-tests` **告警降级跳过**(不硬失败,但 主机侧纯逻辑单测裸奔) | `winget install Microsoft.VisualStudio.2022.BuildTools`,勾"使用 C++ 的桌面开发" | +| **MSVC(VS + Windows SDK)** | 🟡 建议 | 缺则 `host-unit-tests` **告警降级跳过**(不硬失败,但 主机侧纯逻辑单测裸奔) | `winget install Microsoft.VisualStudio.2022.BuildTools`,勾"使用 C++ 的桌面开发" | | Keil / ST-LINK | ⛔ 不需要 | CI 刻意不跑 `@build` | — | | 开发板 / 串口 | ⛔ 不需要 | 板端测试套件/主机侧回归/人工目检不上 CI | — | @@ -723,7 +723,7 @@ python -m pre_commit --version 也是本环境最隐蔽的一个坑。 **现象**:`pre-commit` 在你自己的终端里好好的(`pre-commit --version` 有输出), -但 CI 的 `l0-gates` job 三个候选 Python 全部探测失败: +但 CI 的 `static-gates` job 三个候选 Python 全部探测失败: ``` ::error::no Python with a SYSTEM-visible pre-commit. Tried: @@ -859,7 +859,7 @@ Start-ScheduledTask -TaskName GiteaActionsRunner ### 4.2 MSVC 的路径要求(探测逻辑) -`l05-host-unit-tests` 的 MSVC 探测优先读机器级 `DTU_MSVC_CL`(x64 `cl.exe` 完整路径; +`host-unit-tests` 的 MSVC 探测优先读机器级 `DTU_MSVC_CL`(x64 `cl.exe` 完整路径; 值无效 → 硬失败、不静默回退);未设置时按**标准布局自动探测**(根目录由 `%ProgramFiles` / `%ProgramFiles(x86)` 推导,不写死盘符;与 `test/host/common/unit_test_harness.py` 的 `_default_vs_roots()` 保持一致): @@ -963,7 +963,7 @@ github.com 会在 checkout 步骤卡住/失败。三种处置: | 手段 | 说明 | |---|---| -| `Tee-Object` 落盘 | 两个 job 把关键输出 `Tee` 到 `ci-artifacts/l0-pre-commit.log`、`ci-artifacts/l05-<模块>.log`(**留在 runner 工作区**,已 gitignore) | +| `Tee-Object` 落盘 | 两个 job 把关键输出 `Tee` 到 `ci-artifacts/static-pre-commit.log`、`ci-artifacts/host-unit-<模块>.log`(**留在 runner 工作区**,已 gitignore) | | `$GITHUB_STEP_SUMMARY` | 写入 job 的 **step-summary**(Gitea UI 运行页直接可见,无需下钻日志) | | `::notice::` 汇总行 | 控制台醒目行,便于日志检索 | @@ -971,8 +971,8 @@ github.com 会在 checkout 步骤卡住/失败。三种处置: | 层 | 汇总行 | |---|---| -| 静态门禁 | `静态门禁: pre-commit hooks -- passed=N failed=M skipped=K (log: ci-artifacts\l0-pre-commit.log)` | -| 主机侧纯逻辑单测 | `主机侧纯逻辑单测: runner(s), / assertions passed, fail=`(逐模块另有 `::notice::<模块>: N/M PASSED (failed=0)`) | +| 静态门禁 | `static: pre-commit hooks -- passed=N failed=M skipped=K (log: ci-artifacts\static-pre-commit.log)` | +| 主机侧纯逻辑单测 | `host-unit: runner(s), / assertions passed, fail=`(逐模块另有 `::notice::<模块>: N/M PASSED (failed=0)`) | > ⚠️ **`ci-artifacts/` 已加入 `.gitignore`**:它是 runner 工作区的运行时产物,不要提交。 > @@ -988,8 +988,8 @@ github.com 会在 checkout 步骤卡住/失败。三种处置: | job | `runs-on` | 步骤要点 | |---|---|---| -| `l0-gates` | `windows` | checkout(`fetch-depth: 0`,gitlint/cubemx 需完整历史)→ 环境自检(**Python 探测走 `scripts/ci_find_python.ps1`**、cppcheck 按 `DTU_CPPCHECK` → PATH → 标准安装目录,缺则 `::error::` + exit 1)→ `pre_commit install --install-hooks` → **提交信息门禁 `scripts/ci_gitlint_range.ps1`**(见 §7 排障表"钩子条数对不上"行)→ **决定跳过哪些钩子**(探测 `.map`,写入 `GITHUB_OUTPUT`;无 `.map` 时 `::warning::` 声明体积门禁未跑)→ `pre-commit run --all-files --show-diff-on-failure`(`env: SKIP`,输出 `Tee` 到 `ci-artifacts/l0-pre-commit.log`)→ 打印 `::notice::静态门禁: passed/failed/skipped` 汇总行 → 归档日志说明 | -| `l05-host-unit-tests` | `windows` | checkout → **探测 MSVC**(按 VS_ROOTS 找 `Hostx64\x64\cl.exe`)→ 遍历 `test/host/**/run_unit_test.py` 逐个跑(输出 `Tee` 到 `ci-artifacts/l05-<模块>.log`;解析各模块 `N/M PASSED` 汇总行 → 聚合 `::notice::主机侧纯逻辑单测: K runner(s), A/B assertions passed`;失败 `::error::` 并置退出码);**未探测到 MSVC 时打印醒目 WARNING 并降级跳过**(不硬失败,避免"没装 VS 的 runner 全红") | +| `static-gates` | `windows` | checkout(`fetch-depth: 0`,gitlint/cubemx 需完整历史)→ 环境自检(**Python 探测走 `scripts/ci_find_python.ps1`**、cppcheck 按 `DTU_CPPCHECK` → PATH → 标准安装目录,缺则 `::error::` + exit 1)→ `pre_commit install --install-hooks` → **提交信息门禁 `scripts/ci_gitlint_range.ps1`**(见 §7 排障表"钩子条数对不上"行)→ **决定跳过哪些钩子**(探测 `.map`,写入 `GITHUB_OUTPUT`;无 `.map` 时 `::warning::` 声明体积门禁未跑)→ `pre-commit run --all-files --show-diff-on-failure`(`env: SKIP`,输出 `Tee` 到 `ci-artifacts/static-pre-commit.log`)→ 打印 `::notice::static: passed/failed/skipped` 汇总行 → 归档日志说明 | +| `host-unit-tests` | `windows` | checkout → **探测 MSVC**(按 VS_ROOTS 找 `Hostx64\x64\cl.exe`)→ 遍历 `test/host/**/run_unit_test.py` 逐个跑(输出 `Tee` 到 `ci-artifacts/host-unit-<模块>.log`;解析各模块 `N/M PASSED` 汇总行 → 聚合 `::notice::host-unit: K runner(s), A/B assertions passed`;失败 `::error::` 并置退出码);**未探测到 MSVC 时打印醒目 WARNING 并降级跳过**(不硬失败,避免"没装 VS 的 runner 全红") | > 🔴 **两条 Python 探测链必须同序同步**:本地 `scripts/ci_local.py` 与 CI 侧 > `scripts/ci_find_python.ps1` 的候选顺序要一致。2026-10-11 那次 静态门禁全灭,就是 workflow @@ -1009,9 +1009,9 @@ github.com 会在 checkout 步骤卡住/失败。三种处置: | 3 | `scripts\ci_runner_bootstrap.ps1 -Status` | 计划任务已注册 / daemon 在跑 / Gitea 可达 / 三个 `DTU_*` 已设——**四项全绿** | | 4 | runner 上 `python --version` / `cppcheck --version` / `python -m pre_commit --version` | 三条全通(且对 SYSTEM 成立,见 §4.1) | | 5 | `git push` 一次(或页面 Run workflow) | Actions 页出现运行中任务 | -| 6 | `l0-gates` 日志 | `using python (…)` + `runner environment check passed`;`::notice::静态门禁: pre-commit hooks -- passed=.. failed=0 skipped=..`;无 `.map` 时见 `::warning::` 体积门禁未跑 | -| 7 | `l0-gates` 的 **提交信息门禁** 步骤 | `::notice::gitlint: N commit message(s) checked, 0 failed`(**这一行以前根本不存在**,见 §7) | -| 8 | `l05-host-unit-tests` 日志 | `::notice::主机侧纯逻辑单测: runner(s), A/A assertions passed, fail=0`。**`N` = `test/host/` 下 `run_unit_test.py` 的个数**(2026-10-12 实测:`master` 3 个 / 288 断言,`v2-on-ui` 10 个);断言总数随单测增减而变,判定只看 **`fail=0` 且 A==B**,不要拿历史数字当基准 | +| 6 | `static-gates` 日志 | `using python (…)` + `runner environment check passed`;`::notice::static: pre-commit hooks -- passed=.. failed=0 skipped=..`;无 `.map` 时见 `::warning::` 体积门禁未跑 | +| 7 | `static-gates` 的 **提交信息门禁** 步骤 | `::notice::gitlint: N commit message(s) checked, 0 failed`(**这一行以前根本不存在**,见 §7) | +| 8 | `host-unit-tests` 日志 | `::notice::host-unit: runner(s), A/A assertions passed, fail=0`。**`N` = `test/host/` 下 `run_unit_test.py` 的个数**(2026-10-12 实测:`master` 3 个 / 288 断言,`v2-on-ui` 10 个);断言总数随单测增减而变,判定只看 **`fail=0` 且 A==B**,不要拿历史数字当基准 | | 9 | job 运行页的 **Summary** 区块 | 出现 "静态门禁 static gates" / "Commit message gate (gitlint)" / "主机侧纯逻辑单测 host-side unit tests" 小标题与汇总行 | | 10 | 提交记录旁出现 ✅ | CI 全绿 | @@ -1057,17 +1057,17 @@ date +%s # 对比 last_online |---|---|---| | 运行状态是 **`cancelled`** 且**一行日志都没有** | runner 掉线时 run 只在队列里等,**下一次 push 触发 `cancel-in-progress` 把它们取消**——所以表现为"任务凭空消失"而不是"任务失败" | 先按下一行修 runner;被取消的 run 可在页面 Re-run,或重新 push | | Runners 页 runner 显示 **`offline`** | daemon 进程不在:`gitea-runner` v4 的 `daemon` **首次 Declare 失败就直接退出**(Gitea 重启/机器重启后无人拉起) | §2.4 用 `ci_runner_bootstrap.ps1` 装看门狗 + 计划任务;`-Status` 复查 | -| `l0-gates` 报 `no Python with a SYSTEM-visible pre-commit` | runner 是 SYSTEM:PATH 上的 python 无 pre-commit、用户级 site 不可见、`.venv` 未入库(§4.1 三重坑) | `ci_runner_bootstrap.ps1 -SetEnv` 写机器级 `DTU_PRECOMMIT_PYTHON`,或把 pre-commit 装进机器级解释器;**改完必须重启 runner** | -| `l0-gates` 报 `runner missing cppcheck` | 机器 PATH 无 Cppcheck 且无机器级 `DTU_CPPCHECK` | `-SetEnv`(会自动发现 `C:\Program Files\Cppcheck`)或装 `Cppcheck.Cppcheck` | +| `static-gates` 报 `no Python with a SYSTEM-visible pre-commit` | runner 是 SYSTEM:PATH 上的 python 无 pre-commit、用户级 site 不可见、`.venv` 未入库(§4.1 三重坑) | `ci_runner_bootstrap.ps1 -SetEnv` 写机器级 `DTU_PRECOMMIT_PYTHON`,或把 pre-commit 装进机器级解释器;**改完必须重启 runner** | +| `static-gates` 报 `runner missing cppcheck` | 机器 PATH 无 Cppcheck 且无机器级 `DTU_CPPCHECK` | `-SetEnv`(会自动发现 `C:\Program Files\Cppcheck`)或装 `Cppcheck.Cppcheck` | | **钩子条数对不上**(CI 报告条数 = 定义数 − 1;2026-10-12 实测 `master` 18 定义 / 17 报告,`v2-on-ui` 26 定义 / 25 报告——**增删钩子的分支必须重算这个差值**) | `gitlint` 的 `stages: [commit-msg]` 写在**上游** `.pre-commit-hooks.yaml`(项目 `.pre-commit-config.yaml` 里 grep 不到,别据此断定"没限制"),`pre-commit run --all-files` **跑不到它**,且表现为全绿——静默空转 | 已由 `scripts/ci_gitlint_range.ps1` 单独校验;**以后增删钩子必须核对"定义数 == 日志条数 + gitlint"** | | gitlint 步骤 **Failed 但提交信息其实合规**(同一条命令在本地全绿),日志里是 `exit code: 254` + `Missing git configuration: please set user.name` | `actions/checkout` 把 HOME 临时指向每次任务的临时目录,而 SYSTEM 账号没有全局 git 身份 → gitlint **在读到任何规则之前**就因配置错误退出(**254 不是规则违规**)。旧脚本末尾那句 `::error::...rule: single-line Chinese title, < 100 chars` 是**写死的猜测文案**,把环境问题伪装成"提交信息不合格";且该步 `exit 1` 会让后面的 `--all-files` **整个不跑**——看着像"门禁拦住了",实际 26 个钩子一个都没执行 | `ci_gitlint_range.ps1` 先探测身份,缺失就用 `GIT_CONFIG_COUNT/KEY/VALUE` 注入一次性身份(**不写任何文件**,开发者机器不受影响),并把 254 与真实违规分开报(**2 = 门禁没跑起来**,1 = 信息确实违规)。实测同一命令:空 HOME 下改前 254、改后 `0 failed`(2026-10-12 task 31 首次暴露) | | push 后 Actions 页**空白/无任务** | ① 服务端开关未开 ② workflow 没 push 到远程 ③ 只改了 `docs/**`(被 `paths-ignore` 滤掉) ④ **新建分支的首次 push**(实测 2026-10-12:创建 `v2-on-ui-ci` 那次 push 没产生 run——runner 在线空闲、job 日志零新增;紧接着一次普通 push 立刻出 task。原因:`before` 是全零 SHA,变更文件集算不出来,`paths-ignore` 求值时被滤空) | ①②③ 逐条核对 §1 / §5.1 / §5.2;④ 用 `workflow_dispatch` 手动触发,或再推一个非文档路径的提交 | | 任务一直 **Waiting / 排队** | 没有 label 匹配 `windows` 的 runner | 检查 `--labels "windows:host"`;Settings→Runners 是否 Idle | -| `l0-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) | +| `static-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) | | `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 | | `l05` 日志出现 `::warning::runner 未安装 MSVC` | VS 装在非默认路径或未装 | 见 §4.2(默认路径 / 同步改两处根目录) | | `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;但注意 **CI 未守体积门禁**(见 §0.0),改代码后本地 `@build` + `check_mem_numbers.py --scan` 兜底 | -| 主机侧纯逻辑单测显示 `2 runner(s), X/Y ...` 但 X.log` | +| 主机侧纯逻辑单测显示 `2 runner(s), X/Y ...` 但 X.log` | | 主机侧纯逻辑单测报 `produced no '=== ... N/M PASSED ===' summary line` | 该模块运行器未输出汇总行(改了 harness 或运行器崩溃) | 核对 `test/host/common/unit_test_harness.py` 的 `summary_marker` 与该模块 `TestBuild` 是否一致 | | `Tee-Object` 步骤报 `TerminatorExpectedAtEndOfString` / 字符串缺少终止符 | `run:` 块混入了非 ASCII(GBK 解码错位) | 见文件头 🔴🔴;`run:` 内必须纯 ASCII | | `l05` 报 `fatal error C1083: 无法打开包括文件 "stdio.h"` | Windows SDK 缺失 | 装 VS 时勾"使用 C++ 的桌面开发"+ SDK | @@ -1082,7 +1082,7 @@ date +%s # 对比 last_online | `Register-ScheduledTask` 报 **拒绝访问** | Shell 未提权(`IsAdmin=False`) | 右键 PowerShell → **以管理员身份运行**(§3.6) | | 任务 `LastTaskResult` 非 0 | 启动器路径/权限问题,或任务被手工终止 | 看 `log\runner-watchdog.log` 与 `log\runner-daemon.log`;确认 `run_runner_watchdog.ps1` 存在且为纯 ASCII;`0x41301` 是"正在运行"不是错误 | | 🔴 **runner 网页显示 online,但 `runner-watchdog.log` 每 10 秒刷 `starting daemon` → `daemon exited, exit code 1`** | **两个看门狗抢 `.runner.lock`**:`Unregister-ScheduledTask` 不会带走旧实例启动的进程,孤儿看门狗养着的旧 daemon 仍占着锁,新看门狗起不来(daemon 日志写 `another gitea-runner process is already using ".runner"` / `runner file is already locked by another process`)。此时 online 是**旧 daemon 在硬撑**,任务实际谁也没管 | `scripts\ci_runner_bootstrap.ps1 -Status` 会报出重复实例;**管理员**跑 `-StartNow`(现已会先清残留进程再启动,runner 约离线几秒)。应急可手工:`Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" \| ? CommandLine -match 'run_runner_watchdog'` 找出多个 pid,杀掉**旧的那个**,再 `taskkill /PID <旧 daemon>` 让在册看门狗接管。**✅ 2026-10-12 实测收敛**:`-StartNow` 输出 `stopping stale watchdog pid 26900 / 19716` → `stopping stale daemon pid 53848` → `daemon up: pid 40360 (single instance, holds .runner.lock)`;之后看门狗日志 25 秒零增长、runner 回 `online`。切换瞬间新看门狗可能先失败 1~2 次(旧 daemon 刚被杀、锁尚未释放),10 秒重拉后自愈,属预期 | -| `l0-gates` 报 `Cannot find: pwsh in PATH` | `shell: pwsh` = PS7,本机只有 WinPS 5.1 | 改 `shell: powershell` | +| `static-gates` 报 `Cannot find: pwsh in PATH` | `shell: pwsh` = PowerShell 7(本机只有 Windows PowerShell 5.1) | 改 `shell: powershell` | | 🔴 `doc-links` 钩子本地过、CI 挂 | 引用了 **gitignore 的运行时文件**(如 `.dsh/skills/serial-assistant/scripts/state/*.json`),本地存在、干净克隆里没有 | 该行加状态词「未创建/不存在」等(§4.1.2) | | 任务跑满 72 小时被杀 | 计划任务默认 `ExecutionTimeLimit` | 设为 `0`(不限时长),见 §3.3 警示 | | `config set log.file ...` 报 `unknown config key` | **v4 的 log 段无 `file` 键** | 用 `log.job.dir` 或输出重定向(§2.3) | @@ -1326,9 +1326,9 @@ powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun > 故已改写为看门狗方案;旧脚本的失效机理见 §3.2。 > **CI 验收标准**:两个 job 都要 `status=1`。**口径是规则,不是具体数字**(2026-10-12): -> `l0-gates` 报 **定义数 − 1 条钩子**(gitlint 属 `commit-msg` stage,不进 `--all-files` 报告, +> `static-gates` 报 **定义数 − 1 条钩子**(gitlint 属 `commit-msg` stage,不进 `--all-files` 报告, > 见 §7),其中 `mem-numbers` 在无 `.map` 时为 `Skipped`(本机唯一常见跳过项);另有提交信息 -> 门禁 `::notice::gitlint: N checked, 0 failed`。`l05-host-unit-tests` 报 +> 门禁 `::notice::gitlint: N checked, 0 failed`。`host-unit-tests` 报 > **` runner(s), A/A assertions passed, fail=0`**,`N` = `test/host/` 下 `run_unit_test.py` 个数。 > 实测快照:`master` 18 定义 / 17 报告、3 套件 / 288 断言;`v2-on-ui` 26 定义 / 25 报告、10 套件。 > 🗑 更早快照(勿当基准):run#8(2026-10-02)当时是"14 个钩子全 Passed + 39/39 单测"。 @@ -1362,7 +1362,7 @@ powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun | 2026-10-11 | **bootstrap 自身修复**:`StartWhenAvailable` 误设到**触发器**上(该属性属**任务设置**,触发器没有)→ 脚本在"已注销旧任务、尚未注册新任务"之间抛 `SetValueInvocationException`,把 runner 任务删没了。现改为①先构建 action/trigger/settings、**后**注销旧任务,②`Register-ScheduledTask` 失败时显式告警"旧任务已删、CI 裸奔",③新增 **`-DryRun`**(免管理员即可全量校验任务对象),④`-Status` 在非管理员下额外提示"看不见任务 ≠ 任务不存在"。同时更正根因:旧任务其实存在,但跑的是**单次** `daemon`——daemon 属"正常退出"而非"失败",计划任务的失败重启**救不到**它,必须用看门狗循环重拉 | | 2026-10-11 | **修"很多任务不执行"(runner 静默停摆)+ 补 gitlint 门禁**:① §2.4 重写——`gitea-runner` v4 的 `daemon` **无服务支持、首次 Declare 失败即退出进程**(日志实证),Gitea/机器重启一次 CI 就停摆,**run 16~21 共 12 个 job 零日志**(`cancelled`/`queued`,叠加 `cancel-in-progress` 后表现为"任务凭空消失"而非失败);新增 **`scripts/ci_runner_bootstrap.ps1`**(等 Gitea 就绪 + 崩溃自拉起的看门狗 + 开机计划任务 + `-SetEnv` 写机器级 `DTU_*` + `-Status` 只读体检)。② §3.1 重写——SYSTEM 可见性三重坑(PATH python 是 msys64 无 pre-commit / 用户级 site 对 SYSTEM 不可见 / `.venv` 已 gitignore)是 run 14~15 静态门禁全灭根因(`33a4aa4` 把候选收窄成这两个、又删了 cppcheck 目录兜底,而机器级 `DTU_*` 从未设置);探测收进 **`scripts/ci_find_python.ps1`**(两 job 共用、可离线自测、失败列出全部候选)并恢复机器级安装兜底。③ 新增 **gitlint 提交信息门禁**:钩子定义数比 CI 报告条数多 **1**(当时 17/16,master 并入 `test-cppcheck` 后为 18/17),缺的正是 gitlint——其 `stages: [commit-msg]` 声明在**上游** `.pre-commit-hooks.yaml`(`--all-files` 跑不到,且表现为全绿 → 长期无人发现)→ **`scripts/ci_gitlint_range.ps1`** 逐条校验本次 push/PR 范围(消息文件须写在仓库内 + UTF-8 无 BOM)。④ `GITHUB_ENV/PATH/OUTPUT` 写入统一显式 `-Encoding ascii`(PS 5.1 `Out-File -Append` 默认 **UTF-16LE**,实测落盘 `42 00 42 00 42 00 3D 00 31 00`)。§5 清单 8→10 条、§6 排障表 +5 行、§7 维护项重写 | | 2026-10-02 | **S3 + S14**:新增 §0.0「CI 不覆盖的体积门禁」醒目声明;新增 §4.4「产物存档与结果汇总」(零依赖 `Tee-Object` + `$GITHUB_STEP_SUMMARY` + `::notice::` 汇总行,不用 `actions/upload-artifact`;记录 `$ErrorActionPreference='Stop'` 与 `2>&1 \| Tee-Object` 的陷阱及处置)。§5 验证清单(7→8 条)与 §6 排障表同步更新 | -| 2026-10-02 | **workflow 接入 `DTU_*` 机器级变量**(`本地工具路径环境变量方案.md` 实施):`l0-gates` 的 Python 探测改为 `DTU_PRECOMMIT_PYTHON`(显式,无效即硬失败)→ PATH → `.venv`(删除 `C:\ProgramData\anaconda3` 固定候选);cppcheck 改为 `DTU_CPPCHECK` 优先 → PATH(**移除 `C:\Program Files\Cppcheck` 固定回退**);`l05` 的 MSVC 探测改为 `DTU_MSVC_CL` 优先 → `%ProgramFiles*` 推导的标准根(与 `unit_test_harness.py` 同步,不再写死盘符);新增 §5.1.3 机器级变量设置方法(`Machine` scope + 重启任务)、§5.2 与 check_env 样例、排障表 cppcheck 行、TL;DR 5b 步骤。§5.1.1 的报错原文标注为历史记录 | +| 2026-10-02 | **workflow 接入 `DTU_*` 机器级变量**(`本地工具路径环境变量方案.md` 实施):`static-gates` 的 Python 探测改为 `DTU_PRECOMMIT_PYTHON`(显式,无效即硬失败)→ PATH → `.venv`(删除 `C:\ProgramData\anaconda3` 固定候选);cppcheck 改为 `DTU_CPPCHECK` 优先 → PATH(**移除 `C:\Program Files\Cppcheck` 固定回退**);`host-unit` 的 MSVC 探测改为 `DTU_MSVC_CL` 优先 → `%ProgramFiles*` 推导的标准根(与 `unit_test_harness.py` 同步,不再写死盘符);新增 §5.1.3 机器级变量设置方法(`Machine` scope + 重启任务)、§5.2 与 check_env 样例、排障表 cppcheck 行、TL;DR 5b 步骤。§5.1.1 的报错原文标注为历史记录 | | 2026-10-02 | ✅ **首次 CI 全绿(run#8:静态门禁 + 主机侧纯逻辑单测双 job Success)**。定位并修复最后两个拦路虎:① **§5.1.1 用户态 site-packages 陷阱**——`pre-commit` 只装在 `%APPDATA%\Roaming\Python\...`(SYSTEM 不可见);且提权 `pip install --no-user` 会因 pip 扫**整个 `sys.path`**、见 user-site 已满足而**静默跳过**全局安装(9 依赖中唯 `cfgv` 因版本相同被跳过→`-s` 下 `ModuleNotFoundError`)。修法:提权 + `--force-reinstall --no-deps` 强制落全局,并以 `-s` 复验。② **§5.1.2 doc-links 假阳性**——文档引用了 gitignore 的运行时文件 `.dsh/skills/serial-assistant/scripts/state/meta.json`,本地存在、干净克隆无 → 按《文档规范》§3.3 加同状态词「运行时未创建」。排障表补 6 条(pre-commit 不可见 / GBK 中文 / pwsh / cppcheck / doc-links / MSVC 降级)。新增配套脚本 `install_precommit_global.ps1`、`install_precommit_global2.ps1`、`fix_cfgv_global.ps1` | | 2026-10-02 | 🔴 **常驻方式重大修正(实测推翻)**:`Start-Service gitea-runner` 必然失败。经二进制逆向确认 `gitea-runner v4.0.1` **无任何服务管理代码**(命令集仅 `bug-report/cache-server/config/daemon/exec/register`,无 `service.go`;`OpenSCManagerW` 等符号来自 Docker SDK,非服务安装代码),`daemon` 是纯控制台程序、不向 SCM 回报 `SERVICE_RUNNING`。**§4 全节重写为「任务计划程序」方案**(新增 §4.0 根因分析、§4.2 `deploy_runner_task.ps1`、§4.3 手动命令 + 72h 超时警示、§4.5 清理遗留服务、§4.6 提权);删除失效的 `deploy_runner.ps1`/`install_service.ps1`;排障表补「错误 1053」「LastTaskResult 非 0」「72 小时被杀」3 条;拓扑图/§5.1/§7 日志/§10 TL;DR 同步更新。另修 `config.yaml` 的 `fetch_timeout` 5m→1m(超出 RPC 1m 会被截断并告警) | | 2026-10-02 | **修 `.ps1` 编码坑(实测)**:无 BOM 的 UTF-8 脚本被 WinPS 5.1 按 GBK 解读 → 中文乱码(`鐒跺悗鎵ц`)并撞坏引号配对,报 `源文本中包含无法识别的标记`/`字符串缺少终止符`。**新增 §7.1「给含中文的 `.ps1` 加 UTF-8 BOM」**(两种修法 + 解析器校验命令);排障表补该条目;配套脚本已统一加 BOM | diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md index 4c458219..be4f50f0 100644 --- a/docs/产品需求/产品需求设计文档.md +++ b/docs/产品需求/产品需求设计文档.md @@ -134,7 +134,7 @@ flowchart LR | G2 | **可维护**:分层解耦、单一事实源、双端复用、门禁强制 | 六层架构、就绪屏障、生成器(菜单/字体/任务表)、pre-commit 钩子族 | | G3 | **可移植**:UI/配置源码 MCU 与 PC 模拟器共用 | 平台后端抽象(`*_mcu.c` / `*_sim.c`),核心零平台依赖 | | G4 | **可扩展**:新子系统/新配置段/新页面按模板接入 | 事件位扩展、段注册三步、页面插件描述符、`App/protocol/` 预留 | -| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `@build` / `@test` / L0 静态 / L0.5 主机侧单测 / 台架验证 | +| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `@build` / `@test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 | **设计原则**(贯穿全项目): @@ -1743,10 +1743,10 @@ mac=00:00:00:00:00:00 | 层 | 内容 | 运行位置 | |---|---|---| -| L0 | 静态门禁(pre-commit 钩子族:格式 / 编码 / 静态分析 / 文档链接 / 体积 / 生成物新鲜度 / 编号) | 本地 / CI | -| L0.5 | 主机侧单元测试(MSVC 离线编译真实源码) | 本地 / CI | -| 语义取数与有效性 / 全周傅氏相量 | 板端套件 / 模拟器回归 | 本地(需硬件 / 模拟器) | -| 保护判据 | 真机台架(`@test` 闭环 + 探针) | 本地(需真机 + ST-LINK) | +| 静态门禁 | pre-commit 钩子族:格式 / 编码 / 静态分析 / 文档链接 / 体积 / 生成物新鲜度 / 编号 | 本地 / CI | +| 主机侧纯逻辑单测 | MSVC 离线编译真实源码 | 本地 / CI | +| 板端测试套件 | 板端套件(testTask 串口 `[PASS]/[FAIL]` 汇总) | 本地(需真机 + 串口) | +| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `@test` 闭环(含探针) | 本地(模拟器免硬件;`@test` 需真机 + ST-LINK) | **构建 / 测试命令**: diff --git a/requirements.txt b/requirements.txt index 93c2ae88..ebbb676c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ fonttools==4.51.0 # tools/font_gen/make_font.py (font generation, verified version) pillow==10.3.0 # simulator/i1_to_png.py (screenshot to PNG, verified on this machine) pyserial==3.5 # .dsh/skills/serial-assistant/scripts/serial_tool.py (serial daemon + CLI) -pre-commit==4.6.2 # commit gate (scripts/ci_local.py L0; keeps rebuilt .venv self-sufficient) +pre-commit==4.6.2 # commit gate (scripts/ci_local.py static stage; keeps rebuilt .venv self-sufficient) diff --git a/scripts/README.md b/scripts/README.md index e3eb3fcb..f6acfa5a 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -41,9 +41,9 @@ | 脚本 / 文件 | 作用 | |---|---| -| `ci_local.py` | **本地跑与 CI 等价的门禁**(L0 pre-commit + L0b 提交信息 + **L0c 体积门禁** + L0.5 主机侧单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers` / `mem-budget`(⚠ 那是 SKIP 不是通过,缺口由 L0c 兜住);退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,覆盖边界见 `docs/测试说明/测试开发标准流程.md` §9 | -| `check_size_gate.py` | **体积门禁的强制路径**:`.map` 只在本地有,钩子无它时自适应 SKIP,于是"没测=通过"。本脚本用**已提交的同码凭证** `scripts/size_attestation.json` 把这条判据变成无需 Keil 也能跑:`--stamp` 只在"`.map` 存在、比全部受管源码新、且未超 92%"时才盖章(已挂在 `ram_baseline.py --update` 里,无新增人工步骤);`--local` 供 ci_local 的 L0c 步;`--ci` 供 runner(同码性 + 按凭证数字复核红线,主干 push 拦红、其余事件 `--warn-only`);`--show` 排障。**口径与边界的唯一正文在本脚本模块 docstring** | -| `size_attestation.json` | (被 `check_size_gate.py` 读写)体积同码凭证:受管源码指纹(`git ls-files` 范围内逐文件 sha256 聚合)+ 测量数字与占比 + `.map` 出处。改固件源码后不重测 → 指纹对不上 → L0c 与 CI 同时拦红 | +| `ci_local.py` | **本地跑与 CI 等价的门禁**(静态门禁 pre-commit + 提交信息门禁 + **体积门禁** + 主机侧纯逻辑单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers` / `mem-budget`(⚠ 那是 SKIP 不是通过,缺口由体积门禁兜住);退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,覆盖边界见 `docs/测试说明/测试开发标准流程.md` §9 | +| `check_size_gate.py` | **体积门禁的强制路径**:`.map` 只在本地有,钩子无它时自适应 SKIP,于是"没测=通过"。本脚本用**已提交的同码凭证** `scripts/size_attestation.json` 把这条判据变成无需 Keil 也能跑:`--stamp` 只在"`.map` 存在、比全部受管源码新、且未超 92%"时才盖章(已挂在 `ram_baseline.py --update` 里,无新增人工步骤);`--local` 供 ci_local 的体积门禁步;`--ci` 供 runner(同码性 + 按凭证数字复核红线,主干 push 拦红、其余事件 `--warn-only`);`--show` 排障。**口径与边界的唯一正文在本脚本模块 docstring** | +| `size_attestation.json` | (被 `check_size_gate.py` 读写)体积同码凭证:受管源码指纹(`git ls-files` 范围内逐文件 sha256 聚合)+ 测量数字与占比 + `.map` 出处。改固件源码后不重测 → 指纹对不上 → 体积门禁与 CI 同时拦红 | | `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 16 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;**不在 pre-commit 里**,入口是 `ci_local.py --coverage`(口径见 `docs/测试说明/主机单测覆盖率口径.md`) | | `ci_gitlint_range.ps1` | CI 侧**提交信息门禁**:逐条校验本次 push/PR 引入的提交(`--all-files` 跑不到 `commit-msg` stage,故必须单独守) | | `ci_find_python.ps1` | CI 侧 Python 探测(候选顺序须与 `ci_local.py` 同序同步,否则 CI 与本地判定会分叉) | @@ -59,11 +59,11 @@ # 其他装有 pre-commit 的解释器可用 DTU_PRECOMMIT_PYTHON 指定) & '.venv\Scripts\python.exe' -m pre_commit run --all-files -# 本地跑与 CI 等价的门禁(L0 + L0b + L0c + L0.5) +# 本地跑与 CI 等价的门禁(静态门禁 + 提交信息门禁 + 体积门禁 + 主机侧纯逻辑单测) & '.venv\Scripts\python.exe' scripts/ci_local.py -& '.venv\Scripts\python.exe' scripts/ci_local.py --l0-only # 只跑 L0 -& '.venv\Scripts\python.exe' scripts/ci_local.py --l05-only # 只跑 L0.5 -& '.venv\Scripts\python.exe' scripts/ci_local.py --size-only # 只跑 L0c 体积门禁(强制路径) +& '.venv\Scripts\python.exe' scripts/ci_local.py --static-only # 只跑静态门禁 +& '.venv\Scripts\python.exe' scripts/ci_local.py --host-unit-only # 只跑主机侧纯逻辑单测 +& '.venv\Scripts\python.exe' scripts/ci_local.py --size-only # 只跑体积门禁(强制路径) & '.venv\Scripts\python.exe' scripts/check_size_gate.py --show # 体积同码凭证现状(0.3 s,无需 Keil) & '.venv\Scripts\python.exe' scripts/check_size_gate.py --stamp # 用新鲜 .map 重新盖戳(--update 已自动带做) diff --git a/scripts/archive/id_gate/编号治理历史.md b/scripts/archive/id_gate/编号治理历史.md index 7b97534c..9731000c 100644 --- a/scripts/archive/id_gate/编号治理历史.md +++ b/scripts/archive/id_gate/编号治理历史.md @@ -23,6 +23,7 @@ - `§9` 三批计划(按「值不值得动门禁」排序) - `§9` 别再做什么 - `§9` 批次五换算表:「数据可信」任务卡号(2026-10-27 整族清退) +- `§10` 批次六换算表:测试验证层级号(2026-10-28 整族清退) --- @@ -277,6 +278,44 @@ P-6 号必须能反查到定义(属主登记处得存在)· P-7 编号不语 --- +## 10. 批次六换算表:测试验证层级号(2026-10-28 整族清退) + +用户裁定:「我不想看到编号」→ 测试验证层级的 `L0 / L0a / L0b / L0c / L0.5 / L0.5c / L1 / L2 / L3` 整族停用, +活文档、代码注释、CI 步骤名、门禁输出标签一律改语义名。**只有 CI 与门禁输出需要一个 ASCII 段名**(日志与命令行 +要纯 ASCII,中文进 PowerShell 汇总行会有代码页风险),它不是编号、是可读的英文段名。 + +| 旧层级号 | 语义名(文档 / CI 步骤名 / 表格) | ASCII 段名(`[SUMMARY]` 标签、CI job id、日志文件名、命令行旗标) | +|---|---|---| +| `L0` | 静态门禁 | `static`(job `static-gates`、`static-pre-commit.log`、旗标 `--static-only`) | +| `L0a` | 钩子解释器预检 | `preflight`(`--preflight-only`;无 `[SUMMARY]` 行,只在收尾「已跑:」列表里出现) | +| `L0b` | 提交信息门禁 | `commitmsg`(`[SUMMARY] gitlint:` 这个既有标签不改,与层级号无关) | +| `L0c` | 体积门禁 | `size`(`--size-only`、`[SUMMARY] size-gate:` 保持不变) | +| `L0.5` | 主机侧纯逻辑单测 | `host-unit`(job `host-unit-tests`、`host-unit-<模块>.log`、`--host-unit-only`) | +| `L0.5c` | 主机单测覆盖率 | `coverage`(`--coverage`,opt-in) | +| `L1` | 板端测试套件 | 无(只出现在文档,从不进日志) | +| `L2` | 主机侧回归 | 无 | +| `L3` | 人工目检 | 无 | + +**旧旗标保留为别名**:`scripts/ci_local.py --l0-only` / `--l05-only` 仍可用(argparse 双选项名),肌肉记忆与既有脚本片段不断档;文档与新写的命令一律用新旗标。 + +### 10.1 撞族警示——同形字面量还属于另外六个族,读旧文先判族 + +这一族清掉**不等于**仓库里的 `L0`/`L1`/`L2`/`L3` 都消失了。同形形态仍属这些族,各自另有口径: + +- **UI 设计令牌层**:`Drivers/BSP/UI/ui_tokens.h` 的「设计令牌层 L0」——层级架构名,**保留**。 +- **UI 菜单层级**:`L0~L3` 已于批次三a 改「主界面 / 一级 / 二级 / 三级菜单」,`docs/产品需求/产品需求设计文档.md` 尚有 2 处残留待清。 +- **采集链分层**:批次一已整族改语义层名(语义取数与有效性 → 标定折算 → 全周傅氏相量 → 测频与遥测快照 → 保护判据 → 出口与记录 → 动作留痕)。 +- **链路时延分段**:批次三b 改为中文段名 + ASCII 短码(`SPI_CMD` 等 11 个);`test/board/` 仍有残留待清。 +- **CubeMX 防丢失三层优先级**:批次三c 改为「写进 `.ioc` / 搬进 USER CODE 区 / 审计兜底」。 +- **硬件位号与封装尺寸**:磁珠电感位号 `L1`/`L2`/`L3`、封装 `SOT-23_L2.9-W1.3`、`CRYSTAL-L3.2`,以及 FTP 应答 `"215 UNIX Type: L8"` 这类**协议字符串字面量**——永不改(改了就是改行为)。 + +> 这就是 AGENTS「绝不全局查找替换」那条的又一次实测:本族改名如果按裸形 `L1` 全局替换,会顺手把上面六个族的 +> 字面量一起改掉,其中 6 处直接改到 C 断言文案与 FTP 应答。**每一步都要先判族再改,且只按行内置换。** + +### 10.2 读史口径 + +历史文档、变更记录行、提交信息、`.gitea` 历史 run 里出现的 `L0`/`L0.5` 一律按本表换算来读;不要拿它们当"现行名"去搜代码或 CI(现行 CI 里搜 `L0` 只会命中别的族)。本表所在目录不参与编号门禁(`check_ids_new.py` 排除 `scripts/archive/`)。 + ## 变更记录(2026-10-28 瘦身以前的全量流水) | 日期 | 变更 | 说明 | diff --git a/scripts/check_generated_fresh.py b/scripts/check_generated_fresh.py index 9983316d..eae9cf2b 100644 --- a/scripts/check_generated_fresh.py +++ b/scripts/check_generated_fresh.py @@ -41,7 +41,7 @@ r"""check_generated_fresh.py — 生成器幂等性门禁(重跑到临时目 没装 fontTools,于是那次提交实测报: [FAIL] cn-font tools/font_gen/make_font.py 退出码 1:ERROR: No module named 'fontTools' 纯标准库的 menu-gen / cn-chars 照旧绿,所以这是一根**只在字库链上假红**的闸:换任何一台没把 - .venv 排在 PATH 前面的机器都会重犯(同 scripts/ci_local.py 文件头 2026-10-11 记的 L0 全灭)。 + .venv 排在 PATH 前面的机器都会重犯(同 scripts/ci_local.py 文件头 2026-10-11 记的静态门禁全灭)。 修法:脚本自己按优先级挑解释器(显式 DTU_GEN_PYTHON > 当前解释器 > 仓库 .venv > PATH 探到的 机器级)**且逐个探针校验**(要真能 import fontTools 才算数),整轮生成器统一用挑出来的这一个 跑(换解释器就可能换输出格式,逐字节比对要的是同一把尺子);一个都挑不出来时打 [ENV] + 候选 @@ -167,7 +167,7 @@ def ws_path(rel): # 显式变量 DTU_GEN_PYTHON > 当前解释器 > 仓库 .venv > PATH 上探到的机器级,并且**必须探针校验** # 候选真的跑得动(装了 pre-commit ≠ 装了 fontTools;Microsoft Store 的 python 占位程序更是跑不起 # 来)。为什么不能只把钩子 entry 写死成 .venv 里的解释器:ci_local.py 文件头记着 2026-10-11 那次 -# 「workflow 把候选收窄成 PATH python + 仓库 .venv,runner 上两者都是死路,L0 全灭」——收窄候选 +# 「workflow 把候选收窄成 PATH python + 仓库 .venv,runner 上两者都是死路,静态门禁全灭」——收窄候选 # 等于把门禁绑死在一台机器上,本处保留全部候选 + 逐个探针,就是为了不再犯第三次。 PROBE_CODE = ( "import sys\n" diff --git a/scripts/check_registration.py b/scripts/check_registration.py index 8098cc8b..5f501dfc 100644 --- a/scripts/check_registration.py +++ b/scripts/check_registration.py @@ -88,20 +88,20 @@ SIM_EXEMPT = [ ("App/algo/adc_input.c", "依赖 HAL/TPAFE 取数,MCU 专属"), ("App/algo/phasor.c", "全周傅氏相量纯算法,模拟器无消费路径"), ("App/algo/prot_elm.c", "保护判据元件纯算法,模拟器无消费路径"), - ("App/util/boot_stats.c", "开机账本(SD2506 用户 SRAM + RCC_CSR 快照,依赖 I2C/HAL 位掩码),MCU 专属;L0.5 覆盖于 test/host/boot_stats"), + ("App/util/boot_stats.c", "开机账本(SD2506 用户 SRAM + RCC_CSR 快照,依赖 I2C/HAL 位掩码),MCU 专属;主机侧纯逻辑单测覆盖于 test/host/boot_stats"), ("App/algo/prot_set.c", "动作留痕保护定值纯算法,模拟器无消费路径"), ("App/algo/freq.c", "全周傅氏相量派生测频纯算法(输入=phasor 使用区),模拟器无消费路径"), ("App/cfg/prot_set_store.c", "定值持久化 MCU 胶水(依赖 prot_set 纯算法),模拟器无消费路径"), - ("App/log/soe_rec.c", "SOE 记录编解码纯逻辑(B9),L0.5 主机单测覆盖,模拟器无消费路径"), + ("App/log/soe_rec.c", "SOE 记录编解码纯逻辑(B9),主机侧纯逻辑单测覆盖,模拟器无消费路径"), ("App/log/soe_store.c", "SOE 落盘 MCU 胶水(队列+FatFs 单写者,B9),模拟器无消费路径"), ("App/algo/sample_ring.c", "全周傅氏相量环形缓冲纯算法,模拟器无消费路径"), ("App/algo/adc_cal.c", "标定折算纯算法 + cfg 段注册(消费者 = MCU 侧 adcTask 每拍;模拟器不跑采样任务),模拟器无消费路径"), - ("App/algo/meas.c", "测频与遥测快照发布纯算法(唯一发布者 = MCU 侧 adcTask 200 ms 分频;104 改读快照与 UI 侧消费属另一波次另行接线,模拟器无消费路径;L0.5 单测 test/host/meas 覆盖),模拟器无消费路径"), - ("App/algo/run_par.c", "运行参数段纯算法 + [run_par] cfg 段注册 + 104 地址映射(写者 = iec104Task 直通、属主落盘 defaultTask;模拟器不跑 104 协议,模拟器无消费路径;L0.5 单测 test/host/run_par 覆盖),模拟器无消费路径"), + ("App/algo/meas.c", "测频与遥测快照发布纯算法(唯一发布者 = MCU 侧 adcTask 200 ms 分频;104 改读快照与 UI 侧消费属另一波次另行接线,模拟器无消费路径;主机侧纯逻辑单测 test/host/meas 覆盖),模拟器无消费路径"), + ("App/algo/run_par.c", "运行参数段纯算法 + [run_par] cfg 段注册 + 104 地址映射(写者 = iec104Task 直通、属主落盘 defaultTask;模拟器不跑 104 协议,模拟器无消费路径;主机侧纯逻辑单测 test/host/run_par 覆盖),模拟器无消费路径"), ("App/app/app_main.c", "固件启动/就绪屏障,MCU 专属"), ("App/app/ui_data_feed.c", "UI 快照装配器(依赖 phasor/prot_set/iec104 端口常量等固件侧数据源);模拟器不跑装配链,快照由 simulator/main.c 直接发布固定帧,模拟器无消费路径"), ("App/fs/vol_lock.c", "FreeRTOS 互斥量卷锁,MCU 专属"), - ("App/fs/vol_idle_sync.c", "MCU 存储属主后台空闲同步(FatFs disk_ioctl + 卷锁 + sys_events),L0.5 覆盖于 test/host/vol_idle_sync/,模拟器无消费路径"), + ("App/fs/vol_idle_sync.c", "MCU 存储属主后台空闲同步(FatFs disk_ioctl + 卷锁 + sys_events),主机侧纯逻辑单测覆盖于 test/host/vol_idle_sync/,模拟器无消费路径"), ("App/log/console_log.c", "MCU 控制台输出,模拟器无消费路径"), ("App/sync/time_sync.c", "对时任务(net+RTOS),MCU 专属"), ("App/task/adc_task.c", "固件 FreeRTOS 任务,模拟器不跑固件任务"), @@ -111,8 +111,8 @@ SIM_EXEMPT = [ ("App/task/net_task.c", "固件 FreeRTOS 任务,模拟器不跑固件任务"), ("App/task/rs485_task.c", "固件 RS-485 任务,MCU 专属(另:uvprojx 缺口见 KNOWN_GAPS)"), ("App/task/ui_task.c", "固件 UI 任务,模拟器另有 main.c 驱动 UI"), - ("App/protocol/iec104/iec104_asdu.c", "104 ASDU 编解码纯逻辑,L0.5 主机单测覆盖,模拟器无消费路径"), - ("App/protocol/iec104/iec104_link.c", "104 链路状态机纯逻辑(I/O 注入),L0.5 覆盖,模拟器无消费路径"), + ("App/protocol/iec104/iec104_asdu.c", "104 ASDU 编解码纯逻辑,主机侧纯逻辑单测覆盖,模拟器无消费路径"), + ("App/protocol/iec104/iec104_link.c", "104 链路状态机纯逻辑(I/O 注入),主机侧纯逻辑单测覆盖,模拟器无消费路径"), ("App/protocol/iec104/iec104_pts.c", "104 点表/数据源适配(phasor/prot 依赖),模拟器无消费路径"), ("App/util/sha256.c", "纯工具(FTP 鉴权在 MCU 端),模拟器无消费路径"), ("App/util/sys_clock.c", "RTC/SD2506 依赖墙钟,MCU 专属"), diff --git a/scripts/check_size_gate.py b/scripts/check_size_gate.py index 7c96123e..b823678c 100644 --- a/scripts/check_size_gate.py +++ b/scripts/check_size_gate.py @@ -22,7 +22,7 @@ Grand Totals;2) `.map` 不早于指纹范围内任何源码的 mtime(防"改完代码 拿旧构建盖章");3) 受管区占比 <= `ram_baseline.THRESHOLD_PCT`(与 mem-budget 同一根线、同一个常数,不另立第二套口径)。 - --local 开发者自查 / ci_local 的 L0c 步:工作树指纹必须等于凭证指纹。 + --local 开发者自查 / ci_local 的体积门禁步:工作树指纹必须等于凭证指纹。 --ci runner 上跑(无 Keil、无 `.map`):A. 同码性;B. 凭证里记录的受管区 占比 <=92%(数字来自一次真实链接);C. 与 `ram_baseline.tsv` 末行同源=OK, 不同源=WARN(多半只 --stamp 没 --update,不拦)。 diff --git a/scripts/check_test_coverage.py b/scripts/check_test_coverage.py index c221cd7a..73173786 100644 --- a/scripts/check_test_coverage.py +++ b/scripts/check_test_coverage.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- r""" -主机侧单元测试(L0.5)的**覆盖率**口径、报告与增量门禁(纯标准库)。 +主机侧纯逻辑单元测试的**覆盖率**口径、报告与增量门禁(纯标准库)。 做什么: - 1. 发现 test/host/**/run_unit_test.py(与 scripts/ci_local.py 的 L0.5 同一套发现规则), + 1. 发现 test/host/**/run_unit_test.py(与 scripts/ci_local.py 的主机侧纯逻辑单测同一套发现规则), 直接 import 各模块运行器、复用其中声明的 TestBuild(**单一事实源**:include 目录、 桩顺序、测试单元路径都不在这里重复一遍); 2. 用**与门禁完全相同的编译开关**(unit_test_harness._BASE_FLAGS)另建一份带 /Zi 调试 @@ -16,7 +16,7 @@ r""" 为什么要它(2026-10-23,W5 验证常态): 原评审报告 §4.8 记的两处空白之一(该报告 2026-10-25 已删除):无覆盖率口径—— - L0.5 只回答“跑过哪些 TC”,回答不了“生产源码里哪些行根本没进过”。用例数与 TC 追溯率 + 主机侧纯逻辑单测只回答“跑过哪些 TC”,回答不了“生产源码里哪些行根本没进过”。用例数与 TC 追溯率 会随时间上升,但覆盖率照的是另一类真相:**新增分支一行没测**(那类“读代码 才发现”的缺陷,覆盖率是最便宜的机器照法)。 @@ -127,7 +127,7 @@ def load_harness(): def discover_modules(): - """{模块名: run_unit_test.py 绝对路径};发现规则与 ci_local.py 的 L0.5 一致。""" + """{模块名: run_unit_test.py 绝对路径};发现规则与 ci_local.py 的主机侧纯逻辑单测一致。""" out = {} if not HOST_TEST_DIR.is_dir(): return out @@ -825,7 +825,7 @@ def run(args): def main(): ap = argparse.ArgumentParser( prog="check_test_coverage.py", - description="主机侧单测(L0.5)覆盖率:插桩跑测试 + 统计自研生产源码行/函数覆盖 + 基线增量门禁") + description="主机侧纯逻辑单测覆盖率:插桩跑测试 + 统计自研生产源码行/函数覆盖 + 基线增量门禁") ap.add_argument("--module", action="append", metavar="NAME", help="只处理指定模块(可重复;默认全部)") ap.add_argument("--list", action="store_true", help="只列出发现的模块名") diff --git a/scripts/ci_find_python.ps1 b/scripts/ci_find_python.ps1 index bbf85e29..0754bc92 100644 --- a/scripts/ci_find_python.ps1 +++ b/scripts/ci_find_python.ps1 @@ -20,7 +20,7 @@ The interpreter that does work (a machine-wide install such as C:\ProgramData\anaconda3\python.exe) was no longer probed, and no - machine-level DTU_PRECOMMIT_PYTHON had ever been set -> every L0 job died + machine-level DTU_PRECOMMIT_PYTHON had ever been set -> every static-gate job died in "environment check" before running a single hook. So discovery lives here now: one implementation, shared by both CI jobs and @@ -38,7 +38,7 @@ %LOCALAPPDATA%\Programs\Python), newest version first .PARAMETER NeedPreCommit - Require the interpreter to expose the pre-commit module (L0 needs it). + Require the interpreter to expose the pre-commit module (static gates need it). Probed with `-s` (no user site-packages) on purpose: the runner service runs as SYSTEM, which cannot see a USER-scope site install at all, so only an interpreter that works without user-site is accepted. diff --git a/scripts/ci_local.py b/scripts/ci_local.py index 456c9223..dc99473e 100644 --- a/scripts/ci_local.py +++ b/scripts/ci_local.py @@ -8,13 +8,13 @@ 跑同一套判定,命令与退出码与 CI 一一对应。 覆盖范围(与 CI 完全一致): - L0 静态门禁 = `pre-commit run --all-files` + 提交信息门禁(gitlint) - L0c 体积门禁(强制路径,2026-10-25)= `scripts/check_size_gate.py --local` + 静态门禁(static) = `pre-commit run --all-files` + 提交信息门禁(gitlint) + 体积门禁(size,强制路径,2026-10-25)= `scripts/check_size_gate.py --local` **fail-closed**:没有体积同码凭证、或代码与测量不同码即红——不再"没跑=通过"。 CI 侧的「体积门禁」步跑的是同一条实现(那条无需 Keil,见 .gitea/workflows/ci.yml)。 - L0.5 主机侧纯逻辑单测 = `test/host/**/run_unit_test.py` - L0.5c 覆盖率(**opt-in:`--coverage`**,2026-10-23)= `scripts/check_test_coverage.py` - 默认**不跑**、CI 也**不跑**:它依赖 VS 可选的「代码覆盖率工具」组件,且与 L0.5 那 16 个 + 主机侧纯逻辑单测(host-unit) = `test/host/**/run_unit_test.py` + 主机单测覆盖率(coverage,**opt-in:`--coverage`**,2026-10-23)= `scripts/check_test_coverage.py` + 默认**不跑**、CI 也**不跑**:它依赖 VS 可选的「代码覆盖率工具」组件,且与主机侧单测那 16 个 钩子跑的是同一批测试(覆盖率只是换一种看法)。口径与盲区 → docs/测试说明/主机单测覆盖率口径.md ⚠️ gitlint 必须**单独跑**(2026-10-11 审计):`stages: [commit-msg]` 写在 gitlint **上游** @@ -28,25 +28,25 @@ - `@build`(需 Keil 授权;CI 也不跑,真编译是开发者本地红线) - `mem-numbers` / `mem-budget` 钩子(事实源 `.map` 是 Keil 产物——**本地若存在 .map 就照常跑**,与 CI 的自适应逻辑一致)。⚠ 这两根闸在"无 .map"时是 **SKIP 而不是 - PASS**(等于这一趟什么都没查);那个缺口由上面的 L0c 体积门禁补住:同码凭证 + PASS**(等于这一趟什么都没查);那个缺口由上面的体积门禁(size)补住:同码凭证 + 92% 红线复核,无需 Keil 也能判,且 fail-closed。 退出码: 0 = 全部通过(或不适用项已按规则跳过) 1 = 有门禁/单测失败 - 2 = 环境错误(缺 pre-commit / python 模块等;L0a 钩子解释器预检未过也是 2) + 2 = 环境错误(缺 pre-commit / python 模块等;钩子解释器预检未过也是 2) 汇总行(2026-10-02 起,对齐 CI 的 ::notice::): - [SUMMARY] L0: pre-commit hooks -- passed=N failed=M skipped=K + [SUMMARY] static: pre-commit hooks -- passed=N failed=M skipped=K [SUMMARY] gitlint: N commit message(s) checked, M failed - [SUMMARY] L0.5: K runner(s), A/B assertions passed, fail=F + [SUMMARY] host-unit: K runner(s), A/B assertions passed, fail=F 用法(仓库根目录): & '.venv\\Scripts\\python.exe' scripts/ci_local.py - & '.venv\\Scripts\\python.exe' scripts/ci_local.py --l0-only + & '.venv\\Scripts\\python.exe' scripts/ci_local.py --static-only & '.venv\\Scripts\\python.exe' scripts/ci_local.py --size-only - & '.venv\\Scripts\\python.exe' scripts/ci_local.py --l05-only - & '.venv\\Scripts\\python.exe' scripts/ci_local.py --preflight-only # 只跑 L0a 钩子解释器预检 + & '.venv\\Scripts\\python.exe' scripts/ci_local.py --host-unit-only + & '.venv\\Scripts\\python.exe' scripts/ci_local.py --preflight-only # 只跑钩子解释器预检 & '.venv\\Scripts\\python.exe' scripts/ci_local.py --keep-going # 失败也继续跑完 & '.venv\\Scripts\\python.exe' scripts/ci_local.py --commits 20 # 提交信息校验条数 @@ -69,7 +69,7 @@ def _global_python_candidates(): """常见**机器级** Python 安装位置(必须与 scripts/ci_find_python.ps1 同序)。 两处探测一旦不同步,就会出现"本地能跑、CI 挂"的老毛病:2026-10-11 的 - L0 全灭就是 workflow 把候选收窄成 PATH python + 仓库 .venv(runner 上两者 + 静态门禁全灭就是 workflow 把候选收窄成 PATH python + 仓库 .venv(runner 上两者 都是死路),而真正装了 pre-commit 的机器级解释器(如 C:\\ProgramData\\anaconda3\\python.exe)不再被探测。 @@ -362,7 +362,7 @@ def run_hook_preflight(): """ print("") print("=" * 64) - print(" L0a 钩子解释器预检(只读,不改环境)") + print(" preflight 钩子解释器预检(只读,不改环境)") print("=" * 64) hook = REPO_ROOT / ".git" / "hooks" / "pre-commit" @@ -467,7 +467,7 @@ def _run(cmd, env=None): def _run_capture(cmd, env=None): """跑子进程,输出**既直通终端又捕获**(对齐 CI 的 Tee-Object)。 - 返回 (退出码, 全部输出文本)。用于 L0.5 解析各模块汇总行。 + 返回 (退出码, 全部输出文本)。用于主机侧纯逻辑单测解析各模块汇总行。 """ print("$ " + " ".join(str(c) for c in cmd)) cp = subprocess.run(cmd, cwd=str(REPO_ROOT), env=env, @@ -485,11 +485,11 @@ def _run_capture(cmd, env=None): return cp.returncode, text -def run_l0(): - """L0:pre-commit 全部钩子。返回退出码(0/1/2)。""" +def run_static_gates(): + """静态门禁:pre-commit 全部钩子。返回退出码(0/1/2)。""" print("") print("=" * 64) - print(" L0 静态门禁(pre-commit --all-files)") + print(" static 静态门禁(pre-commit --all-files)") print("=" * 64) env = dict(os.environ) @@ -514,7 +514,7 @@ def run_l0(): passed = sum(1 for l in lines if l.rstrip().endswith("Passed")) failed = sum(1 for l in lines if l.rstrip().endswith("Failed")) skipd = sum(1 for l in lines if l.rstrip().endswith("Skipped")) - print("[SUMMARY] L0: pre-commit hooks -- passed={} failed={} skipped={}".format( + print("[SUMMARY] static: pre-commit hooks -- passed={} failed={} skipped={}".format( passed, failed, skipd)) return rc @@ -528,7 +528,7 @@ def run_gitlint(count): """ print("") print("=" * 64) - print(" L0b 提交信息门禁(gitlint / commit-msg stage,最近 {} 条)".format(count)) + print(" commitmsg 提交信息门禁(gitlint / commit-msg stage,最近 {} 条)".format(count)) print("=" * 64) def _git(*args): @@ -589,7 +589,7 @@ def run_gitlint(count): def run_size_gate(): - """L0c:体积门禁(强制路径)。返回退出码(0/1/2)。 + """体积门禁(强制路径)。返回退出码(0/1/2)。 为什么必须单列一步(2026-10-25):`mem-numbers` / `mem-budget` 的事实源是 Keil `.map`,无 `.map` 时两者**自适应 SKIP 并返回 0**——"改了代码但根本没编译" @@ -603,7 +603,7 @@ def run_size_gate(): """ print("") print("=" * 64) - print(" L0c 体积门禁(同码凭证 + 92% 红线,无需 Keil)") + print(" size 体积门禁(同码凭证 + 92% 红线,无需 Keil)") print("=" * 64) script = REPO_ROOT / "scripts" / "check_size_gate.py" if not script.is_file(): @@ -619,11 +619,11 @@ def run_size_gate(): return rc -def run_l05(args): - """L0.5:主机侧单测。返回退出码(0/1/2)。""" +def run_host_unit_tests(args): + """主机侧纯逻辑单测。返回退出码(0/1/2)。""" print("") print("=" * 64) - print(" L0.5 主机侧纯逻辑单测(MSVC,零硬件)") + print(" host-unit 主机侧纯逻辑单测(MSVC,零硬件)") print("=" * 64) runners = _find_unit_runners() @@ -665,7 +665,7 @@ def run_l05(args): else: print("[OK] {}".format(rel)) - print("[SUMMARY] L0.5: {} runner(s), {}/{} assertions passed, fail={}".format( + print("[SUMMARY] host-unit: {} runner(s), {}/{} assertions passed, fail={}".format( runner_count, sum_pass, sum_total, rc_all)) return rc_all @@ -673,12 +673,12 @@ def run_l05(args): def run_coverage(): """覆盖率附加步骤(opt-in,CI 不跑):主机单测覆盖率 + 基线增量门禁。 - 单独一步而不并进 L0.5:L0.5 是**必须绿**的门禁,覆盖率是**趋势视图**,两者失败含义不同 + 单独一步而不并进主机侧纯逻辑单测:那一层是**必须绿**的门禁,覆盖率是**趋势视图**,两者失败含义不同 (前者=测试挂了,后者=覆盖回退了 / 缺工具链)。退出码原样透传,由 main 汇总。 """ print("") print("=" * 64) - print(" L0.5c 主机单测覆盖率(mscoverage 动态插桩 + 基线增量门禁)") + print(" coverage 主机单测覆盖率(mscoverage 动态插桩 + 基线增量门禁)") print("=" * 64) script = REPO_ROOT / "scripts" / "check_test_coverage.py" if not script.is_file(): @@ -702,16 +702,16 @@ def main(argv=None): pass ap = argparse.ArgumentParser( - description="本地跑与 CI 等价的门禁(L0 + L0.5,纯标准库)") + description="本地跑与 CI 等价的门禁(静态门禁 + 主机侧纯逻辑单测,纯标准库)") g = ap.add_mutually_exclusive_group() - g.add_argument("--l0-only", action="store_true", help="只跑 L0 静态门禁") - g.add_argument("--l05-only", action="store_true", help="只跑 L0.5 主机侧单测") + g.add_argument("--static-only", "--l0-only", dest="static_only", action="store_true", help="只跑静态门禁(旧旗标 --l0-only 仍可用)") + g.add_argument("--host-unit-only", "--l05-only", dest="host_unit_only", action="store_true", help="只跑主机侧纯逻辑单测(旧旗标 --l05-only 仍可用)") g.add_argument("--size-only", action="store_true", - help="只跑 L0c 体积门禁(强制路径;约 0.3 s,无需 Keil)") + help="只跑体积门禁(强制路径;约 0.3 s,无需 Keil)") ap.add_argument("--coverage", action="store_true", help="额外跑主机单测覆盖率(约 25 s;需 VS 代码覆盖率组件;默认不跑,CI 也不跑)") ap.add_argument("--preflight-only", action="store_true", - help="只跑 L0a 钩子解释器预检(只读;缺包时当场指名钩子)") + help="只跑钩子解释器预检(只读;缺包时当场指名钩子)") ap.add_argument("--keep-going", action="store_true", help="某步失败也继续跑后续步骤(默认首错即停)") ap.add_argument("--commits", type=int, default=10, metavar="N", @@ -740,50 +740,50 @@ def main(argv=None): print(" - 或在当前环境安装:pip install pre-commit") return 2 - # L0a 钩子解释器预检:把"裸 python 条目 + 缺第三方包 + 脚本不自解析"这条隐性依赖 + # 钩子解释器预检(preflight):把"裸 python 条目 + 缺第三方包 + 脚本不自解析"这条隐性依赖 # 提到所有钩子之前判掉(道G task-33)。--preflight-only 时它是唯一一段。 rc_pre = run_hook_preflight() if args.preflight_only: return rc_pre if rc_pre == 2: print("") - print("[FAIL] L0a 预检未过——先按上面指名的钩子/包修配置或环境,再跑门禁") + print("[FAIL] 钩子解释器预检未过——先按上面指名的钩子/包修配置或环境,再跑门禁") if not args.keep_going: return 2 rc = 0 # 实际跑过哪些段——收尾那行只报这些。理由与那个盲区同源且是同一类错误: - # "--l05-only 却宣布 L0/L0b/L0c 全部通过"就是把没跑的说成跑过了(SKIP 冒充 PASS - # 的镜像版)。2026-10-25 做 task-10 时从 --l05-only 的输出里发现,当场改掉。 - ran = ["L0a"] # 钩子解释器预检已在本脚本开头无条件跑过,收尾行照实列出 + # "--host-unit-only 却宣布静态门禁/提交信息门禁/体积门禁全部通过"就是把没跑的说成跑过了(SKIP 冒充 PASS + # 的镜像版)。2026-10-25 做 task-10 时从 --host-unit-only 的输出里发现,当场改掉。 + ran = ["preflight"] # 钩子解释器预检已在本脚本开头无条件跑过,收尾行照实列出 try: if args.size_only: return run_size_gate() - if not args.l05_only: - rc = run_l0() - ran.append("L0") + if not args.host_unit_only: + rc = run_static_gates() + ran.append("static") # 提交信息门禁独立于 --all-files(stage 不同),故单独跑一段; - # 即使 L0 已失败也照跑,一次看全所有违规项。 + # 即使静态门禁已失败也照跑,一次看全所有违规项。 rc_msg = run_gitlint(args.commits) - ran.append("L0b") - # L0c 体积门禁同样**无条件照跑**:它回答的是"这份代码测过体积没有", - # 与 L0/L0b 的结论正交,且只要 0.3 s——少跑一次就是那个盲区。 + ran.append("commitmsg") + # 体积门禁同样**无条件照跑**:它回答的是"这份代码测过体积没有", + # 与静态门禁/提交信息门禁的结论正交,且只要 0.3 s——少跑一次就是那个盲区。 rc_size = run_size_gate() - ran.append("L0c") + ran.append("size") if rc != 0 or rc_msg != 0 or rc_size != 0: if not args.keep_going: print("") - print("[FAIL] L0 / L0b / L0c 有未通过项 —— 中止" - "(--keep-going 可继续跑 L0.5)") + print("[FAIL] 静态门禁 / 提交信息门禁 / 体积门禁有未通过项 —— 中止" + "(--keep-going 可继续跑主机侧纯逻辑单测)") return 1 rc = rc or rc_msg or rc_size - if not args.l0_only: - rc05 = run_l05(args) - ran.append("L0.5") - rc = rc or rc05 + if not args.static_only: + rc_hu = run_host_unit_tests(args) + ran.append("host-unit") + rc = rc or rc_hu if args.coverage: rc = rc or run_coverage() - ran.append("L0.5c") + ran.append("coverage") except FileNotFoundError as e: print("[ERROR] 环境错误:{}".format(e)) return 2 diff --git a/scripts/ci_runner_bootstrap.ps1 b/scripts/ci_runner_bootstrap.ps1 index 253f20eb..04b2886f 100644 --- a/scripts/ci_runner_bootstrap.ps1 +++ b/scripts/ci_runner_bootstrap.ps1 @@ -27,7 +27,7 @@ 2. NO MACHINE-LEVEL TOOLCHAIN. The runner service runs as SYSTEM. pre-commit / cppcheck were only reachable through the interactive user's PATH, and no machine-level DTU_* variable was ever set, so - even a live runner died in the L0 environment check. + even a live runner died in the static-gate environment check. This script installs the missing supervision (a watchdog launcher + a scheduled task) and, with -SetEnv, writes the machine-level DTU_* variables @@ -361,7 +361,7 @@ if ($SetEnv) { $cl = Find-MsvcCl if ($cl) { Set-MachineEnv 'DTU_MSVC_CL' $cl 'DTU_MSVC_CL' } - else { Warn 'MSVC cl.exe (Hostx64\x64) not found -- L0.5 will degrade to "skipped" without it' } + else { Warn 'MSVC cl.exe (Hostx64\x64) not found -- host-side unit tests will degrade to "skipped" without it' } Info 'machine env vars are read when a PROCESS STARTS: the daemon must be restarted below' } diff --git a/scripts/ram_baseline.py b/scripts/ram_baseline.py index 4a44996a..d69f189c 100644 --- a/scripts/ram_baseline.py +++ b/scripts/ram_baseline.py @@ -176,7 +176,7 @@ def cmd_gate(args): print("[SKIP] 无 .map(未做 Keil 构建)→ RAM 预算门禁不适用") print(" ⚠ **SKIP 不等于 PASS**:本钩子这一趟什么都没检查。") print(" 强制兜底(无需 Keil,改代码后必然拦红):") - print(" 本地 scripts/ci_local.py 的 L0c 步 / CI 的「体积门禁」步,") + print(" 本地 scripts/ci_local.py 的体积门禁步 / CI 的「体积门禁」步,") print(" 都跑 scripts/check_size_gate.py(同码凭证 + 92% 红线复核)。") print(" 改过代码请先 @build,再 python scripts/ram_baseline.py --update。") return 0 @@ -334,7 +334,7 @@ def cmd_update(args): # 强制路径(2026-10-25):同一次「真实测量」顺带刷新**体积同码凭证** # scripts/size_attestation.json——这样「每次 @build 后跑 --update」这条**既有 # 纪律**同时喂饱那条无需 Keil 也能判的强制门禁(check_size_gate --ci / - # --local,即 ci_local 的 L0c 步与 CI 的体积门禁步),不新增人工步骤。 + # --local,即 ci_local 的体积门禁步与 CI 的体积门禁步),不新增人工步骤。 # 盖戳失败只告警、不改本命令返回值:凭证新鲜度由门禁自己 fail-closed 兜, # 失败原因(.map 比源码旧 / 已超线)会在门禁那一侧明确报出来。 # 为什么在函数内 import:check_size_gate 模块级 import 本模块,反过来即成环。