From 34e0ccafa705330650e3e9ff7b6ecb03c9d1dad1 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:43:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?release:=20v0.16.0=EF=BC=88Claude=20?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E9=9D=A2=E8=A1=A5=E4=BB=B6=20+=20go=20CVE=20?= =?UTF-8?q?=E7=94=9F=E6=80=81=20+=20pathsep=20+=20strict=5Fmode=20?= =?UTF-8?q?=E6=91=98=E9=99=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit codegraph 深查(2026-09-23)四个实测挂账项(OpenSpec 2026-09-23-claude-manifest-and-go-cve 已归档,两份 ADDED delta 并入主 specs): - .claude-plugin/marketplace.json 补齐 Claude 宿主安装面(canvas 仓 074a580 形态), 并纳入 bump-plugin.mjs 版本链(防下次发版漂移),版本一致性双向锁测试 - CVE 新增 go 生态:规范映射独立条目(别名 golang/gomod、标志 go.mod/go.sum), trivy 原生解析 gomod、报告格式复用 trivy 解析器、生态身份按映射报告为 go (输入/输出标识同源);缺严重度发现不预过滤(防假 PASS) - paths.py 5 处硬编码 ':' → os.pathsep(Windows PATH 正确性) - 摘除 strict_mode 死旋钮(config 解析 + README 双语句;零消费、与协议 §1 矛盾) - tests/_subsets.go 标签精确化(go.lock-only 目录走 universal 兜底——与 go 生态 自动选择语义自洽,原文易误读为 go 恒走 universal) 验证:unittest 612(+9 回归)、run_all 19/19、check_architecture OK、ruff 全绿、 openspec --all 通过。5.6 归档与死码清理留给在飞 refactor-codeguard-architecture。 --- .agents/plugins/marketplace.json | 8 +- .claude-plugin/marketplace.json | 14 +++ .codex-plugin/plugin.json | 2 +- .zcode-plugin/plugin.json | 2 +- README.md | 2 +- README.zh-CN.md | 2 +- kimi.plugin.json | 2 +- .../.openspec.yaml | 2 + .../design.md | 37 ++++++ .../proposal.md | 25 ++++ .../specs/cve-dependency-scan/spec.md | 25 ++++ .../specs/plugin-manifest-contracts/spec.md | 15 +++ .../tasks.md | 15 +++ openspec/specs/cve-dependency-scan/spec.md | 27 +++- .../specs/plugin-manifest-contracts/spec.md | 14 +++ scripts/bump-plugin.mjs | 7 +- scripts/codeguard/config.py | 5 +- scripts/codeguard/cve.py | 20 ++- scripts/codeguard/cve_reports.py | 5 +- scripts/codeguard/cve_scanners.py | 15 +++ scripts/paths.py | 10 +- tests/_subsets.py | 2 +- tests/test_gap_closure_20260923.py | 118 ++++++++++++++++++ 23 files changed, 352 insertions(+), 22 deletions(-) create mode 100644 .claude-plugin/marketplace.json create mode 100644 openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/.openspec.yaml create mode 100644 openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/design.md create mode 100644 openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/proposal.md create mode 100644 openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/cve-dependency-scan/spec.md create mode 100644 openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/plugin-manifest-contracts/spec.md create mode 100644 openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/tasks.md create mode 100644 tests/test_gap_closure_20260923.py diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index e298e43..20c2f96 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -9,20 +9,20 @@ "source": { "source": "url", "url": "https://github.com/full-stack-plugins/codeguard-plugin.git", - "ref": "v0.15.4" + "ref": "v0.16.0" }, "policy": { "installation": "AVAILABLE", "authentication": "ON_USE" }, "category": "Developer Tools", - "version": "0.15.4", + "version": "0.16.0", "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.", - "icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.4/assets/official-logo.png", + "icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.16.0/assets/official-logo.png", "interface": { "displayName": "代码规范守卫", "shortDescription": "Trustworthy code checks and Java impact analysis", - "logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.15.4/assets/official-logo.png" + "logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.16.0/assets/official-logo.png" } } ] diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..621caaf --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,14 @@ +{ + "name": "codeguard-plugin", + "owner": { + "name": "full-stack-plugins" + }, + "plugins": [ + { + "name": "codeguard", + "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.", + "version": "0.16.0", + "source": "./" + } + ] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index bdc265a..7362a81 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "codeguard", - "version": "0.15.4+codex.20260923", + "version": "0.16.0+codex.20260923", "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.", "author": { "name": "Full Stack Skills / PartMe.AI", diff --git a/.zcode-plugin/plugin.json b/.zcode-plugin/plugin.json index 5366423..edb8742 100644 --- a/.zcode-plugin/plugin.json +++ b/.zcode-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "代码规范守卫", "zh-CN": "代码规范检查" }, - "version": "0.15.4", + "version": "0.16.0", "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.", "description_i18n": { "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.", diff --git a/README.md b/README.md index 0787ddf..ac4adf7 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Output logs default to /out/.codeguard-last.log; CLI --quiet disables l ## Configuration and coverage -Root codeguard.json may set gate_scope to delta or repo and customize extension/exclusion detection. User settings retain enabled_languages, auto_fix_on_save and lint_timeout_seconds. strict_mode is reserved and does not make PostToolUse block. See the [hook protocol](hooks/__protocol__.md). +Root codeguard.json may set gate_scope to delta or repo and customize extension/exclusion detection. User settings retain enabled_languages, auto_fix_on_save and lint_timeout_seconds. See the [hook protocol](hooks/__protocol__.md). The registry contains **54 Stable adapters and 3 Planned entries**. “Stable” does not certify every toolchain or project. Markdown/YAML require project configuration; missing configuration is UNVERIFIED. Markdown findings are advisory. Generated and dependency directories are excluded from ordinary lint scope, not automatically accepted for commit. Python checks honor the project's own ruff configuration (ruff.toml / .ruff.toml / [tool.ruff]); when none exists, codeguard injects a default rule set pinned to the CI baseline (ruff==0.16.8) so verdicts do not drift with whichever ruff version a machine happens to have. Full command inventory: [languages](docs/LANGUAGES.md). diff --git a/README.zh-CN.md b/README.zh-CN.md index 16c6b2d..6693275 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -116,7 +116,7 @@ python3 scripts/run_check.py --mcp /path/to/project ## 配置与覆盖 -仓根 codeguard.json 的 gate_scope 可选 delta/repo,也可定制扩展名和排除规则。用户设置保留 enabled_languages、auto_fix_on_save、lint_timeout_seconds。strict_mode 是保留字段,不会令 PostToolUse 阻断,详见[钩子协议](hooks/__protocol__.md)。 +仓根 codeguard.json 的 gate_scope 可选 delta/repo,也可定制扩展名和排除规则。用户设置保留 enabled_languages、auto_fix_on_save、lint_timeout_seconds。详见[钩子协议](hooks/__protocol__.md)。 注册表含 **54 个 Stable 适配器和 3 个 Planned 项**。“Stable” 不证明全部工具链或项目已验证。Markdown/YAML 需要项目配置,缺配置为 UNVERIFIED;Markdown 违规只告警。生成物和依赖目录从普通 lint 范围排除,不等于允许入库。Python 检查优先使用项目自有 ruff 配置(ruff.toml / .ruff.toml / [tool.ruff]);项目无自有配置时注入钉扎在 CI 基线(ruff==0.16.8)的默认规则集,判定不随机器上 ruff 版本漂移。完整命令见[语言清单](docs/LANGUAGES.md)。 diff --git a/kimi.plugin.json b/kimi.plugin.json index 3f6150c..4414ec0 100644 --- a/kimi.plugin.json +++ b/kimi.plugin.json @@ -1,6 +1,6 @@ { "name": "codeguard", - "version": "0.15.4", + "version": "0.16.0", "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.", "author": { "name": "Full Stack Skills / PartMe.AI" diff --git a/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/.openspec.yaml b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/.openspec.yaml new file mode 100644 index 0000000..75289e4 --- /dev/null +++ b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-24 diff --git a/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/design.md b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/design.md new file mode 100644 index 0000000..b9462a1 --- /dev/null +++ b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/design.md @@ -0,0 +1,37 @@ +## Context + +在飞大重构(refactor-codeguard-architecture,5.5/5.6)已把执行内核/判定/Java/CVE/状态全部模块化到 `scripts/codeguard/`,四个缺口是它范围之外的挂账项:安装面缺件、生态覆盖、跨平台 PATH、死配置。四项互不相交且与在飞文件仅 config.py 一处相邻(摘两行死配置,属该模块的清理型修改)。 + +## Goals / Non-Goals + +**Goals:** + +- Claude 宿主安装面闭环(清单 + 版本链 + 回归锁)。 +- go 生态进入规范映射,自动选择与显式选择(含别名)同源,阈值语义与既有生态一致。 +- PATH 分隔符跨平台正确;死旋钮不留假象。 + +**Non-Goals:** + +- 不做 govulncheck 原生接入(无严重度输出,阈值语义无处安放;trivy 对 gomod 原生解析已满足扫描与严重度过滤)。 +- 不动 5.6 归档、死代码清理(在飞重构的地盘)。 +- 不实现 `strict_mode` 阻塞语义(与协议 §1 矛盾,永久移除)。 + +## Decisions + +1. **go 的解析器复用 trivy 格式、身份走规范映射**——`_PARSERS["go"] = _trivy` 是格式复用;`_result("go", …)` 保证"结果中报告的标识来自同一份权威映射"(spec 既有要求)。若让 go 报成 universal,命令行 `--ecosystem go` 的输入标识与输出标识就分叉了。 +2. **保留 UNKNOWN 进 severity 过滤**(同 scan_trivy 注释):先滤掉缺严重度发现会让解析器把它们当不存在——假 PASS。测试锁住该语义。 +3. **`.claude-plugin/marketplace.json` 同时进 bump 链**——只补清单不补发版链 = 下一版必漂移;`bumpPlain` 对单点 version 字段即覆盖,两行接入。 +4. **README 句级删除而非保留标注**——strict_mode 的"保留"状态只制造配置噪音;文档与解析同批消失,测试双向锁死。 +5. **paths.py 用 `os.pathsep` 而非双平台分支**——语义就是"PATH 的分隔符",抽象层早该在。 + +## Risks / Trade-offs + +- [go 报告格式依赖 trivy] → 与 universal 同一依赖面,无新增失败模式;precheck(go.mod/go.sum)保证非 go 项目走不到扫描。 +- [.claude-plugin 清单形状无法在本机装 Claude 验证] → 形态逐字段对齐 canvas 仓先例(074a580),版本链由 bump 与测试双锁;宿主现场安装仍属外部验收(同 5.4 的表述纪律)。 +- [strict_mode 从配置中消失] → 从无消费点;用户 yaml 里写了它也只被忽略(与现状一致),README 不再误导。 + +## Migration Plan + +1. 规格先行(两份 ADDED delta),归档前 strict 校验。 +2. 实现 + `tests/test_gap_closure_20260923.py` 同批;全套(unittest/run_all/check_architecture/ruff/openspec)绿后按仓规 minor bump 0.16.0。 +3. PR/CI/不可变 tag/Release 闭环,市场仓在主检出对齐新 main 后同步。 diff --git a/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/proposal.md b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/proposal.md new file mode 100644 index 0000000..6058fb0 --- /dev/null +++ b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/proposal.md @@ -0,0 +1,25 @@ +## Why + +codegraph 深查(2026-09-23)对当前 HEAD 实测出四个长期挂账的小缺口:① `hooks/hooks.json` 引用 `${CLAUDE_PLUGIN_ROOT}` 5 处、Claude 是声明宿主,但仓内**没有** `.claude-plugin/` 清单——Claude 宿主安装面缺件(canvas 仓先例:074a580 补清单);② CVE 扫描缺 go/gomod 生态(语言注册表有 go,自动选择映射到空,跑不出任何依赖漏洞检查);③ `scripts/paths.py` 5 处硬编码 `":"`,Windows 上 PATH 拼接/分割必错(4.1 记录在案的未验收面,修兼容是纯正确性);④ `strict_mode` 死旋钮——`config.py` 只解析零消费(PostToolUse 恒 exit 0 是协议 §1 设计,阻塞语义与之矛盾),README 也自述"保留未接线",假旋钮误导配置。 + +## What Changes + +- 新增 `.claude-plugin/marketplace.json`(对齐 canvas 仓形态:顶层仓库名 + owner + plugins[0] 条目),并把该清单纳入 `bump-plugin.mjs` 的版本链(否则下次发版必漂移)。 +- CVE 新增 **go** 生态:规范映射独立条目(aliases `golang`/`gomod`、languages `go`、markers `go.mod`/`go.sum`),扫描器走 trivy(原生解析 gomod),**报告格式复用 trivy JSON 解析器**、生态身份按规范映射报告——格式是格式,身份是身份;保留 UNKNOWN 进过滤(防假 PASS 同 universal 语义)。 +- `paths.py` 全部改为 `os.pathsep`(5 处)。 +- 摘除 `strict_mode`:config 解析 + defaults + README 双语句同步删除(保留解析 = 假旋钮)。 + +## Capabilities + +### New Capabilities + +无。 + +### Modified Capabilities + +- `plugin-manifest-contracts`: 新增 Claude 宿主市场清单的存在性与版本链要求。 +- `cve-dependency-scan`: 新增 go 生态经规范映射可扫的要求(含别名归一与阈值语义保持)。 + +## Impact + +`scripts/codeguard/{cve,cve_scanners,cve_reports,config}.py`、`scripts/paths.py`、`scripts/bump-plugin.mjs`、`.claude-plugin/marketplace.json`(新)、README 双语、新增 `tests/test_gap_closure_20260923.py`。退出码/JSON schema 不变;`strict_mode` 配置键移除(从无消费点)。5.6 归档与死码清理属在飞 `refactor-codeguard-architecture`,本变更不触碰。 diff --git a/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/cve-dependency-scan/spec.md b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/cve-dependency-scan/spec.md new file mode 100644 index 0000000..5aaa8e4 --- /dev/null +++ b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/cve-dependency-scan/spec.md @@ -0,0 +1,25 @@ +## ADDED Requirements + +### Requirement: Go ecosystem SHALL be scannable through the canonical map + +go/gomod SHALL 是规范映射中的一等生态(标识 `go`,别名至少含 `golang`/`gomod`),支持自动选择(项目含 `go.mod` 或 `go.sum` 时按语言映射命中)与显式选择(别名规范化到同一标识)。扫描结果中报告的生态标识 MUST 为 `go`(输入标识与输出标识同源);阈值语义 MUST 与其他生态一致(阈值及以上 + 缺严重度发现不被预过滤——不得产生假 PASS)。非 go 项目 SHALL 在扫描前被前置条件判为无法验证而非有漏洞。 + +#### Scenario: A Go project auto-selects the go ecosystem + +- **WHEN** 项目含 `go.mod` 且检测到 go 语言文件 +- **THEN** 自动选择解析为生态 `go` 并调用其扫描器 + +#### Scenario: Alias selection canonicalizes + +- **WHEN** 命令行显式选择 `--ecosystem golang`(或 `gomod`) +- **THEN** 规范化为 `go`,结果中报告的标识也是 `go` + +#### Scenario: Missing severity data is not pre-filtered + +- **WHEN** 扫描报告含缺严重度的 go 依赖发现且阈值为 HIGH +- **THEN** 该发现不被预过滤丢弃(与 universal 同语义),按判定规则计数 + +#### Scenario: Non-Go project without markers is unverified + +- **WHEN** 项目缺 `go.mod` 与 `go.sum` 时显式选择 go 生态 +- **THEN** 前置条件判定为无法验证(缺标志文件),不产生"有漏洞"结论 diff --git a/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/plugin-manifest-contracts/spec.md b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/plugin-manifest-contracts/spec.md new file mode 100644 index 0000000..9724084 --- /dev/null +++ b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/specs/plugin-manifest-contracts/spec.md @@ -0,0 +1,15 @@ +## ADDED Requirements + +### Requirement: Claude Code marketplace manifest SHALL exist and track the release version + +`.claude-plugin/marketplace.json` MUST 存在且为合法 JSON,形态为市场清单(顶层仓库标识与 owner,`plugins[]` 含 `name`/`description`/`version`/`source`)。`plugins[0].version` MUST 与各宿主 manifest 的版本一致,且发版工具 MUST 将该文件纳入版本链更新(版本漂移按契约失败)。 + +#### Scenario: Claude host installs the plugin + +- **WHEN** 检查发布仓的 `.claude-plugin/marketplace.json` +- **THEN** 文件存在、可解析、`plugins[0]` 的 name 为插件 id、`source` 为 `"./"`,且 version 与 `kimi.plugin.json` 等 manifest 一致 + +#### Scenario: A release bumps the version + +- **WHEN** 发版工具执行版本升级 +- **THEN** `.claude-plugin/marketplace.json` 的版本随其余 manifest 同步更新,读回校验覆盖该文件 diff --git a/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/tasks.md b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/tasks.md new file mode 100644 index 0000000..ad9241e --- /dev/null +++ b/openspec/changes/archive/2026-09-23-claude-manifest-and-go-cve/tasks.md @@ -0,0 +1,15 @@ +## 1. Specification + +- [x] 1.1 两份 ADDED delta(manifest 版本链 / go 生态规范映射) +- [x] 1.2 `openspec validate --strict` 通过 + +## 2. Implementation + +- [x] 2.1 `.claude-plugin/marketplace.json`(canvas 仓形态)+ `bump-plugin.mjs` 版本链接入 +- [x] 2.2 go 生态:`scan_go`(trivy、UNKNOWN 保序)+ `_PARSERS["go"]` 格式复用 + ECOSYSTEM_SCANNERS 条目(别名/语言/标志) +- [x] 2.3 `paths.py` 5 处 `os.pathsep`;`config.py` 摘除 `strict_mode`;README 双语句同步 + +## 3. Verification + +- [x] 3.1 `tests/test_gap_closure_20260923.py`:清单形态/版本链/bump 覆盖锁、go 映射/别名/前置条件/trivy 格式解析 parity、pathsep 源级锁、strict_mode 双向消失锁 +- [x] 3.2 全套终验(unittest / run_all / check_architecture / ruff / openspec --all) diff --git a/openspec/specs/cve-dependency-scan/spec.md b/openspec/specs/cve-dependency-scan/spec.md index 58d2f29..b3640cd 100644 --- a/openspec/specs/cve-dependency-scan/spec.md +++ b/openspec/specs/cve-dependency-scan/spec.md @@ -2,9 +2,7 @@ ## Purpose 定义 `codeguard cve` 的生态覆盖边界:哪些生态必须给出可验证结论、无原生扫描器时如何兜底、生态标识如何被接受与报告、严重级别阈值如何统一作用于每个扫描器,以及「通过 / 存在漏洞 / 无法验证」三类结果如何判定与退出。 - ## Requirements - ### Requirement: Universal fallback for ecosystems without a native scanner 当项目被识别到的语言不属于任何已有原生扫描器的生态时,系统 SHALL 使用通用扫描器给出结论,而不是报告「无可扫描生态」。 @@ -93,3 +91,28 @@ #### Scenario: npm findings below configured severity - **WHEN** npm 返回非零但报告只有阈值以下的漏洞 - **THEN** 当前阈值判定 PASS,并保留原始工具退出码 + +### Requirement: Go ecosystem SHALL be scannable through the canonical map + +go/gomod SHALL 是规范映射中的一等生态(标识 `go`,别名至少含 `golang`/`gomod`),支持自动选择(项目含 `go.mod` 或 `go.sum` 时按语言映射命中)与显式选择(别名规范化到同一标识)。扫描结果中报告的生态标识 MUST 为 `go`(输入标识与输出标识同源);阈值语义 MUST 与其他生态一致(阈值及以上 + 缺严重度发现不被预过滤——不得产生假 PASS)。非 go 项目 SHALL 在扫描前被前置条件判为无法验证而非有漏洞。 + +#### Scenario: A Go project auto-selects the go ecosystem + +- **WHEN** 项目含 `go.mod` 且检测到 go 语言文件 +- **THEN** 自动选择解析为生态 `go` 并调用其扫描器 + +#### Scenario: Alias selection canonicalizes + +- **WHEN** 命令行显式选择 `--ecosystem golang`(或 `gomod`) +- **THEN** 规范化为 `go`,结果中报告的标识也是 `go` + +#### Scenario: Missing severity data is not pre-filtered + +- **WHEN** 扫描报告含缺严重度的 go 依赖发现且阈值为 HIGH +- **THEN** 该发现不被预过滤丢弃(与 universal 同语义),按判定规则计数 + +#### Scenario: Non-Go project without markers is unverified + +- **WHEN** 项目缺 `go.mod` 与 `go.sum` 时显式选择 go 生态 +- **THEN** 前置条件判定为无法验证(缺标志文件),不产生"有漏洞"结论 + diff --git a/openspec/specs/plugin-manifest-contracts/spec.md b/openspec/specs/plugin-manifest-contracts/spec.md index 4e515eb..a564ddd 100644 --- a/openspec/specs/plugin-manifest-contracts/spec.md +++ b/openspec/specs/plugin-manifest-contracts/spec.md @@ -22,3 +22,17 @@ For each released manifest, the test contract SHALL assert that `len(glob("skill - **WHEN** a directory `skills//SKILL.md` exists but `` is neither in any lock source nor in `plugin-local-skills.json` - **THEN** the test fails with the same symmetric-set difference, pointing at the orphan directory +### Requirement: Claude Code marketplace manifest SHALL exist and track the release version + +`.claude-plugin/marketplace.json` MUST 存在且为合法 JSON,形态为市场清单(顶层仓库标识与 owner,`plugins[]` 含 `name`/`description`/`version`/`source`)。`plugins[0].version` MUST 与各宿主 manifest 的版本一致,且发版工具 MUST 将该文件纳入版本链更新(版本漂移按契约失败)。 + +#### Scenario: Claude host installs the plugin + +- **WHEN** 检查发布仓的 `.claude-plugin/marketplace.json` +- **THEN** 文件存在、可解析、`plugins[0]` 的 name 为插件 id、`source` 为 `"./"`,且 version 与 `kimi.plugin.json` 等 manifest 一致 + +#### Scenario: A release bumps the version + +- **WHEN** 发版工具执行版本升级 +- **THEN** `.claude-plugin/marketplace.json` 的版本随其余 manifest 同步更新,读回校验覆盖该文件 + diff --git a/scripts/bump-plugin.mjs b/scripts/bump-plugin.mjs index 43905b9..04f122a 100644 --- a/scripts/bump-plugin.mjs +++ b/scripts/bump-plugin.mjs @@ -96,7 +96,12 @@ const today = new Intl.DateTimeFormat("en-CA", { const repoDir = path.join(workspace, plugin.localDirectory); const edits = [{ file: catalogPath, description: `${pluginId}: ${oldVersion} -> ${newVersion}` }]; -const plainManifestRels = [".zcode-plugin/plugin.json", "kimi.plugin.json"]; +const plainManifestRels = [ + ".zcode-plugin/plugin.json", + "kimi.plugin.json", + // Claude 宿主市场清单(plugins[0].version 单点,bumpPlain 首个 version 匹配即覆盖) + ".claude-plugin/marketplace.json", +]; if (fs.existsSync(path.join(repoDir, "plugin.json"))) plainManifestRels.push("plugin.json"); for (const rel of plainManifestRels) { diff --git a/scripts/codeguard/config.py b/scripts/codeguard/config.py index 246f943..506eeea 100644 --- a/scripts/codeguard/config.py +++ b/scripts/codeguard/config.py @@ -17,7 +17,8 @@ def load_user_config() -> dict: cfg_path = Path.home() / ".zcode" / "settings.local.yaml" defaults = { "enabled_languages": [], - "strict_mode": True, + # strict_mode 已移除:从未有消费点(PostToolUse 恒 exit 0 是协议 §1 设计, + # 阻塞语义与之矛盾),文档行同步摘除——保留解析等于给用户假旋钮。 "auto_fix_on_save": True, # 默认 300s:Maven 冷缓存 install -DskipTests 普遍超 2 分钟(旧 120s # 实测把超时误报为阻断)。AI 可在 ~/.zcode/settings.local.yaml 覆盖。 @@ -34,7 +35,7 @@ def load_user_config() -> dict: if not m: return cfg block = m.group(1) - for key in ("strict_mode", "auto_fix_on_save"): + for key in ("auto_fix_on_save",): mm = re.search(rf"{key}:\s*(true|false)", block) if mm: cfg[key] = mm.group(1) == "true" diff --git a/scripts/codeguard/cve.py b/scripts/codeguard/cve.py index 1f3587f..ab30074 100644 --- a/scripts/codeguard/cve.py +++ b/scripts/codeguard/cve.py @@ -6,7 +6,15 @@ from .config import ConfigurationError, get_overrides from .cve_policy import CVSS_BAND_FLOOR, EXIT_USAGE, aggregate_exit -from .cve_scanners import run, scan_cargo, scan_maven, scan_node, scan_pip, scan_trivy +from .cve_scanners import ( + run, + scan_cargo, + scan_go, + scan_maven, + scan_node, + scan_pip, + scan_trivy, +) from .discovery import detect_languages, find_project_root @@ -34,6 +42,10 @@ def _scan_cargo_ecosystem(root: Path, severity: str, allow_fix: bool) -> dict: return scan_cargo(root, severity) +def _scan_go_ecosystem(root: Path, severity: str, allow_fix: bool) -> dict: + return scan_go(root, severity) + + def _scan_trivy_ecosystem(root: Path, severity: str, allow_fix: bool) -> dict: return scan_trivy(root, severity) @@ -46,6 +58,12 @@ def _scan_trivy_ecosystem(root: Path, severity: str, allow_fix: bool) -> dict: "markers": ("pom.xml",), "scan": _scan_maven_ecosystem, }, + "go": { + "aliases": ("golang", "gomod"), + "languages": ("go",), + "markers": ("go.mod", "go.sum"), + "scan": _scan_go_ecosystem, + }, "node": { "aliases": (), "languages": ("typescript",), diff --git a/scripts/codeguard/cve_reports.py b/scripts/codeguard/cve_reports.py index 07efe7c..01b7020 100644 --- a/scripts/codeguard/cve_reports.py +++ b/scripts/codeguard/cve_reports.py @@ -141,7 +141,10 @@ def _trivy(report, threshold): return ReportEvidence(tuple(findings), len(findings), exceeded, unknown) -_PARSERS = {"maven": _maven, "node": _node, "python": _pip, "rust": _cargo, "universal": _trivy} +# "go" 与 "universal" 同为 trivy JSON 报告格式——解析器按格式复用, +# 生态身份仍由 _result 的 eco 字段按规范映射报告。 +_PARSERS = {"maven": _maven, "node": _node, "python": _pip, "rust": _cargo, + "universal": _trivy, "go": _trivy} def parse_report(ecosystem: str, source: str, threshold: str | int) -> ReportEvidence: diff --git a/scripts/codeguard/cve_scanners.py b/scripts/codeguard/cve_scanners.py index 39a1196..06d7cae 100644 --- a/scripts/codeguard/cve_scanners.py +++ b/scripts/codeguard/cve_scanners.py @@ -78,6 +78,21 @@ def scan_cargo(root: Path, threshold: str = "LOW") -> dict: "按报告升级 Cargo.toml 中的受影响 crate(cargo update 可试)") +def scan_go(root: Path, threshold: str) -> dict: + """go 生态扫描:trivy 原生解析 gomod/go.sum,报告格式与 universal 同族。 + + 保留 UNKNOWN 进入过滤(同 scan_trivy 注释):若先滤掉缺严重度的发现, + 解析器会把它们当不存在——假 PASS。标识为 "go"(规范映射单独成条), + 解析复用 trivy 格式解析器——格式是格式,身份是身份。 + """ + selected = severities_at_and_above(threshold) + ["UNKNOWN"] + argv = ["trivy", "fs", "--scanners", "vuln", "--format", "json", "--exit-code", "2", + "--severity", ",".join(selected), "."] + outcome = run(argv, cwd=root, timeout=1800) + return _result("go", "trivy", root, argv, outcome, parse_report("go", outcome[1], threshold), + "确认 trivy 与漏洞库可用;按报告升级 go.mod 受影响依赖(go get 对应模块)", (0, 2)) + + def scan_trivy(root: Path, threshold: str) -> dict: # 若先过滤 UNKNOWN,解析器看不到缺严重度的发现,会产生假 PASS。 selected = severities_at_and_above(threshold) + ["UNKNOWN"] diff --git a/scripts/paths.py b/scripts/paths.py index a7b9602..35b6df1 100644 --- a/scripts/paths.py +++ b/scripts/paths.py @@ -37,18 +37,18 @@ def ensure_user_path(from_login_shell: bool = False) -> None: str(Path.home() / ".local" / "pipx" / "bin"), ] cur = os.environ.get("PATH", "") - parts = cur.split(":") + parts = cur.split(os.pathsep) for d in reversed(static_dirs): if Path(d).exists() and d not in parts: parts.insert(0, d) - os.environ["PATH"] = ":".join(parts) + os.environ["PATH"] = os.pathsep.join(parts) # node/npx 不可用且静态目录未覆盖时,自动降级登录 shell 继承一次 # (覆盖 nvm/fnm/Kimi runtime 等非标准 node 安装;约 100-300ms) def _node_available() -> bool: return any( d and (Path(d) / "node").exists() - for d in os.environ.get("PATH", "").split(":") + for d in os.environ.get("PATH", "").split(os.pathsep) ) if not from_login_shell and not _node_available(): @@ -66,7 +66,7 @@ def _node_available() -> bool: try: age = now - cache_file.stat().st_mtime cached = cache_file.read_text().strip() - if age < 600 and ":" in cached: + if age < 600 and os.pathsep in cached: os.environ["PATH"] = cached return except OSError: @@ -83,7 +83,7 @@ def _node_available() -> bool: # 即使登录 shell 没提供更丰富的 PATH,也缓存这次探测结果。 # Linux CI 的 login shell 常与当前 PATH 等价;若不写缓存, # 每次钩子都会重复 spawn shell,违背十分钟缓存契约。 - resolved = inherited[-1] if inherited[-1].count(":") > cur.count(":") else os.environ["PATH"] + resolved = inherited[-1] if inherited[-1].count(os.pathsep) > cur.count(os.pathsep) else os.environ["PATH"] os.environ["PATH"] = resolved with contextlib.suppress(OSError): cache_file.write_text(resolved) diff --git a/tests/_subsets.py b/tests/_subsets.py index d335d41..31ea767 100644 --- a/tests/_subsets.py +++ b/tests/_subsets.py @@ -692,7 +692,7 @@ def test_cve(): (d / "main.go").write_text("package main\n") r = run_cve([str(d)], d) out = r.stdout + r.stderr - ok("go 项目自动走 universal 兜底", "ecosystems: ['universal']" in r.stdout, r.stdout.strip()[:120]) + ok("go.lock-only 目录(无 .go 文件)走 universal 兜底", "ecosystems: ['universal']" in r.stdout, r.stdout.strip()[:120]) ok("trivy 缺失 → 无法验证(1) 而非漏洞(2)", r.returncode == 1, f"rc={r.returncode}") ok("兜底路径不误报「有漏洞」", "有漏洞" not in out) diff --git a/tests/test_gap_closure_20260923.py b/tests/test_gap_closure_20260923.py new file mode 100644 index 0000000..6cddd59 --- /dev/null +++ b/tests/test_gap_closure_20260923.py @@ -0,0 +1,118 @@ +"""2026-09-23 缺口补齐回归(claude-manifest / go cve / pathsep / strict_mode 摘除)。 + +OpenSpec change 2026-09-23-claude-manifest-and-go-cve。每条断言对应一个实测挂账项: +Claude 安装面缺件、go 生态映射为空、PATH 硬编码分隔符、零消费的假配置旋钮。 +""" +from __future__ import annotations + +import json +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +import sys + +sys.path.insert(0, str(ROOT / "scripts")) +sys.path.insert(0, str(ROOT / "scripts" / "codeguard")) + + +class ClaudeManifestTests(unittest.TestCase): + """Claude 宿主市场清单:存在、形态、版本链、发版工具覆盖。""" + + def test_manifest_exists_and_matches_canvas_shape(self) -> None: + path = ROOT / ".claude-plugin" / "marketplace.json" + self.assertTrue(path.is_file(), "Claude 宿主安装面缺 .claude-plugin/marketplace.json") + data = json.loads(path.read_text(encoding="utf-8")) + self.assertEqual(data["name"], "codeguard-plugin") + self.assertEqual(data["owner"], {"name": "full-stack-plugins"}) + entry = data["plugins"][0] + self.assertEqual(entry["name"], "codeguard") + self.assertEqual(entry["source"], "./") + self.assertGreater(len(entry["description"]), 32) + self.assertNotIn("mcpServers", data) + + def test_version_tracks_the_release_chain(self) -> None: + claude = json.loads( + (ROOT / ".claude-plugin" / "marketplace.json").read_text(encoding="utf-8") + ) + kimi = json.loads((ROOT / "kimi.plugin.json").read_text(encoding="utf-8")) + self.assertEqual( + claude["plugins"][0]["version"], kimi["version"], + "Claude 清单版本必须与其余 manifest 同链,否则安装面拿到错版本", + ) + + def test_bump_tool_covers_the_claude_manifest(self) -> None: + source = (ROOT / "scripts" / "bump-plugin.mjs").read_text(encoding="utf-8") + self.assertIn(".claude-plugin/marketplace.json", source, + "发版工具必须把 Claude 清单纳入版本链,否则下次发版必漂移") + + +class GoCveEcosystemTests(unittest.TestCase): + """go 生态:规范映射独立条目,解析复用 trivy 格式,身份不与 universal 混同。""" + + def test_go_entry_in_the_canonical_map(self) -> None: + from codeguard.cve import ECOSYSTEM_SCANNERS, language_ecosystem_map + + spec = ECOSYSTEM_SCANNERS["go"] + self.assertEqual(spec["aliases"], ("golang", "gomod")) + self.assertEqual(spec["languages"], ("go",)) + self.assertEqual(spec["markers"], ("go.mod", "go.sum")) + self.assertEqual(language_ecosystem_map().get("go"), "go", + "go 语言此前映射到空——自动选择永远选不出可扫描生态") + + def test_report_identity_is_go_not_universal(self) -> None: + from codeguard.cve_reports import parse_report + + sample = json.dumps({ + "Results": [{"Target": "go.mod", "Vulnerabilities": [ + {"VulnerabilityID": "GO-2026-0001", "Severity": "HIGH"}, + {"VulnerabilityID": "GO-2026-0002", "Severity": "CRITICAL"}]}] + }) + report = parse_report("go", sample, "HIGH") + self.assertGreater(report.observed, 0) + self.assertTrue(report.exceeded) + # 同一份 trivy 格式在 universal 下逐字段同判定(格式复用语义) + other = parse_report("universal", sample, "HIGH") + self.assertEqual(report.observed, other.observed) + self.assertEqual(report.exceeded, other.exceeded) + + def test_missing_severity_is_not_prefiltered(self) -> None: + """缺严重度发现不得被预过滤成假 PASS(与 universal 同语义)。""" + from codeguard.cve_reports import parse_report + + sample = json.dumps({"Results": [{"Target": "go.sum", "Vulnerabilities": [ + {"VulnerabilityID": "GO-2026-0003"}]}]}) + report = parse_report("go", sample, "HIGH") + self.assertTrue(report.unknown, "缺严重度发现必须进入报告而非被滤掉") + + def test_precheck_requires_go_markers(self) -> None: + from codeguard.cve import precheck + + bare = Path(tempfile.mkdtemp(prefix="cg-nogo-")) + ok, reason = precheck(bare, "go") + self.assertFalse(ok) + self.assertIn("go.mod", reason) + (bare / "go.mod").write_text("module x\n", encoding="utf-8") + ok, reason = precheck(bare, "go") + self.assertTrue(ok, reason) + + +class PathsepAndDeadKnobTests(unittest.TestCase): + def test_paths_uses_os_pathsep(self) -> None: + text = (ROOT / "scripts" / "paths.py").read_text(encoding="utf-8") + self.assertIn("os.pathsep", text) + self.assertNotIn('":".join', text, "PATH 拼接必须用 os.pathsep,Windows 上 \":\" 是错的") + self.assertNotIn('split(":")', text) + + def test_strict_mode_is_gone_everywhere_it_misled(self) -> None: + from codeguard.config import load_user_config + + self.assertNotIn("strict_mode", load_user_config()) + for name in ("README.md", "README.zh-CN.md"): + text = (ROOT / name).read_text(encoding="utf-8") + self.assertNotIn("strict_mode", text, f"{name} 不得再描述假旋钮") + + +if __name__ == "__main__": + unittest.main() From c236c0434fd4b1d87241667540fc5d0301982368 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:54:25 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(docs):=20bin=20=E7=94=9F=E6=80=81?= =?UTF-8?q?=E7=94=A8=E6=B3=95=E8=A1=A5=20go=20+=20CHANGELOG=20=E5=A2=9E=20?= =?UTF-8?q?0.16.0=20=E6=9D=A1=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 合并态 doc-behavior-parity 缺口:0.16.0 新增 go CVE 生态后 bin/codeguard 用法行未列 go(test_bin_usage_ecosystems_match_canonical_set FAIL);CHANGELOG 缺 0.16.0 段。两处补齐后 parity 全绿。 --- CHANGELOG.md | 5 +++++ bin/codeguard | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a892a..b87c98f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ 按版本段落提炼的主题摘要(生成于 2026-09-23,来源:git 历史 212 个提交与各 release 提交)。逐提交细节以 `git log` 与 GitHub Releases 为准;本文件按主题归纳,不逐条罗列。 +## v0.16.0 — Claude 安装面与 Go CVE 生态 + +- Claude 安装面补件(新增 `.claude-plugin/marketplace.json`);CVE 新增 go 生态; + pathsep 修复;`strict_mode` 摘除。 + ## v0.15.x — Git 归因收敛与诊断安全 - **v0.15.4** 测试卫生与可见性:`tests/run_all.py`(809 行)拆分为 diff --git a/bin/codeguard b/bin/codeguard index df5c413..7477266 100755 --- a/bin/codeguard +++ b/bin/codeguard @@ -6,7 +6,7 @@ # codeguard fix [--lang LANG] [path] # 自动修复 lint 问题 # codeguard cve [--ecosystem E] [--severity S] [--fix] [--json] [path] # # CVE 依赖漏洞扫描 -# # 生态: maven(别名 java) / node / python / rust / universal(别名 trivy) +# # 生态: maven(别名 java) / node / python / rust / go / universal(别名 trivy) # # 退出码: 0 通过 / 1 无法验证 / 2 存在漏洞 / 3 参数错误 # codeguard dockerfile [--json] [path] # Dockerfile 安全风险检查(hadolint + trivy config) # codeguard detect [path] # 检测项目语言 From 7709e114b5acc22b5714551168398a6b33597041 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:59:47 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(test):=20cve=20=E5=AD=90=E9=9B=86?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E5=AF=B9=E9=BD=90=200.16.0=20go=20=E7=94=9F?= =?UTF-8?q?=E6=80=81=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 组合态回归 5 红根因:0.16.0 起 go 已是 canonical 生态,_subsets 里 以 go 为「未声明/未知生态/兜底」样例的旧断言全部过期: - 未声明标识样例 go → deno - 未知生态退出码=3 测试 go → deno(连带 3 断言) - universal 兜底 fixture go.mod+main.go → index.php(保持 「无原生扫描器→兜底」原意图;go 派发语义由 test_cve_boundaries 覆盖) run_all 144/0/0 本地全绿。 --- tests/_subsets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/_subsets.py b/tests/_subsets.py index 31ea767..ab61258 100644 --- a/tests/_subsets.py +++ b/tests/_subsets.py @@ -598,7 +598,7 @@ def test_cve(): ok("trivy 归一到 universal", cve.canonical_ecosystem("trivy") == "universal") ok("规范标识自身可解析", cve.canonical_ecosystem("python") == "python") ok("大小写不敏感", cve.canonical_ecosystem("JAVA") == "maven") - ok("未声明标识返回 None", cve.canonical_ecosystem("go") is None) + ok("未声明标识返回 None", cve.canonical_ecosystem("deno") is None) ok("可接受值含别名", "java" in cve.ecosystem_choices() and "trivy" in cve.ecosystem_choices()) # 1.2:前置条件从权威映射派生,独立死表已移除 @@ -611,7 +611,7 @@ def test_cve(): all(v in cve.ECOSYSTEM_SCANNERS for v in cve.language_ecosystem_map().values())) # 5.2:未知生态在扫描前拒绝,退出码 3,且不混用其他结论文案 - r = run_cve(["--ecosystem", "go", str(tmp)], tmp) + r = run_cve(["--ecosystem", "deno", str(tmp)], tmp) out = r.stdout + r.stderr ok("未知生态退出码=3", r.returncode == 3, f"rc={r.returncode}") ok("退出码与「存在漏洞」「无法验证」不重叠", r.returncode not in (1, 2)) @@ -687,12 +687,12 @@ def test_cve(): ok("漏洞(2) 优先于无法验证(1)", rc_mixed == 2, f"rc={rc_mixed}") # ── C1-5.4/5.5:无原生扫描器的语言自动落兜底;trivy 缺失=无法验证而非漏洞 ── + # (0.16.0 起 go 已有原生生态 → 兜底样例改用 php;go 派发语义由 test_cve_boundaries 覆盖) d = Path(tempfile.mkdtemp()) - (d / "go.mod").write_text("module x\n\ngo 1.21\n") - (d / "main.go").write_text("package main\n") + (d / "index.php").write_text("