Skip to content

Commit 31b5923

Browse files
committed
fix(hooks): 提交门禁改直跑全树校验
- .husky/pre-commit 不再经 lint-staged:改为 `node scripts/line-limits.mjs --full && node scripts/docs-check.mjs`(任一失败即非零)。 - 两个检查都是全树只读扫描、且忽略传入的文件参数(line-limits 只认 --write/--full) ⇒ lint-staged 的「暂存文件列表」语义对它们零贡献,却带回 stash / reset --hard / hide / auto-stage / .git/lint-staged_unstaged.patch 一整类风险。 - 用 --full 的理由:本地与 CI 同口径守 (e)「登记值 == 实测值」;--full 只读,写表只走 --write,故不会逼人重生成表。旧接线只在 {package.json, scripts/line-limits.mjs, docs/standards/line-limit-exemptions.md} 三者之一被暂存时才跑 --full —— 实测只暂存 一个改动过的源文件时该段 0 files / SKIPPED,改源码造成的登记值过期在本地永远绿灯。 - 门禁每次都跑全树 ⇒ 不再依赖 glob:glob app/**/*.{ts,tsx,rs} 命中 872 个 tracked 文件而非扫描域 868(差 build.rs、examples/capture_ocr_diag.rs、vite.config.ts、 vitest.config.ts),命令跑了却不被看见;直跑全树消除这处假覆盖。 - package.json:删除已成死配置的 lint-staged 块(devDependencies 的 lint-staged 保留, 留给将来的文件级任务);dependencies/devDependencies/scripts 无变化。 - scripts/line-limits.mjs(仍 299 行 ≤300):docblock 等价性措辞改准(纯 \r 分行与 内容只有 BOM 两类例外,仓内 0 命中);--full 成功文案补「· 数值一致」标记。
1 parent 6d77572 commit 31b5923

3 files changed

Lines changed: 20 additions & 16 deletions

File tree

‎.husky/pre-commit‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
# Why: lint-staged 默认 stash:true + hidePartiallyStaged:true,而它的**失败路径**是
2-
# `git reset --hard HEAD` → 再靠 `git stash apply --index <备份>` 把工作树还原 → 最后 drop 掉
3-
# 那份备份(`git stash list` 随即为空,只剩一个悬空 commit)。即:工作树先被整体清掉、
4-
# 还原再不成立就当场丢在制品(控制方实测到过丢改动;本机实测到过整文件被 CRLF 化)。
5-
# 本批是多代理并发 + 常带未暂存改动 ⇒ 门禁在任何路径上都不得碰工作树。
6-
# `--no-stash` 让 lint-staged 完全不碰工作树/索引(且上游 implies 关掉 hide);
7-
# 这里把 `--no-hide-partially-staged` 也写死,避免将来 imply 变化后悄悄退回
8-
# "藏了未暂存部分却没有备份可还原"(那条路失败时必丢)。
9-
npx lint-staged --no-stash --no-hide-partially-staged
1+
# Why(2026-09-11 终局评审后重接):**绕开 lint-staged,直跑全树校验**。
2+
# ① 两个检查都是**全树只读扫描**,且**忽略传入的文件参数**(`line-limits.mjs` 只认
3+
# `--write` / `--full`)⇒ lint-staged「把暂存文件列表交给命令」的语义对它们**零贡献**,
4+
# 却带进 stash / `git reset --hard` / hide / auto-stage / `.git/lint-staged_unstaged.patch`
5+
# 一整类风险(`lint-staged@15.5.0` 的失败路径曾把未暂存改动丢出工作树,见
6+
# docs/versions/v0.22.md「工具链真相 2」)。
7+
# ② 这里用 `--full`:让**本地与 CI 同口径**守 (e)「登记值 == 实测值」。`--full` 是**只读**的
8+
# (写登记表只走 `--write`),故它**不会**逼人每次提交重生成表。
9+
# ③ 门禁**每次都跑全树**,不再依赖 `package.json` 的 glob:旧接线只在暂存到
10+
# `{package.json, scripts/line-limits.mjs, docs/standards/line-limit-exemptions.md}` 之一时
11+
# 才跑 `--full`(只暂存一个改动过的源文件时该段 0 files / SKIPPED ⇒ (e) 在本地全时绿灯),
12+
# 而 glob `app/**/*.{ts,tsx,rs}` 命中的 **872** 个 tracked 文件也**大于**脚本扫描域的 **868** 个
13+
# (差 `app/src-tauri/build.rs`、`app/src-tauri/examples/capture_ocr_diag.rs`、`app/vite.config.ts`、
14+
# `app/vitest.config.ts`)⇒「命令跑了、这 4 个文件却没被看见」。直跑全树把这两处一起消掉。
15+
# `&&` 串联:任一失败即以非零退出,husky 拦下提交(不吞前一段的退出码)。
16+
node scripts/line-limits.mjs --full && node scripts/docs-check.mjs

‎package.json‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,5 @@
2828
"husky": "9.1.7",
2929
"lint-staged": "15.5.0",
3030
"semantic-release": "24.2.3"
31-
},
32-
"lint-staged": {
33-
"docs/**/*.md": "node scripts/docs-check.mjs",
34-
"app/**/*.{ts,tsx,rs}": "node scripts/line-limits.mjs",
35-
"{package.json,scripts/line-limits.mjs,docs/standards/line-limit-exemptions.md}": "node scripts/line-limits.mjs --full"
3631
}
3732
}

‎scripts/line-limits.mjs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* 「立刻为零」—— 拆分是 0-C2/C3 的事,本脚本负责让违规**不再增加**且**可见**。
1313
*
1414
* 用法:node scripts/line-limits.mjs [--write|--full]
15+
* 忽略任何传入的文件参数,**永远全树扫描**(门禁因此不必依赖 glob —— 旧 glob 的命中集还大于扫描域)。
1516
*/
1617
import { readdirSync, readFileSync, statSync, writeFileSync, existsSync } from 'node:fs';
1718
import { join, relative, dirname, sep, resolve } from 'node:path';
@@ -56,7 +57,8 @@ const stripOverPrefix = (t) =>
5657

5758
/**
5859
* 行数口径的**唯一实现**:全部行数(含空行)。
59-
* 与 [System.IO.File]::ReadAllLines(path, UTF8).Count 等价:末尾换行不额外算一行。
60+
* 与 [System.IO.File]::ReadAllLines(path, UTF8).Count 对**全部常规文本**等价(末尾换行不额外算一行);
61+
* 例外仅两处(评审 15 例边界比对中唯二不等,仓内 0 命中):纯 `\r` 分行(无 `\n`)的文件、内容只有 BOM 的文件。
6062
*/
6163
export function countLines(absPath) {
6264
const s = readFileSync(absPath, 'utf8');
@@ -285,7 +287,7 @@ function check({ full }) {
285287
console.error(`\n现状:>${HARD_LIMIT} 硬限 ${over}/${FROZEN_OVER_LIMIT.length}(棘轮)· ${SOFT_LIMIT+1}–${HARD_LIMIT} 档 ${band} · 登记条目 ${rows.length}`);
286288
process.exit(1);
287289
}
288-
console.log(`✅ line-limits(${mode}):>${HARD_LIMIT} 硬限 ${over}(棘轮内)· ${SOFT_LIMIT+1}–${HARD_LIMIT} 档 ${band} · 登记条目 ${rows.length}`);
290+
console.log(`✅ line-limits(${mode}${full ? ' · 数值一致' : ''}):>${HARD_LIMIT} 硬限 ${over}(棘轮内)· ${SOFT_LIMIT+1}–${HARD_LIMIT} 档 ${band} · 登记条目 ${rows.length}`);
289291
}
290292

291293
// 主入口判定:**必须**用 fileURLToPath + resolve 比较。

0 commit comments

Comments
 (0)