Skip to content

Commit 9dbbb8d

Browse files
authored
release: v0.8.0(门禁硬化:14 项实弹审计修复全落地) (#27)
- 整调用声明:exit 2 拒绝的是完整 Bash 调用(前序步骤未执行),指令要求拆分调用 - is_guarded 覆盖一层解释器间接(脚本/-c 内联),封堵 runner.sh 式绕过 - 缓存键纳入工作区内容指纹;git 仓缺省 delta 作用域 + gate_scope 项目级覆盖 - exit 2 归未验证(hook skipped / CLI-MCP unverified);节选附总量+完整日志 - scope 层(scripts/scope.py):ruff 默认配置注入、单文件物化、全量剔除 vendor - PostToolUse 单文件 format + 波及清单注入;fix 缺省 delta、--all 全仓 - UPS 非 git 跳过、与硬门共享 skipGate、否定语境不触发;bypass 记账 + Stop 告警 - 状态迁 ~/.codeguard + 条目归一化(修 KeyError-fail-open 潜伏炸弹) - 双副本 SessionStart 告警 + 事件去重;shell.lint 对齐 --severity=warning - __protocol__.md 符号定位;OpenSpec 2026-09-22-harden-commit-gate 三份 delta 已归档 - 测试:run_all 140/1(优于基线 139/2)+ unittest 73 + 新增 29 例硬编码回归
1 parent f8ee7c1 commit 9dbbb8d

34 files changed

Lines changed: 1463 additions & 169 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.7.1"
12+
"ref": "v0.8.0"
1313
},
1414
"policy": {
1515
"installation": "AVAILABLE",
1616
"authentication": "ON_USE"
1717
},
1818
"category": "Developer Tools",
19-
"version": "0.7.1",
19+
"version": "0.8.0",
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.7.1/assets/official-logo.png",
21+
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.0/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.7.1/assets/official-logo.png"
25+
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.0/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.7.1+codex.20260922",
3+
"version": "0.8.0+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.7.1",
8+
"version": "0.8.0",
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.7.1` |
70+
| Current version | `0.8.0` |
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.7.1` |
70+
| 当前版本 | `0.8.0` |
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`);见快速开始 |

‎docs/LANGUAGES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
| PHP | `.php` | `php -l {file}` | `php-cs-fixer fix` | composer require --dev php-cs-fixer |
2121
| Ruby | `.rb` | `rubocop` | `rubocop -A` | gem install rubocop |
2222
| Scala | `.scala` `.sc` | `scalafmt --check .` | `scalafmt` | coursier install scalafmt |
23-
| Shell | `.sh` `.bash` `.zsh` | `shellcheck {file}` | `shfmt -w .` | brew install shellcheck shfmt |
23+
| Shell | `.sh` `.bash` `.zsh` | `shellcheck --severity=warning {file}` | `shfmt -w .` | brew install shellcheck shfmt |
2424
| Dockerfile | (文件名匹配) | `hadolint {file}` | `hadolint` | brew install hadolint |
2525
| YAML | `.yml` `.yaml` | `yamllint .` | `yamllint .` | pip install yamllint |
2626
| Elixir | `.ex` `.exs` | `mix credo --strict` | `mix format` | 项目需配置 credo 依赖 |

‎hooks/__protocol__.md‎

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
| PostToolUse `Write\|Edit\|MultiEdit` | `post_tool_lint.py` | JSON `{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"..."}, "systemMessage":"..."}` | 仅内部错误时 | 0 | 否 |
1919
| Stop | `stop_summary.py` | 人类可读会话摘要 | 仅内部错误时 | 0 | 否 |
2020

21-
实现定位(行号随 v0.6.2 锁定,下一次重构时核对):
22-
- SessionStart 摘要:`hooks/env_check.py:34`。
23-
- UserPromptSubmit JSON:`hooks/user_prompt_validator.py:104-109` 与 `:117-125`。
24-
- PreToolUse stderr:`hooks/pre_tool_git_guard.py:159`。
25-
- PostToolUse JSON:`hooks/post_tool_lint.py:226-232`(通过)、`:253-259`(自动修复后通过)、`:275-281`(失败 additionalContext)。
26-
- Stop 摘要:`hooks/stop_summary.py:46`。
21+
实现定位(**符号而非行号**——行号随每次重构腐烂,符号不腐;v0.8.0 起弃用行号指针):
22+
- SessionStart 摘要:`hooks/env_check.py::main`(含双副本告警段)。
23+
- UserPromptSubmit JSON:`hooks/user_prompt_validator.py::main`(非 git 目录跳过说明 `_non_git_note`)与 `::is_trigger`。
24+
- PreToolUse stderr:`hooks/pre_tool_git_guard.py::main`;拦截判定 `::is_guarded`(直接 + `_command_indirect` 一层间接)。
25+
- PostToolUse JSON:`hooks/post_tool_lint.py::main`(通过/自动修复后通过/失败 additionalContext 三处 print)。
26+
- Stop 摘要:`hooks/stop_summary.py::main`(含绕过计数 `summarize` 与 skipGate 遗留告警)。
2727

2828
---
2929

@@ -57,7 +57,7 @@ if __name__ == "__main__":
5757
含义:
5858
- 钩子内部任何未捕获异常 → stderr 打印 traceback 摘要 → **exit 0**。
5959
- AI 与用户上下文永远收到「钩子无意见」的语义。
60-
- 同一约定适用于:`env_check.py:127-131`、`post_tool_lint.py:293-298`、`pre_tool_git_guard.py:166-170`、`user_prompt_validator.py:137-141`、`stop_summary.py:55-62`。
60+
- 同一约定适用于全部 5 个钩子的 `__main__` 尾部:`env_check.py`、`post_tool_lint.py`、`pre_tool_git_guard.py`、`user_prompt_validator.py`、`stop_summary.py`(符号级约束,见 §6)。
6161

6262
**禁止**把 fail-open 改为 exit 2——会破坏宿主工作流。
6363

@@ -66,13 +66,28 @@ if __name__ == "__main__":
6666
## 4. PreToolUse 硬拦截(exit 2)的语义边界
6767

6868
`pre_tool_git_guard.py` 是**唯一**会 exit 2 的 hook——且仅当:
69-
1. 入参命令包含 `git commit` 或 `git push`(`is_guarded()` 命中);并且
70-
2. 仓库级 `git config codeguard.skipGate true` 未设置;并且
69+
1. 入参命令命中 `is_guarded()`:**直接**(分隔符切段后段首为 `git commit|push`;
70+
子串匹配会误伤 payload/echo 文本)**或一层解释器间接**(`bash|sh|python… <脚本>`
71+
的脚本文本、`-c` 内联代码按同规则扫描——`bash runner.sh` 式绕过曾连推 4 次漏网;
72+
拼接式 subprocess 不在静态扫描承诺内);并且
73+
2. 仓库级 `git config codeguard.skipGate true` 未设置(命中豁免时记账一次,
74+
`gate_lib.record_skip_event`,Stop 汇总可见);并且
7175
3. 实际跑 linter 后存在非 skipped 的 failures。
7276

73-
出口内容(`pre_tool_git_guard.py:158-163`):stderr 报告块**首行必须是 `codeguard ❌ 提交门禁未通过:` 综述**,其后跟具体问题列表;这是 `tests/run_all.py:142-144` 守护的契约。
77+
出口内容(`gate_lib.gate_directive` 生成):
78+
- **首行必须是 `codeguard ❌ 提交门禁未通过:` 综述**(tests/run_all 守护);
79+
- 报告块**末段必须包含整调用声明**——"整个工具调用没有执行(含非 git 前序步骤),
80+
请把修复与提交拆成两次独立调用"。PreToolUse 的 exit 2 拒绝的是**整个 Bash 工具
81+
调用**,此前未声明这一点,AI 反复把写文件与提交塞进同一调用并误判"编辑被吞"。
7482

75-
**禁止**在 lint skipped(工具未装/项目未接入)时 exit 2——这是「无法验证」而非「验证失败」。
83+
**禁止**在 lint skipped(工具未装/项目未接入/本次改动未涉及/exit 2 工具链异常)
84+
时 exit 2——这是「无法验证」而非「验证失败」。
85+
86+
**一致性约束**:`UserPromptSubmit` 软门禁与本硬门禁共用同一条 skipGate 豁免,
87+
且**都不得在非 git 目录回退成"扫描 cwd"**——UPS 对非 git 目录输出一行
88+
`_non_git_note` 说明并 exit 0(工作区根被回退扫描 = 上百无关仓的存量 lint
89+
变成永久红,实测)。双副本事件去重键:PreToolUse 用 `tool_use_id`、UPS 用
90+
`session_id + 文本`;payload 不带这些字段(测试协议)时不去重。
7691

7792
---
7893

‎hooks/env_check.py‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
2. 检查是否已有 linter 配置(.pre-commit-config.yaml、checkstyle.xml、.clippy.toml、eslint.config.js、ruff.toml)
77
3. 输出一段 AGENTS.md 风格的提示,让 AI 知道自己在一个被 codeguard 管理的项目里
88
"""
9+
import contextlib
910
import os
1011
import subprocess
1112
import sys
@@ -31,14 +32,12 @@ def notify(title: str, message: str) -> None:
3132
return
3233
safe_t = title.replace('"', "'")
3334
safe_m = message.replace('"', "'")[:200]
34-
try:
35+
with contextlib.suppress(OSError):
3536
subprocess.Popen(
3637
["osascript", "-e",
3738
f'display notification "{safe_m}" with title "{safe_t}"'],
3839
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
3940
)
40-
except OSError:
41-
pass
4241

4342

4443
def detect_linter_config(project_root: Path) -> dict:
@@ -114,6 +113,24 @@ def main() -> int:
114113
else:
115114
lines.append("- 未配置任何 linter(建议运行 /init 接入 codeguard)")
116115

116+
# 双副本检测:partme-ai 与 full-stack-plugins 同时启用时每个钩子事件
117+
# 跑两遍(历史实测双份触发);提示用户停用其一,而不是默默重复。
118+
try:
119+
import json as _json
120+
_reg = _json.loads((Path.home() / ".zcode" / "cli" / "config.json").read_text(encoding="utf-8"))
121+
_orgs = sorted({
122+
key.split("@", 1)[1]
123+
for key in (_reg.get("plugins", {}) or {}).get("enabledPlugins", {})
124+
if key.startswith("codeguard@")
125+
})
126+
except (OSError, ValueError, AttributeError):
127+
_orgs = []
128+
if len(_orgs) > 1:
129+
lines.append(
130+
f"- ⚠️ 检测到 codeguard **双副本同时启用**({', '.join(_orgs)}):"
131+
"每个钩子事件会执行两遍,报告与统计可能翻倍或分裂——建议只保留一个来源"
132+
)
133+
117134
lines.append("- AI 写完代码会被 PostToolUse 钩子自动 lint,告警会出现在这里,按告警里的「怎么修」处理")
118135
lines.append("- 用户要求「提交/push」时,UserPromptSubmit 钩子会再次确认所有 linter 通过,未通过会拦截提交")
119136
lines.append("")

0 commit comments

Comments
 (0)