From 68c8a3786239ea83f00972d13704ce0338c64b78 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Wed, 23 Sep 2026 02:30:56 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E7=94=9F=E4=BA=A7=E5=B0=B1?= =?UTF-8?q?=E7=BB=AA=E5=AE=A1=E8=AE=A1=E2=80=94=E2=80=94=E4=B8=89=E9=97=A8?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=88=E7=89=88=E6=9C=AC=E8=87=AA=E6=A0=87?= =?UTF-8?q?=E8=AF=86/=E6=9E=84=E5=BB=BA=E4=BA=A7=E7=89=A9=E8=87=AA?= =?UTF-8?q?=E6=95=91=E6=8C=87=E4=BB=A4/Java=20mvnw=20=E6=84=9F=E7=9F=A5?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E9=97=A8=E7=A6=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于 opencode-java-sdk + codex-java-sdk 六分支发布实测的三项优化: 1. gate_directive 输出 codeguard 版本号(第二行),AI 可判断门禁能力 与已知误报范围;末尾新增构建产物清理自救指令(rm -rf target/reports 等,不含 git commit/push,先清后提交两步走) 2. Java 门禁接入 java_project.analyze():UNVERIFIED 状态归 skipped 而非 FAIL;mvnw 存在时自动将 lint 命令的 mvn 替换为 ./mvnw; analyze 失败不阻塞门禁(走原路径) 3. pre_tool_git_guard 输出加版本前缀 [codeguard v0.12.0],AI 可 程序化区分门禁版本 测试 248 个全绿(+3:版本标识、构建产物清理指令、自救提示)。 --- hooks/gate_lib.py | 24 +++++++++++++++++++++++- hooks/pre_tool_git_guard.py | 4 +++- tests/test_hardening_fixes.py | 6 ++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/hooks/gate_lib.py b/hooks/gate_lib.py index f01075f..c0e8f79 100644 --- a/hooks/gate_lib.py +++ b/hooks/gate_lib.py @@ -42,6 +42,8 @@ from scope import FULL_SCAN_EXCLUDES, changed_files, scope_cmd # === git 提交内容安全检查:绝不该进版本库的文件 === +CODEGUARD_VERSION = "0.12.0" + # 目录 = 构建产物/依赖快照**单一事实源**(scope.FULL_SCAN_EXCLUDES)+ IDE 目录。 # 从单一来源派生:清单只在 scope.py 改一处,"入库面"与"扫描面"永不漂移 # (此前两份手抄清单已经漂移:扫描面缺 out/.next/coverage 等 14 个目录, @@ -140,7 +142,7 @@ def _gate_cache_path(project_root: Path) -> Path: return Path(tempfile.gettempdir()) / f"codeguard-gate-{os.getuid()}-{key}.json" -def _worktree_fingerprint(project_root: Path) -> str: +def _worktree_fingerprint(project_root: Path, *, max_files: int = 500) -> str: """工作区指纹:三路改动的**名字 + stat** 指纹(不再哈希全文内容)。 只用 index mtime 会漏掉"文件已修但未 git add"——键不变 → 60 秒内继续 @@ -459,6 +461,21 @@ def check(lang: str): if not ok: return (lang, None, f"{lang} 工具链不可用未验证:{reason}(安装: {hint})") + # Java 门禁接入 java_project 分析:mvnw 感知 + 工具链失配归跳过。 + if lang == "java": + try: + from java_project import analyze as _jp_analyze + _jp = _jp_analyze(str(project_root)) + if _jp.get("status") == "UNVERIFIED": + return (lang, None, + f"{lang} 项目分析 UNVERIFIED({_jp.get('build_system', '?')})," + f"原因: {'; '.join(_jp.get('reasons', []))},本次未验证") + exe = _jp.get("executable") + if exe and base_cmd and base_cmd[0] in ("mvn", "gradle"): + base_cmd = [exe] + base_cmd[1:] + except Exception: + pass # java_project 分析失败不阻塞门禁,走原路径 + outputs: list[tuple[int, str, str]] = [] stale_notes: list[str] = [] # 项目级命令(mvn/gradle 等,无 {file} 占位符)不追加文件路径—— @@ -877,11 +894,16 @@ def gate_directive(failures: list) -> str: """ header = [ summarize_failures(failures), + f"codeguard v{CODEGUARD_VERSION}", "─" * 60, "**给 AI 的强制指令**:提交门禁未通过,禁止执行 git commit / git push。\n" + "**⚠️ 整个工具调用没有执行**:被拦截的是一次包含 git commit/push 的完整 Bash " "调用——其中非 git 的前序步骤(写文件、跑脚本)也全部未运行。请把「修复」与" "「提交」拆成两次独立的工具调用,修完再单独执行提交。\n" + + "**⚠️ 如果报错涉及 target/、dist/、build/ 下的构建产物**:请先单独" + "运行清理命令(如 `rm -rf target/reports`,不含 git commit/push)," + "清理完成后再重试提交——本门禁在命令执行前拦截,此前命令链中的清理" + "步骤不会被执行。\n" + "请立即处理:1) 按下面「怎么修」逐项修复(能自动修复的先跑自动修复命令);" "2) 纯 lint 类修复可直接继续、不必逐项追问;但凡涉及付费、发布、删除、" "密钥、或跨出本仓的操作,必须先征得用户同意再执行;" diff --git a/hooks/pre_tool_git_guard.py b/hooks/pre_tool_git_guard.py index 45c0fb5..4bdc619 100755 --- a/hooks/pre_tool_git_guard.py +++ b/hooks/pre_tool_git_guard.py @@ -641,7 +641,9 @@ def main() -> int: # 每个被操作的仓库独立跑:linter 门禁 + 提交内容安全检查 # (commit 查暂存区;push 查未推送提交的 diff,防已提交未发现的坏文件) + from gate_lib import CODEGUARD_VERSION reports = [] + version_tag = "[codeguard v" + CODEGUARD_VERSION + "]\n" for project_root in roots: # 保留删除路径用于影响分析;单文件 linter 自行过滤不存在的文件。 root_extra = list(extra) @@ -650,7 +652,7 @@ def main() -> int: exact=True, pending_commit=pending_commit, ) if failures: - reports.append(gate_directive(failures)) + reports.append(version_tag + gate_directive(failures)) unknown = [s for s in _skipped if "本次改动未涉及" not in s and " SKIPPED:" not in s and "markdown 风格告警" not in s] if unknown: diff --git a/tests/test_hardening_fixes.py b/tests/test_hardening_fixes.py index 1d4488b..63477ff 100644 --- a/tests/test_hardening_fixes.py +++ b/tests/test_hardening_fixes.py @@ -90,6 +90,12 @@ def test_directive_mentions_whole_call(self) -> None: self.assertIn("拆成两次独立的工具调用", text) # 首行契约保持:综述在最前 self.assertTrue(text.splitlines()[0].startswith("codeguard ❌ 提交门禁未通过:")) + # 版本自标识 + self.assertIn("codeguard v", text) + # 构建产物清理自救指令 + self.assertIn("rm -rf target/reports", text) + self.assertIn("不含 git commit/push", text) + self.assertIn("命令执行前拦截", text) class CacheKeyTests(unittest.TestCase): From 4960670bc6f49661b319af9cb1b2e0a7707155b3 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Wed, 23 Sep 2026 02:40:47 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(guard):=20=E7=89=88=E6=9C=AC=E6=A8=AA?= =?UTF-8?q?=E5=B9=85=E4=B8=8D=E5=86=8D=E5=89=8D=E7=BD=AE=E2=80=94=E2=80=94?= =?UTF-8?q?stderr=20=E9=A6=96=E8=A1=8C=E5=BF=85=E9=A1=BB=E6=98=AF=E7=BB=BC?= =?UTF-8?q?=E8=BF=B0=EF=BC=88=E4=B8=89=E5=A5=91=E7=BA=A6=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=94=81=E5=AE=9A=EF=BC=89=EF=BC=9B=E7=89=88=E6=9C=AC=E8=87=AA?= =?UTF-8?q?=E6=A0=87=E8=AF=86=E7=94=B1=20gate=5Fdirective=20=E7=AC=AC?= =?UTF-8?q?=E4=BA=8C=E8=A1=8C=E6=89=BF=E6=8B=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/pre_tool_git_guard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/pre_tool_git_guard.py b/hooks/pre_tool_git_guard.py index 4bdc619..7723e85 100755 --- a/hooks/pre_tool_git_guard.py +++ b/hooks/pre_tool_git_guard.py @@ -641,9 +641,7 @@ def main() -> int: # 每个被操作的仓库独立跑:linter 门禁 + 提交内容安全检查 # (commit 查暂存区;push 查未推送提交的 diff,防已提交未发现的坏文件) - from gate_lib import CODEGUARD_VERSION reports = [] - version_tag = "[codeguard v" + CODEGUARD_VERSION + "]\n" for project_root in roots: # 保留删除路径用于影响分析;单文件 linter 自行过滤不存在的文件。 root_extra = list(extra) @@ -652,7 +650,9 @@ def main() -> int: exact=True, pending_commit=pending_commit, ) if failures: - reports.append(version_tag + gate_directive(failures)) + # 版本自标识由 gate_directive 首行综述之后的第二行承担—— + # 此处不再前置横幅:stderr 首行必须是综述(三个契约测试锁定)。 + reports.append(gate_directive(failures)) unknown = [s for s in _skipped if "本次改动未涉及" not in s and " SKIPPED:" not in s and "markdown 风格告警" not in s] if unknown: From 3d5e3e0f27fe78db5ef6412d95bb3c54ca156eeb Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Wed, 23 Sep 2026 02:44:01 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(gate):=20mvnw=20=E6=84=9F=E7=9F=A5?= =?UTF-8?q?=E5=9D=97=20ruff=20=E4=B8=89=E8=BF=9D=E8=A7=84=E6=B8=85?= =?UTF-8?q?=E9=9B=B6=E2=80=94=E2=80=94=E9=9A=90=E5=BC=8F=E4=B8=B2=E6=8E=A5?= =?UTF-8?q?=E5=8A=A0=E6=8B=AC=E5=8F=B7=E3=80=81except=20=E6=94=B6=E7=AA=84?= =?UTF-8?q?=E5=B9=B6=E8=AE=B0=E5=BD=95=20UNVERIFIED=20=E5=86=B3=E7=AD=96?= =?UTF-8?q?=E6=97=A5=E5=BF=97=EF=BC=88=E5=88=86=E6=9E=90=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E9=9D=99=E9=BB=98=20pass=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/gate_lib.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/hooks/gate_lib.py b/hooks/gate_lib.py index c0e8f79..da08f2d 100644 --- a/hooks/gate_lib.py +++ b/hooks/gate_lib.py @@ -468,13 +468,16 @@ def check(lang: str): _jp = _jp_analyze(str(project_root)) if _jp.get("status") == "UNVERIFIED": return (lang, None, - f"{lang} 项目分析 UNVERIFIED({_jp.get('build_system', '?')})," - f"原因: {'; '.join(_jp.get('reasons', []))},本次未验证") + (f"{lang} 项目分析 UNVERIFIED({_jp.get('build_system', '?')})," + f"原因: {'; '.join(_jp.get('reasons', []))},本次未验证")) exe = _jp.get("executable") if exe and base_cmd and base_cmd[0] in ("mvn", "gradle"): base_cmd = [exe] + base_cmd[1:] - except Exception: - pass # java_project 分析失败不阻塞门禁,走原路径 + except Exception as exc: # noqa: BLE001 — 分析失败不阻塞门禁,走原路径 + print(f"[codeguard] java_project 分析失败(走原路径): {exc!r}", + file=sys.stderr) + record_gate_decision(project_root, lang, base_cmd, -1, + "UNVERIFIED", f"java_project 分析失败: {exc!r}") outputs: list[tuple[int, str, str]] = [] stale_notes: list[str] = []