Skip to content

Commit 0e699b8

Browse files
authored
Merge pull request #46 from full-stack-plugins/release/v0.14.0
refactor: CodeGuard v0.14.0 五类 Hook 应用层收敛
2 parents fb95bba + 8e2157d commit 0e699b8

30 files changed

Lines changed: 1300 additions & 847 deletions

‎.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.13.0"
12+
"ref": "v0.14.0"
1313
},
1414
"policy": {
1515
"installation": "AVAILABLE",
1616
"authentication": "ON_USE"
1717
},
1818
"category": "Developer Tools",
19-
"version": "0.13.0",
19+
"version": "0.14.0",
2020
"description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
21-
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.13.0/assets/official-logo.png",
21+
"icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.14.0/assets/official-logo.png",
2222
"interface": {
2323
"displayName": "代码规范守卫",
2424
"shortDescription": "Trustworthy code checks and Java impact analysis",
25-
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.13.0/assets/official-logo.png"
25+
"logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.14.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.13.0+codex.20260923",
3+
"version": "0.14.0+codex.20260923",
44
"description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
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.13.0",
8+
"version": "0.14.0",
99
"description": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",
1010
"description_i18n": {
1111
"en": "Evidence-backed code checks and Git content gates for AI assistants, with Maven/Gradle module impact analysis. Save hooks provide feedback; unverified checks are explicit.",

‎docs/current-architecture.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
```mermaid
88
flowchart TD
99
Host[Codex / ZCode / Kimi] --> Entry[CLI / MCP / 五类 Hook]
10+
Entry --> HookApp[startup / session / prompt / git_guard / save applications]
1011
Entry --> Check[check_application / language_check]
12+
HookApp --> Gate
13+
HookApp --> Plan
1114
Entry --> Gate[gate / gate_checks]
1215
Entry --> Scan[cve / dockerfile]
1316
Check --> Plan[planning / Java 模块分析]
@@ -30,8 +33,9 @@ flowchart TD
3033
| 位置 | 责任 | 扩展边界 |
3134
|---|---|---|
3235
| `bin/codeguard`、`scripts/run_check.py`、`scripts/fix.py` | CLI 参数、输出与 MCP stdio 注册 | 不复制扫描器或判定逻辑 |
33-
| `hooks/` | 宿主 JSON、会话事件、通知与退出协议 | PreToolUse/PostToolUse 仍有待迁移的编排,不能称为纯适配器 |
36+
| `hooks/` | 宿主 JSON、会话事件、通知与退出协议 | 五类 Hook 将检查编排委托应用服务;入口保留旧导入兼容面及 fail-open 协议 |
3437
| `scripts/codeguard/check_application.py`、`language_check.py` | 语言选择、检查与修复请求、MCP 工具分发 | 不导入 MCP SDK;旧 `run_per_language.py` 仅再导出 |
38+
| `startup_application.py`、`session_application.py`、`prompt_application.py`、`git_guard_application.py`、`save_application.py` | 五类 Hook 的盘点、状态消费、软提示、Git 守卫及保存反馈编排 | 返回结构化结果,不打印或调用宿主通知;Hook 控制输出、通知与 fail-open。软/硬提示与保存统计在成功输出后确认;Stop 的状态消费仍先于 stdout,输出故障时可能丢失总结 |
3539
| `gate.py`、`gate_checks.py`、`repository_policy.py`、`baseline.py` | Git 门禁、单语言结果、安全规则及有证据的存量比较 | 只有准确基线失败且每条诊断及次数覆盖当前结果才可豁免 |
3640
| `java_build.py`、`java_impact.py`、`java_planning.py` 等 | 构建读取、纯影响闭包、命令选择与环境观察 | 默认保留跳过测试的行为;复杂构建保守扩大检查范围 |
3741
| `cve_reports.py`、`cve_policy.py`、`cve_scanners.py`、`cve.py` | 漏洞报告、阈值、进程适配与复扫编排 | 无有效结构化报告就没有安全通过结论 |
@@ -62,4 +66,4 @@ python3 scripts/vendor/skill_vendor.py check
6266
openspec validate refactor-codeguard-architecture --strict
6367
```
6468

65-
真实 Maven/Gradle 大型项目、联网漏洞数据库、57 种工具链、Windows 与三宿主已安装运行均需要独立证据。临时 Git 快照不是执行沙箱;构建和扫描命令以当前用户权限运行。当前重构的任务进度和本地证据见 `openspec/changes/refactor-codeguard-architecture/verification.md`,发布记录需待版本、CI、tag、市场和宿主分别核对。
69+
真实 Maven/Gradle 大型项目、联网漏洞数据库、57 种工具链、Windows 与三宿主已安装运行均需要独立证据。临时 Git 快照不是执行沙箱;构建和扫描命令以当前用户权限运行。当前重构的任务进度和本地证据见 `openspec/changes/refactor-codeguard-architecture/verification.md`。v0.13.0 的源码、CI、tag、Release 和市场已核对;其后工作树变更仍需独立发布,宿主安装运行尚未验收。

‎hooks/env_check.py‎

Lines changed: 51 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
#!/usr/bin/env python3
2-
"""SessionStart 钩子:检测项目语言,把规则注入 AI 上下文。
2+
"""SessionStart 宿主适配器:读取事件与本机插件配置,输出项目记忆。"""
3+
from __future__ import annotations
34

4-
行为:
5-
1. detect_lang 找到项目语言
6-
2. 检查是否已有 linter 配置(.pre-commit-config.yaml、checkstyle.xml、.clippy.toml、eslint.config.js、ruff.toml)
7-
3. 输出一段 AGENTS.md 风格的提示,让 AI 知道自己在一个被 codeguard 管理的项目里
8-
"""
95
import contextlib
106
import json
117
import os
128
import subprocess
139
import sys
1410
from pathlib import Path
1511

16-
PLUGIN_ROOT = Path(__file__).resolve().parents[1] # hooks/ 的上级 = 插件根(不依赖宿主环境变量)
12+
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
1713
sys.path.insert(0, str(PLUGIN_ROOT / "scripts"))
1814

19-
from detect_lang import ( # ensure_user_path/load_user_config 实际定义:scripts/paths.py、scripts/user_config.py
15+
from codeguard import startup_application
16+
from codeguard.reporting import plugin_version
17+
from detect_lang import ( # 兼容原有 Python 导入接口
2018
LANG_COMMANDS,
2119
REGISTRY,
2220
detect_languages,
@@ -25,79 +23,49 @@
2523
load_user_config,
2624
)
2725

26+
__all__ = [
27+
"LANG_COMMANDS",
28+
"REGISTRY",
29+
"detect_languages",
30+
"detect_linter_config",
31+
"ensure_user_path",
32+
"find_project_root",
33+
"load_user_config",
34+
"main",
35+
"notify",
36+
"version_backlog_note",
37+
]
38+
39+
detect_linter_config = startup_application.detect_linter_config
40+
version_backlog_note = startup_application.version_backlog_note
41+
2842

2943
def notify(title: str, message: str) -> None:
30-
"""macOS 系统通知(非 darwin 静默)"""
44+
"""macOS 系统通知;其他宿主静默。"""
3145
if sys.platform != "darwin":
3246
return
33-
safe_t = title.replace('"', "'")
34-
safe_m = message.replace('"', "'")[:200]
47+
safe_title = title.replace('"', "'")
48+
safe_message = message.replace('"', "'")[:200]
3549
with contextlib.suppress(OSError):
3650
subprocess.Popen(
3751
["osascript", "-e",
38-
f'display notification "{safe_m}" with title "{safe_t}"'],
52+
f'display notification "{safe_message}" with title "{safe_title}"'],
3953
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
4054
)
4155

4256

43-
def detect_linter_config(project_root: Path) -> dict:
44-
"""返回 {language: [存在的 linter 配置文件]},由 languages.json 驱动。
45-
46-
每条 languages.json 条目的 `linter_config_files` 字段声明该语言的 linter
47-
配置文件路径(glob 模式允许,与 requiresConfig 同语义)。新增语言只需
48-
在注册表里填字段,env_check 自动盘点——不再有硬编码常量的同步陷阱。
49-
"""
50-
found = {}
51-
for lang_id, lang_def in REGISTRY.items():
52-
files = lang_def.get("linter_config_files") or []
53-
if not files:
54-
continue
55-
present = [f for f in files if (project_root / f).exists()]
56-
if present:
57-
found[lang_id] = present
58-
return found
59-
60-
61-
def _semver_tuple(v: str) -> tuple[int, ...]:
62-
import re as _re
63-
m = _re.match(r"(\d+(?:\.\d+)*)", v.strip())
64-
if not m:
65-
return (0,)
66-
return tuple(int(x) for x in m.group(1).split("."))
67-
68-
69-
def version_backlog_note(current: str, cache_root: Path) -> str | None:
70-
"""纯函数:本地插件 cache 存在比 current 更新的版本 → 提示文案;否则 None。
71-
72-
检测面 = `~/.zcode/cli/plugins/cache/<vendor>/codeguard/<version>` 的**版本目录
73-
集合**(离线可得的唯一权威)。抓两类漂移:同 vendor 升级后旧目录未清、跨
74-
vendor 版本参差导致实际生效副本落后于本机已有副本。抓不到"本机最新 vs
75-
远端仓库最新"(需网络,钩子不联网)——文案里明示提醒核对远端。
76-
"""
77-
newer: list[str] = []
78-
installed: list[str] = []
79-
if not cache_root.is_dir():
80-
return None
57+
def _enabled_plugin_orgs() -> tuple[str, ...]:
58+
"""ZCode 宿主注册表只在适配器读取,其他宿主缺文件时无副作用。"""
8159
try:
82-
for vendor_dir in sorted(cache_root.glob("*/codeguard/*")):
83-
if not vendor_dir.is_dir():
84-
continue
85-
v = vendor_dir.name
86-
installed.append(v)
87-
if _semver_tuple(v) > _semver_tuple(current):
88-
newer.append(v)
89-
except OSError:
90-
return None
91-
if not newer:
92-
return None
93-
newest = max(newer, key=_semver_tuple)
94-
return (
95-
f"- ⚠️ **版本积压**:当前生效 codeguard `v{current}`,本机 cache 已有更新版本 "
96-
f"`v{newest}`(共 {len(installed)} 份: {', '.join(sorted(installed))})——"
97-
"钩子行为以**生效副本**为准,修复可能已存在却没跑到;请更新插件并清理旧 "
98-
"cache(`~/.zcode/cli/plugins/cache/*/codeguard/`),同时核对远端是否还有"
99-
"更新版本(本检测离线,看不到仓库最新版)。"
100-
)
60+
config = json.loads((Path.home() / ".zcode" / "cli" / "config.json").read_text(encoding="utf-8"))
61+
orgs = {
62+
key.split("@", 1)[1]
63+
for key in (config.get("plugins", {}) or {}).get("enabledPlugins", {})
64+
if key.startswith("codeguard@")
65+
}
66+
return tuple(sorted(orgs))
67+
except (OSError, ValueError, AttributeError):
68+
return ()
10169

10270

10371
def main(payload: dict | None = None) -> int:
@@ -108,100 +76,19 @@ def main(payload: dict | None = None) -> int:
10876

10977

11078
def _main(payload: dict | None = None) -> int:
111-
ensure_user_path(from_login_shell=True) # GUI 宿主 PATH 不含用户级工具目录,先补齐再盘点
112-
project_root = find_project_root(os.getcwd())
113-
if project_root is None:
114-
project_root = Path(os.getcwd())
115-
116-
languages = detect_languages(project_root)
117-
if not languages:
118-
return 0 # 未识别到代码项目,安静退出
119-
120-
linter_cfg = detect_linter_config(project_root)
121-
cfg = load_user_config()
122-
enabled = cfg.get("enabled_languages", [])
123-
if enabled and enabled != ["auto"]:
124-
languages = [lang for lang in languages if lang in enabled]
125-
126-
# linter 盘点:直接复用门禁的探活(比裸 which 准——能发现 npx 包未装、运行时损坏)
127-
from codeguard.toolchain import ToolchainProbe
128-
probe = ToolchainProbe(project_root)
129-
ready, missing = [], []
130-
for lang in languages:
131-
cmd_def = LANG_COMMANDS.get(lang)
132-
if not cmd_def:
133-
continue
134-
ok, reason = probe.probe(cmd_def)
135-
if ok:
136-
ready.append(lang)
137-
else:
138-
hint = cmd_def.get("install_hint") or "见 docs/LANGUAGES.md"
139-
missing.append((lang, reason, hint))
140-
141-
# 输出注入到 AI 上下文的内容(追加模式)
142-
lines = []
143-
lines.append("# codeguard 项目记忆")
144-
lines.append("")
145-
lines.append(f"- 项目根: `{project_root}`")
146-
lines.append(f"- 检测到语言: {', '.join(languages)}")
147-
148-
if missing:
149-
lines.append("")
150-
lines.append("**⚠️ 以下语言的检查未生效,这些语言的文件【不会】被检查(提交门禁也会跳过它们):**")
151-
for lang, reason, hint in missing:
152-
lines.append(f" - {lang}({reason})→ 修复: `{hint}`")
153-
lines.append(" 请主动提示用户安装;安装后新写的文件才会被检查。")
154-
notify(
155-
"codeguard 部分检查未生效",
156-
f"{len(missing)} 个语言未生效: " + ", ".join(lang for lang, _, _ in missing) +
157-
";详情见对话",
158-
)
159-
if ready:
160-
lines.append(f"- 检查已生效的语言: {', '.join(ready)}")
161-
162-
if linter_cfg:
163-
lines.append(f"- 已有的 linter 配置: {linter_cfg}")
164-
else:
165-
lines.append("- 未配置任何 linter(建议运行 /init 接入 codeguard)")
166-
167-
# 双副本检测:partme-ai 与 full-stack-plugins 同时启用时每个钩子事件
168-
# 跑两遍(历史实测双份触发);提示用户停用其一,而不是默默重复。
169-
try:
170-
import json as _json
171-
_reg = _json.loads((Path.home() / ".zcode" / "cli" / "config.json").read_text(encoding="utf-8"))
172-
_orgs = sorted({
173-
key.split("@", 1)[1]
174-
for key in (_reg.get("plugins", {}) or {}).get("enabledPlugins", {})
175-
if key.startswith("codeguard@")
176-
})
177-
except (OSError, ValueError, AttributeError):
178-
_orgs = []
179-
if len(_orgs) > 1:
180-
lines.append(
181-
f"- ⚠️ 检测到 codeguard **双副本同时启用**({', '.join(_orgs)}):"
182-
"每个钩子事件会执行两遍,报告与统计可能翻倍或分裂——建议只保留一个来源"
183-
)
184-
185-
# 版本积压:本机 cache 里有比当前生效副本更新的版本(实测 8 份副本停留在
186-
# 0.5.x 而源码已 0.8.x——"发布了但没跑到"的盲区,SessionStart 即可见)
187-
try:
188-
_mf = json.loads((PLUGIN_ROOT / ".zcode-plugin" / "plugin.json").read_text(encoding="utf-8"))
189-
_current = str(_mf.get("version") or "")
190-
except (OSError, ValueError, AttributeError):
191-
_current = ""
192-
if _current:
193-
_note = version_backlog_note(
194-
_current, Path.home() / ".zcode" / "cli" / "plugins" / "cache"
195-
)
196-
if _note:
197-
lines.append(_note)
198-
199-
lines.append("- AI 写完代码会被 PostToolUse 钩子自动 lint,告警会出现在这里,按告警里的「怎么修」处理")
200-
lines.append("- 用户要求「提交/push」时,UserPromptSubmit 钩子会再次确认所有 linter 通过,未通过会拦截提交")
201-
lines.append("")
202-
lines.append("**重要**:写代码前先阅读对应语言的规范(见 skills/codeguard-{language}/SKILL.md)")
203-
204-
print("\n".join(lines))
79+
ensure_user_path(from_login_shell=True)
80+
project_root = find_project_root(os.getcwd()) or Path(os.getcwd())
81+
current = plugin_version()
82+
report = startup_application.build_startup_report(
83+
project_root,
84+
plugin_version="" if current == "unknown" else current,
85+
cache_root=Path.home() / ".zcode" / "cli" / "plugins" / "cache",
86+
enabled_plugin_orgs=_enabled_plugin_orgs(),
87+
)
88+
if report.notification:
89+
notify(*report.notification)
90+
if report.text:
91+
print(report.text)
20592
return 0
20693

20794

@@ -218,6 +105,6 @@ def _main(payload: dict | None = None) -> int:
218105
sys.exit(main(payload))
219106
except SystemExit:
220107
raise
221-
except Exception as exc: # noqa: BLE001 — 内部错误 fail-open:traceback 绝不进 AI 上下文/阻断工作流
108+
except Exception as exc: # noqa: BLE001 — 内部错误 fail-open,不阻断宿主工作流
222109
print(f"[codeguard] 内部错误已忽略(fail-open): {exc!r}", file=sys.stderr)
223110
sys.exit(0)

0 commit comments

Comments
 (0)