Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
"source": {
"source": "url",
"url": "https://github.com/partme-ai/partme-codeguard-plugin.git",
"ref": "v0.7.1"
"ref": "v0.8.0"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_USE"
},
"category": "Developer Tools",
"version": "0.7.1",
"version": "0.8.0",
"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.",
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.7.1/assets/official-logo.png",
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.0/assets/official-logo.png",
"interface": {
"displayName": "代码规范守卫",
"shortDescription": "Make AI-written code pass lint on first try",
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.7.1/assets/official-logo.png"
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.8.0/assets/official-logo.png"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeguard",
"version": "0.7.1+codex.20260922",
"version": "0.8.0+codex.20260922",
"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.",
"author": {
"name": "Full Stack Skills / PartMe.AI",
Expand Down
2 changes: 1 addition & 1 deletion .zcode-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"en": "CodeGuard",
"zh-CN": "代码规范检查"
},
"version": "0.7.1",
"version": "0.8.0",
"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.",
"description_i18n": {
"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.",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ AI code that passes lint on first try
|---|---|
| Plugin ID | `partme-codeguard-plugin` |
| Hosts | ZCode, Claude Code, Codex CLI, Kimi Code |
| Current version | `0.7.1` |
| Current version | `0.8.0` |
| ZCode manifest | `.zcode-plugin/plugin.json` |
| Codex manifest | `.codex-plugin/plugin.json` |
| MCP server | Published: stdio server via the official SDK (`check_code_style` / `auto_fix` / `list_languages`); see Quick start |
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ AI 一次写出就过 lint 的代码
|---|---|
| 插件 ID | `partme-codeguard-plugin` |
| 宿主 | ZCode、Claude Code、Codex CLI、Kimi Code |
| 当前版本 | `0.7.1` |
| 当前版本 | `0.8.0` |
| ZCode manifest | `.zcode-plugin/plugin.json` |
| Codex manifest | `.codex-plugin/plugin.json` |
| MCP 服务 | 已发布:官方 SDK stdio 服务(`check_code_style` / `auto_fix` / `list_languages`);见快速开始 |
Expand Down
2 changes: 1 addition & 1 deletion docs/LANGUAGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
| PHP | `.php` | `php -l {file}` | `php-cs-fixer fix` | composer require --dev php-cs-fixer |
| Ruby | `.rb` | `rubocop` | `rubocop -A` | gem install rubocop |
| Scala | `.scala` `.sc` | `scalafmt --check .` | `scalafmt` | coursier install scalafmt |
| Shell | `.sh` `.bash` `.zsh` | `shellcheck {file}` | `shfmt -w .` | brew install shellcheck shfmt |
| Shell | `.sh` `.bash` `.zsh` | `shellcheck --severity=warning {file}` | `shfmt -w .` | brew install shellcheck shfmt |
| Dockerfile | (文件名匹配) | `hadolint {file}` | `hadolint` | brew install hadolint |
| YAML | `.yml` `.yaml` | `yamllint .` | `yamllint .` | pip install yamllint |
| Elixir | `.ex` `.exs` | `mix credo --strict` | `mix format` | 项目需配置 credo 依赖 |
Expand Down
37 changes: 26 additions & 11 deletions hooks/__protocol__.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
| PostToolUse `Write\|Edit\|MultiEdit` | `post_tool_lint.py` | JSON `{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"..."}, "systemMessage":"..."}` | 仅内部错误时 | 0 | 否 |
| Stop | `stop_summary.py` | 人类可读会话摘要 | 仅内部错误时 | 0 | 否 |

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

---

Expand Down Expand Up @@ -57,7 +57,7 @@ if __name__ == "__main__":
含义:
- 钩子内部任何未捕获异常 → stderr 打印 traceback 摘要 → **exit 0**。
- AI 与用户上下文永远收到「钩子无意见」的语义。
- 同一约定适用于:`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`。
- 同一约定适用于全部 5 个钩子的 `__main__` 尾部:`env_check.py`、`post_tool_lint.py`、`pre_tool_git_guard.py`、`user_prompt_validator.py`、`stop_summary.py`(符号级约束,见 §6)。

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

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

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

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

**禁止**在 lint skipped(工具未装/项目未接入)时 exit 2——这是「无法验证」而非「验证失败」。
**禁止**在 lint skipped(工具未装/项目未接入/本次改动未涉及/exit 2 工具链异常)
时 exit 2——这是「无法验证」而非「验证失败」。

**一致性约束**:`UserPromptSubmit` 软门禁与本硬门禁共用同一条 skipGate 豁免,
且**都不得在非 git 目录回退成"扫描 cwd"**——UPS 对非 git 目录输出一行
`_non_git_note` 说明并 exit 0(工作区根被回退扫描 = 上百无关仓的存量 lint
变成永久红,实测)。双副本事件去重键:PreToolUse 用 `tool_use_id`、UPS 用
`session_id + 文本`;payload 不带这些字段(测试协议)时不去重。

---

Expand Down
23 changes: 20 additions & 3 deletions hooks/env_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
2. 检查是否已有 linter 配置(.pre-commit-config.yaml、checkstyle.xml、.clippy.toml、eslint.config.js、ruff.toml)
3. 输出一段 AGENTS.md 风格的提示,让 AI 知道自己在一个被 codeguard 管理的项目里
"""
import contextlib
import os
import subprocess
import sys
Expand All @@ -31,14 +32,12 @@ def notify(title: str, message: str) -> None:
return
safe_t = title.replace('"', "'")
safe_m = message.replace('"', "'")[:200]
try:
with contextlib.suppress(OSError):
subprocess.Popen(
["osascript", "-e",
f'display notification "{safe_m}" with title "{safe_t}"'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
except OSError:
pass


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

# 双副本检测:partme-ai 与 full-stack-plugins 同时启用时每个钩子事件
# 跑两遍(历史实测双份触发);提示用户停用其一,而不是默默重复。
try:
import json as _json
_reg = _json.loads((Path.home() / ".zcode" / "cli" / "config.json").read_text(encoding="utf-8"))
_orgs = sorted({
key.split("@", 1)[1]
for key in (_reg.get("plugins", {}) or {}).get("enabledPlugins", {})
if key.startswith("codeguard@")
})
except (OSError, ValueError, AttributeError):
_orgs = []
if len(_orgs) > 1:
lines.append(
f"- ⚠️ 检测到 codeguard **双副本同时启用**({', '.join(_orgs)}):"
"每个钩子事件会执行两遍,报告与统计可能翻倍或分裂——建议只保留一个来源"
)

lines.append("- AI 写完代码会被 PostToolUse 钩子自动 lint,告警会出现在这里,按告警里的「怎么修」处理")
lines.append("- 用户要求「提交/push」时,UserPromptSubmit 钩子会再次确认所有 linter 通过,未通过会拦截提交")
lines.append("")
Expand Down
Loading
Loading