Skip to content

Commit 96aad2f

Browse files
committed
release: v0.8.2(转交4项:门禁判定归一化 + 安全检查范围收窄 + 指令措辞)
来自并行会话的 handoff 清单(codeguard-findings-handoff-2026-09-22): - ① env 前缀绕过:_normalize_segment 剥段首 NAME=VAL / env / 裸 wrapper (sudo/nohup/…)并跳过 git 全局选项(-C/-c/--git-dir=…)—— `VAR=1 git push`、`git -C /repo push`、`sudo git push` 曾静默放行(实测) - ② vendor 误伤:GUARD 的 vendor 段限定**仓根级**=依赖快照;嵌套 (scripts/vendor/skill_vendor.py,skills-check 依赖它)是第一方源码树 - ④ *.db/*.sqlite/*.log 收窄为**仓根级**一池;tests/fixtures/ 的合法 fixture 数据库与归档日志不再被拦,根目录数据转储仍拦;密钥类保持任意层级 - ③ 指辞:gate_directive 不再无条件无需向用户确认——纯 lint 可继续, 付费/发布/删除/密钥/跨仓须先征得同意;guard/UPS 安全段分流 (密钥类立即移出+轮换;非密钥类先确认是否第一方/合法 fixture) - 附带修复 UPS 潜伏崩溃:只有安全违规、零 lint 失败时 notify 取 failures[0][0] 越界 → fail-open 吞成空输出(新测试实测复现后修复) - __protocol__.md §4 同步归一化语义;README 双语版本 0.8.2 验证:unittest 94(+10 回归)、run_all 141/0、openspec 12/12、门禁等价 lint 全绿 转交第⑤项(Kimi 5h 配额窗口)为外部状态,不属本仓改动。
1 parent 58c1f9b commit 96aad2f

11 files changed

Lines changed: 211 additions & 24 deletions

File tree

‎.agents/plugins/marketplace.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@
99
"source": {
1010
"source": "url",
1111
"url": "https://github.com/partme-ai/partme-codeguard-plugin.git",
12-
"ref": "v0.8.1"
12+
"ref": "v0.8.2"
1313
},
1414
"policy": {
1515
"installation": "AVAILABLE",
1616
"authentication": "ON_USE"
1717
},
1818
"category": "Developer Tools",
19-
"version": "0.8.1",
19+
"version": "0.8.2",
2020
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
21-
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.1/assets/official-logo.png",
21+
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.2/assets/official-logo.png",
2222
"interface": {
2323
"displayName": "代码规范守卫",
2424
"shortDescription": "Make AI-written code pass lint on first try",
25-
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.1/assets/official-logo.png"
25+
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.2/assets/official-logo.png"
2626
}
2727
}
2828
]

‎.codex-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codeguard",
3-
"version": "0.8.1+codex.20260922",
3+
"version": "0.8.2+codex.20260922",
44
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
55
"author": {
66
"name": "Full Stack Skills / PartMe.AI",

‎.zcode-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"en": "CodeGuard",
66
"zh-CN": "代码规范检查"
77
},
8-
"version": "0.8.1",
8+
"version": "0.8.2",
99
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
1010
"description_i18n": {
1111
"en": "Cross-language code lint enforcement. PostToolUse hook auto-runs the language-specific linter on every AI-written file; failed lint blocks further writes when strict_mode is on. Ships ready-to-go .pre-commit-config.yaml templates for Java/Rust/TypeScript/Python.",

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ AI code that passes lint on first try
6767
|---|---|
6868
| Plugin ID | `partme-codeguard-plugin` |
6969
| Hosts | ZCode, Claude Code, Codex CLI, Kimi Code |
70-
| Current version | `0.8.1` |
70+
| Current version | `0.8.2` |
7171
| ZCode manifest | `.zcode-plugin/plugin.json` |
7272
| Codex manifest | `.codex-plugin/plugin.json` |
7373
| MCP server | Published: stdio server via the official SDK (`check_code_style` / `auto_fix` / `list_languages`); see Quick start |

‎README.zh-CN.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ AI 一次写出就过 lint 的代码
6767
|---|---|
6868
| 插件 ID | `partme-codeguard-plugin` |
6969
| 宿主 | ZCode、Claude Code、Codex CLI、Kimi Code |
70-
| 当前版本 | `0.8.1` |
70+
| 当前版本 | `0.8.2` |
7171
| ZCode manifest | `.zcode-plugin/plugin.json` |
7272
| Codex manifest | `.codex-plugin/plugin.json` |
7373
| MCP 服务 | 已发布:官方 SDK stdio 服务(`check_code_style` / `auto_fix` / `list_languages`);见快速开始 |

‎hooks/__protocol__.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ if __name__ == "__main__":
6666
## 4. PreToolUse 硬拦截(exit 2)的语义边界
6767

6868
`pre_tool_git_guard.py` 是**唯一**会 exit 2 的 hook——且仅当:
69-
1. 入参命令命中 `is_guarded()`:**直接**(分隔符切段后段首为 `git commit|push`;
69+
1. 入参命令命中 `is_guarded()`:**直接**(分隔符切段、剥除段首 `NAME=VAL`/`env`/常见裸 wrapper 前缀并跳过 git 全局选项(`-C`/`-c`/…)后子命令为 `git commit|push`;
7070
子串匹配会误伤 payload/echo 文本)**或一层解释器间接**(`bash|sh|python… <脚本>`
7171
的脚本文本、`-c` 内联代码按同规则扫描——`bash runner.sh` 式绕过曾连推 4 次漏网;
7272
拼接式 subprocess 不在静态扫描承诺内);并且

‎hooks/gate_lib.py‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@
3636
".nuxt", ".gradle", "vendor", ".idea", ".vscode", "coverage", ".terraform",
3737
".tox", ".eggs", "htmlcov", ".turbo", ".parcel-cache",
3838
}
39-
# 文件名模式(fnmatch):密钥/凭据/本地环境/数据库/系统垃圾
39+
# 文件名模式(fnmatch,任意层级):密钥/凭据/本地环境/系统垃圾
4040
GUARD_EXCLUDE_FILES = [
4141
".env", ".env.*", "*.env", "*.pem", "*.key", "*.p12", "*.pfx", "*.jks",
4242
"*.keystore", "id_rsa", "id_ed25519", "id_ecdsa", "*.pem.orig",
4343
"credentials*.json", "serviceAccount*.json", "*service-account*.json",
44-
"*.sqlite", "*.sqlite3", "*.db", ".DS_Store", "Thumbs.db", "*.log", "*.pyc",
44+
".DS_Store", "Thumbs.db", "*.pyc",
4545
]
46+
# 仅**仓根级**命中的模式:数据库/日志常作第一方 fixture 合法入库
47+
# (tests/fixtures/sample.db、docs 里的归档 log),只拦散落在根目录的
48+
# 数据转储与调试日志——此前与密钥同池任意层级匹配,误伤合法入库(实测)。
49+
GUARD_ROOT_ONLY_FILES = ["*.sqlite", "*.sqlite3", "*.db", "*.log"]
4650

4751

4852
def _git(project_root: Path, *args: str) -> str | None:
@@ -78,7 +82,17 @@ def check_commit_safety(project_root: Path, mode: str) -> list[tuple[str, str, s
7882
if not f:
7983
continue
8084
parts = f.split("/")
81-
hit_dir = next((seg for seg in parts[:-1] if seg in GUARD_EXCLUDE_DIRS), None)
85+
hit_dir = None
86+
for idx, seg in enumerate(parts[:-1]):
87+
if seg not in GUARD_EXCLUDE_DIRS:
88+
continue
89+
# vendor 仅**仓根级**=依赖快照;嵌套(scripts/vendor、tools/vendor)
90+
# 是第一方源码树——裸段匹配曾把 skill_vendor.py 判成"移出版本库"
91+
# (实测误伤,且打断依赖它的 skills-check CI)。
92+
if seg == "vendor" and idx != 0:
93+
continue
94+
hit_dir = seg
95+
break
8296
if hit_dir:
8397
violations.append((
8498
f, f"目录 ./{hit_dir}/ 属于依赖/产物/本地环境,不应入库",
@@ -87,6 +101,11 @@ def check_commit_safety(project_root: Path, mode: str) -> list[tuple[str, str, s
87101
continue
88102
name = parts[-1]
89103
rule = next((pat for pat in GUARD_EXCLUDE_FILES if fnmatch.fnmatch(name, pat)), None)
104+
if rule is None and len(parts) == 1:
105+
# 仓根级 db/log/sqlite 单独一池(见 GUARD_ROOT_ONLY_FILES 注释)
106+
rule = next(
107+
(pat for pat in GUARD_ROOT_ONLY_FILES if fnmatch.fnmatch(name, pat)), None
108+
)
90109
if rule:
91110
violations.append((
92111
f, f"文件命中敏感模式 {rule}(密钥/凭据/本地配置类)",
@@ -466,7 +485,9 @@ def gate_directive(failures: list) -> str:
466485
+ "\n\n"
467486
+ "**给 AI 的强制指令**:提交门禁未通过,禁止执行 git commit / git push。\n"
468487
+ "请立即处理:1) 按上面「怎么修」逐项修复(能自动修复的先跑自动修复命令);"
469-
+ "2) 修复过程中无需向用户确认;3) 全部修复完成后重新执行用户要做的提交操作。\n"
488+
+ "2) 纯 lint 类修复可直接继续、不必逐项追问;但凡涉及付费、发布、删除、"
489+
+ "密钥、或跨出本仓的操作,必须先征得用户同意再执行;"
490+
+ "3) 修复完成后重新执行用户要做的提交操作。\n"
470491
+ "确需绕过(仅用户明确要求时):在该仓库执行 git config codeguard.skipGate true,"
471492
+ "完成后 git config --unset codeguard.skipGate 恢复。环境变量 CODEGUARD_SKIP_GATE "
472493
+ "只对手动直调 run_check 有效(无法传入宿主钩子进程)。\n\n"

‎hooks/pre_tool_git_guard.py‎

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,65 @@ def resolve_project_roots(command: str) -> list[Path]:
9494
return roots
9595

9696

97+
_ASSIGN_RE = None # 延迟编译,保持模块零顶层 re 依赖
98+
_ASSIGN_PATTERN = r"^[A-Za-z_][A-Za-z0-9_.-]*="
99+
_BARE_WRAPPERS = ("env", "sudo", "nohup", "command", "time", "nice", "ionice", "setsid", "stdbuf")
100+
_GIT_VALUE_FLAGS = ("-C", "-c", "--git-dir", "--work-tree", "--exec-path",
101+
"--namespace", "--config-env", "--attr-source")
102+
_GIT_BOOL_FLAGS = ("--no-pager", "--bare", "--literal-pathspecs",
103+
"--no-optional-locks", "--shallow-file", "--exec-path")
104+
105+
106+
def _normalize_segment(seg: str) -> list[str]:
107+
"""剥掉段首的环境赋值/env/裸 wrapper 前缀与 git 全局选项,暴露子命令。
108+
109+
漏检实例(实测静默放行):`VAR=1 git push` 段首是 `VAR=1`;`git -C path push`
110+
tokens[1] 是 `-C`;`sudo git push` 段首是 `sudo`。剥完后若段首仍非 git 则
111+
返回原 tokens(由调用方判定为非 git 段)。
112+
能力边界(docstring 如实声明):带值的 wrapper 参数(`sudo -u root …`)
113+
不做完整解析,只剥裸 wrapper token。
114+
"""
115+
import re as _re
116+
tokens = seg.strip().split()
117+
i, n = 0, len(tokens)
118+
while True:
119+
while i < n and _re.match(_ASSIGN_PATTERN, tokens[i]):
120+
i += 1
121+
if i < n and tokens[i] == "env":
122+
i += 1
123+
continue
124+
break
125+
while i < n and tokens[i] in _BARE_WRAPPERS:
126+
i += 1
127+
if i >= n:
128+
return tokens
129+
if tokens[i].strip("\"'") != "git":
130+
return tokens[i:]
131+
i += 1
132+
while i < n:
133+
tok = tokens[i].strip("\"'")
134+
if tok in _GIT_BOOL_FLAGS:
135+
i += 1
136+
continue
137+
if tok in _GIT_VALUE_FLAGS:
138+
i += 2
139+
continue
140+
if tok.startswith(("-C", "-c", "--git-dir", "--work-tree", "--exec-path")) and len(tok) > 2:
141+
i += 1
142+
continue
143+
break
144+
# 保留 "git" 作为首 token(调用方按 [prog, sub, …] 形状判定),只剥掉前面的
145+
# 前缀与后面的全局选项;`git` 裸段(无子命令)返回 ["git"] → len<2 → None。
146+
return ["git"] + tokens[i:]
147+
148+
97149
def _git_side_effect_sub(seg: str) -> str | None:
98-
"""命令段段首为 git commit|push 时返回子命令(commit/push),否则 None。
150+
"""归一化后,段的子命令为 commit|push 则返回之,否则 None。
99151
100-
首 token 剥一层包裹引号(`bash -c "git commit …"` 切段后首词是 `"git`)。
152+
首 token 剥一层包裹引号(`bash -c "git commit …"` 切段后首词是 `"git`);
153+
前缀与 git 全局选项见 `_normalize_segment`。
101154
"""
102-
tokens = seg.strip().split()
155+
tokens = _normalize_segment(seg)
103156
if len(tokens) < 2:
104157
return None
105158
first = tokens[0].strip("\"'")
@@ -258,9 +311,11 @@ def main() -> int:
258311
violations = check_commit_safety(project_root, mode)
259312
if violations:
260313
reports.append(format_safety_report(violations) + (
261-
"\n\n**给 AI 的强制指令**:先把上述文件移出版本库"
262-
"(git rm --cached + .gitignore),然后重新执行本次 git 命令;"
263-
"涉及密钥/凭据的必须提醒用户轮换,不能只删了事。"
314+
"\n\n**给 AI 的强制指令**:**密钥/凭据类**(.env、*.pem、id_* 等)"
315+
"立即 git rm --cached 并提醒用户轮换密钥,不能只删了事;"
316+
"**非密钥类**(依赖/产物目录、仓根 db/log 等)**先与用户确认**"
317+
"是否为有意入库的第一方代码或合法 fixture——确认属误入库才执行"
318+
"git rm --cached + .gitignore,确认后重新执行本次 git 命令。"
264319
))
265320

266321
if not reports:

‎hooks/user_prompt_validator.py‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,23 @@ def main() -> int:
187187
violations = check_commit_safety(project_root, mode)
188188

189189
if failures or violations:
190-
# 软引导:prompt 正常送达 AI,同时注入修复指令,AI 自动修复后重新提交
190+
# 软引导:prompt 正常送达 AI,且注入修复指令,AI 自动修复后重新提交。
191+
# 注意:可能"只有安全违规、零 lint 失败"——此前 notify 直接取
192+
# failures[0][0] 越界,fail-open 吞成空输出(实测复现),两路都要兜。
191193
_first_issue = failures[0][1].splitlines()[0][:120] if failures and failures[0][1] else "详见对话"
192-
notify(summarize_failures(failures), f"{failures[0][0]}: {_first_issue}")
194+
_headline = (
195+
f"{failures[0][0]}: {_first_issue}" if failures
196+
else (f"提交安全: {violations[0][0]}" if violations else "提交门禁发现违规")
197+
)
198+
notify(summarize_failures(failures), _headline)
193199
parts = [gate_directive(failures)]
194200
if violations:
195201
parts.append(format_safety_report(violations))
196202
parts.append(
197-
"**给 AI 的强制指令**:安全违规文件必须 git rm --cached + 补 .gitignore,"
198-
"涉及密钥/凭据的提醒用户轮换;全部修复后重新执行提交,无需向用户确认。"
203+
"**给 AI 的强制指令**:**密钥/凭据类**必须 git rm --cached 并提醒用户"
204+
"轮换密钥;**非密钥类**先与用户确认是否为有意入库的第一方代码或合法"
205+
" fixture,确认误入库再 git rm --cached + 补 .gitignore;确认并修复后"
206+
"重新执行提交。"
199207
)
200208
print(json.dumps({
201209
"hookSpecificOutput": {

‎kimi.plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codeguard",
3-
"version": "0.8.1",
3+
"version": "0.8.2",
44
"description": "Cross-language code lint enforcement for AI coding assistants (ZCode, Claude Code, Codex CLI, Kimi Code): Java, Rust, TypeScript, Python. PostToolUse hook auto-runs the native linter on every AI-written file and blocks on failure in strict mode.",
55
"author": {
66
"name": "Full Stack Skills / PartMe.AI"

0 commit comments

Comments
 (0)