From 64b46c860a4e33623b8d3aab93e08842e63732c9 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Thu, 24 Sep 2026 02:55:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(rust-cli):=20=E7=BB=9F=E4=B8=80=20Rust?= =?UTF-8?q?=20CLI=20=E5=8F=98=E6=9B=B4=E9=AA=A8=E6=9E=B6=E4=B8=8E=20v0.16.?= =?UTF-8?q?1=20=E5=8F=91=E7=89=88=E9=9D=A2=E9=A2=84=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/rust-cli 五份设计文档(README / 架构 / 技术设计 / 覆盖率验收 / 修复工作流) - openspec 变更 introduce-rust-codeguard-cli:proposal / design / tasks(300 行) / verification + 10 个能力 delta - bump-plugin 写后回读支持 .claude-plugin 嵌套版本路径(plugins[0].version) - 五份清单 bump 0.16.1+codex.20260924;CHANGELOG v0.16.1 段 - hook-protocol delta 补带场景「SkipGate set with a secret on the commit face」, 避免 MODIFIED 重写丢掉已归档语义 --- .agents/plugins/marketplace.json | 8 +- .claude-plugin/marketplace.json | 2 +- .codex-plugin/plugin.json | 2 +- .zcode-plugin/plugin.json | 2 +- CHANGELOG.md | 6 + docs/rust-cli/README.md | 17 + docs/rust-cli/architecture.zh-CN.md | 207 ++++++++++++ .../rust-cli/coverage-and-acceptance.zh-CN.md | 167 ++++++++++ docs/rust-cli/remediation-workflow.zh-CN.md | 214 +++++++++++++ docs/rust-cli/technical-design.zh-CN.md | 292 +++++++++++++++++ kimi.plugin.json | 2 +- .../.openspec.yaml | 3 + .../introduce-rust-codeguard-cli/design.md | 69 ++++ .../introduce-rust-codeguard-cli/proposal.md | 43 +++ .../specs/binary-distribution/spec.md | 33 ++ .../specs/execution-kernel/spec.md | 69 ++++ .../specs/hook-protocol/spec.md | 71 +++++ .../specs/language-gate-commands/spec.md | 71 +++++ .../specs/native-tool-adapters/spec.md | 73 +++++ .../specs/remediation-workflow/spec.md | 93 ++++++ .../specs/rulepack-governance/spec.md | 57 ++++ .../specs/scan-scope-policy/spec.md | 17 + .../specs/unified-cli-contract/spec.md | 57 ++++ .../specs/verdict-integrity/spec.md | 37 +++ .../introduce-rust-codeguard-cli/tasks.md | 300 ++++++++++++++++++ .../verification.md | 38 +++ scripts/bump-plugin.mjs | 7 +- 27 files changed, 1948 insertions(+), 9 deletions(-) create mode 100644 docs/rust-cli/README.md create mode 100644 docs/rust-cli/architecture.zh-CN.md create mode 100644 docs/rust-cli/coverage-and-acceptance.zh-CN.md create mode 100644 docs/rust-cli/remediation-workflow.zh-CN.md create mode 100644 docs/rust-cli/technical-design.zh-CN.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/.openspec.yaml create mode 100644 openspec/changes/introduce-rust-codeguard-cli/design.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/proposal.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/binary-distribution/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/execution-kernel/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/hook-protocol/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/language-gate-commands/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/native-tool-adapters/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/remediation-workflow/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/rulepack-governance/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/scan-scope-policy/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/unified-cli-contract/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/specs/verdict-integrity/spec.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/tasks.md create mode 100644 openspec/changes/introduce-rust-codeguard-cli/verification.md diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 20c2f96..1782c6a 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.16.0" + "ref": "v0.16.1" }, "policy": { "installation": "AVAILABLE", "authentication": "ON_USE" }, "category": "Developer Tools", - "version": "0.16.0", + "version": "0.16.1", "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.16.0/assets/official-logo.png", + "icon": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.16.1/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.16.0/assets/official-logo.png" + "logo": "https://cdn.jsdelivr.net/gh/full-stack-plugins/codeguard-plugin@v0.16.1/assets/official-logo.png" } } ] diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 621caaf..f11c7fd 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,7 +7,7 @@ { "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", + "version": "0.16.1", "source": "./" } ] diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 7362a81..8b9d79c 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "codeguard", - "version": "0.16.0+codex.20260923", + "version": "0.16.1+codex.20260924", "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 edb8742..7550849 100644 --- a/.zcode-plugin/plugin.json +++ b/.zcode-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "代码规范守卫", "zh-CN": "代码规范检查" }, - "version": "0.16.0", + "version": "0.16.1", "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/CHANGELOG.md b/CHANGELOG.md index b87c98f..942554b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ 按版本段落提炼的主题摘要(生成于 2026-09-23,来源:git 历史 212 个提交与各 release 提交)。逐提交细节以 `git log` 与 GitHub Releases 为准;本文件按主题归纳,不逐条罗列。 +## v0.16.1 — 测试可移植性收口与汇流 + +- 测试可移植性与迁移债三批收口(P0 skipIf 守卫、requirements-dev、CI 矩阵 3.11/3.12/3.13;P1 shim 弃用通告、SCRIPT_ROLES 角色登记、双轨分工说明;P2 bump 歧义报错、init exit 0、README 计数锁定),615/615 单测与 run_all 144/144 全过。 +- bump-plugin 写后回读支持 `.claude-plugin` 嵌套版本路径(`plugins[0].version`)。 +- 汇流收口:test-portability 批次与起草中的统一 Rust CLI 变更骨架一并入 main。 + ## v0.16.0 — Claude 安装面与 Go CVE 生态 - Claude 安装面补件(新增 `.claude-plugin/marketplace.json`);CVE 新增 go 生态; diff --git a/docs/rust-cli/README.md b/docs/rust-cli/README.md new file mode 100644 index 0000000..3bec19c --- /dev/null +++ b/docs/rust-cli/README.md @@ -0,0 +1,17 @@ +# Codeguard Rust CLI 设计文档 + +状态:设计基线,尚未实现。日期:2026-09-24。源码核对基线:`03ebb24`;后续实现必须重新核对工作树和已合并规格。 + +| 文档 | 回答的问题 | +|---|---| +| [架构文档](architecture.zh-CN.md) | 产品目标、模块边界、检查流程、低误报机制、权威边界 | +| [技术方案](technical-design.zh-CN.md) | 命令、数据协议、适配器、执行器、配置、缓存、分发与兼容实现 | +| [语言迁移与验收](coverage-and-acceptance.zh-CN.md) | 57 个条目的迁移、类别覆盖、评测方法、验收证据 | +| [持久问题与修复工作流](remediation-workflow.zh-CN.md) | 项目内 codeguard 目录、问题记录、待办任务、智能体修复与复检闭环 | +| [OpenSpec proposal](../../openspec/changes/introduce-rust-codeguard-cli/proposal.md) | 为什么变更及变更范围 | +| [OpenSpec design](../../openspec/changes/introduce-rust-codeguard-cli/design.md) | 架构决策、兼容性、实施顺序和待验证事项 | +| [OpenSpec tasks](../../openspec/changes/introduce-rust-codeguard-cli/tasks.md) | 可执行任务、依赖和完成证据 | + +本次需求的唯一规范性事实源为 `openspec/changes/introduce-rust-codeguard-cli/specs/`。这些说明文档展开设计,不创建第二套任务状态。仓库 `openspec/specs/` 与 [当前架构](../current-architecture.md) 仍描述已落地的旧运行时;本 change 未实施前不得宣称 Rust 行为已经生效。 + +本次验收记录见 [verification.md](../../openspec/changes/introduce-rust-codeguard-cli/verification.md)。文档校验成功不代表二进制、扫描器或宿主运行验收成功。 diff --git a/docs/rust-cli/architecture.zh-CN.md b/docs/rust-cli/architecture.zh-CN.md new file mode 100644 index 0000000..8104944 --- /dev/null +++ b/docs/rust-cli/architecture.zh-CN.md @@ -0,0 +1,207 @@ +# Codeguard Rust CLI 架构设计 + +本方案把 Codeguard 恢复为传统静态工具驱动的多语言代码质量守卫。统一入口是 Rust 二进制 `codeguard`,智能体负责调用、解释和修复,检查事实来自工具,门禁要求来自受控策略。 + +状态:待实现设计。规范性要求见 [OpenSpec change](../../openspec/changes/introduce-rust-codeguard-cli/proposal.md)。实现细节见 [技术方案](technical-design.zh-CN.md),能力与测试见 [迁移验收](coverage-and-acceptance.zh-CN.md)。 + +项目内新增 `./codeguard/` 持久工作区,保存脱敏问题、任务和修复事件;本地报告、日志、缓存默认 Git 忽略。智能体以 `next` 获取可推进任务,以 `task verify` 完成真实复检。完整设计见 [修复工作流](remediation-workflow.zh-CN.md)。 + +## 1. 产品目标与不可变约束 + +1. 覆盖已支持语言的 lint、注释规范、CVE、安全规范及构建检查;每类检查都必须有明确适用性、执行能力和结果。 +2. 优先复用语言生态的成熟工具和项目配置。Rust 统一编排与证据,不重新实现各语言编译器和静态分析器。 +3. 低误报来自准确的工具版本、规则、语言方言、构建上下文和报告解析。不能靠关闭规则、减少检查范围或默认忽略旧问题改善数字。 +4. 代码违规与执行故障分别呈现;必需检查未完成时交付门禁不通过。 +5. AI 无权自行降低质量要求。修代码、修适配器、修工具配置的过程均留下可复核的变化与复检证据。 +6. 一次检查结论绑定实际内容、有效配置、工具与规则版本。不能借用工作树的通过替代 index 或待推送内容。 +7. 单语言、单类别命令的通过只代表该请求完成;全项目交付需要完整 contract 的证据。 + +“所有语言”是可扩展的适配器体系和明确的覆盖承诺,不是任何文件后缀都会自动拥有完整分析器。当前 57 个注册条目是迁移输入;对于没有可用检查器的类别,必须报告缺口,不能生成空适配器充数。 + +## 2. 当前实现证据与设计纠偏 + +以下为 `03ebb24` 的静态观察,不是对当前误报率的统计,也不替代实机复现。 + +| 当前证据 | 影响 | 新设计 | +|---|---|---| +| [入口](../../bin/codeguard) 是 Bash 转发 Python;语言、CVE、Dockerfile 有不同协议 | 调用方容易把不同退出码当成相同状态 | Rust 统一报告;旧数字经版本化兼容层映射 | +| [verdict.py](../../scripts/codeguard/verdict.py) 有通用 rc=1/cargo=101 和环境文本归类 | 进程失败与工具发现仍可能混淆 | 每个工具版本具有专用结果契约,不在通用执行器判断违规 | +| [gate_checks.py](../../scripts/codeguard/gate_checks.py) 在最多 50 个文件时走 delta,并调用基线豁免 | 性能分支改变语义,历史发现可消失 | 全部必需义务保持不变;分批、缓存、基线只优化或分类 | +| [hook-protocol](../../openspec/specs/hook-protocol/spec.md) 规定 fail-open 与 skipGate | 未完成检查可能不阻止交付动作 | 迁移后交付入口要求完整;普通保存反馈仍不冒充交付门禁 | +| [语言表](../../scripts/languages.json) 有 54 stable、3 planned,Julia/Pascal 的 lint 为空 | 语言标签不能证明真实检测能力 | 按语言 × 类别 × 工具 × 平台验收 | +| [本地规则文件](../../linters/checkstyle/p3c-javadoc-enforced.xml) 是 Checkstyle XML | 文件名不能证明已执行官方 P3C | 官方 P3C/PMD 与 Checkstyle/Javadoc 分开建模与取证 | +| [执行内核](../../scripts/codeguard/execution.py)、Git 快照、Java 影响分析已有专门模块 | 有价值的行为与回归样本可迁移 | 迁移证据和正确性约束,不逐行翻译全部历史策略 | + +旧版本的 fail-open、默认 delta、基线豁免是本次明确要改变的行为;不能用“兼容旧行为”把它们重新带入新门禁。兼容层只在显式旧协议范围内存在,并标明不能提供新交付认证。 + +## 3. 系统边界 + +```mermaid +flowchart TB + Human[开发者 / 质量策略负责人] --> Policy[受控质量策略与规则锁] + Agent[AI 编程智能体] --> Plugin[codeguard-plugin 宿主薄入口] + Skills[codeguard-skills 调用与修复指南] --> Agent + Plugin --> CLI[codeguard 原生二进制] + Terminal[终端 / 编辑器] --> CLI + CI[受保护 CI / Git 接入] --> CLI + Policy --> CLI + CLI --> Tools[语言原生 lint / 文档 / 安全 / CVE / 构建工具] + Tools --> Report[原始证据 + 标准报告 + 覆盖账本] + Report --> Gate[完整性与违规门禁] + Gate --> Agent + Gate --> CI +``` + +| 交付物 | 所有权 | 不承担的职责 | +|---|---|---| +| `codeguard-cli` | Rust 内核、适配器、rulepack、schema、二进制发布 | 宿主专用提示词和市场目录 | +| `codeguard-plugin` | 三宿主入口、hook 事件映射、运行时版本/摘要绑定 | 复制检查逻辑、另写 verdict、默默 fallback | +| `codeguard-skills` | 使用、诊断、修复步骤的技能事实源 | 生成比工具证据更高优先级的门禁结论 | +| 项目/组织策略 | 规则、适用性、阈值、豁免审批、受保护 CI | 依赖智能体自觉执行作为唯一约束 | + +现有受管技能仍从独立技能仓经 lock/vendor 同步;不直接修改本插件受管副本。新 Rust 仓的远程地址、发布签名身份及平台 ABI 在实施任务中确定,本次不虚构已有仓库或二进制。 + +## 4. Rust workspace 与依赖方向 + +```text +codeguard-cli/ +├── Cargo.toml +├── crates/ +│ ├── codeguard-cli/ +│ ├── codeguard-core/ +│ ├── codeguard-runtime/ +│ └── codeguard-adapters/ +├── rulepacks/ +├── schemas/ +└── tests/ + ├── fixtures/ + ├── integration/ + └── acceptance/ +``` + +仓库、crate 目录和 Cargo package 使用上面的 kebab-case;模块与 `.rs` 文件使用 snake_case;类型使用 PascalCase;二进制名 `codeguard`。一类主要职责一个文件,`mod.rs` 只组织和重导出;不以 `compat.rs` 堆积领域对象,不提交 `todo!()` 适配器作为完成项。新原生实现没有 Java 对应物时,中文 doc 注释说明用途,不伪造 Java 来源。 + +```mermaid +flowchart LR + CLI[codeguard-cli] --> Core[codeguard-core] + CLI --> Runtime[codeguard-runtime] + CLI --> Adapters[codeguard-adapters] + Runtime --> Core + Adapters --> Core +``` + +这是编译依赖图,不是进程调用图。禁止 core 导入 runtime/adapters/宿主 SDK;禁止 adapters 导入 runtime。core 定义 `ExecutionPort`、`SnapshotPort`、`EvidenceStore` 等接口,runtime 实现;CLI 组合运行时与适配器并调用 core 的协调服务。 + +| crate | 主要模块 | 关键责任 | +|---|---|---| +| cli | args、commands、render、mcp、compat | 参数、入口协议、stdout/stderr、终端报告、退出码映射 | +| core | model、policy、planning、coverage、gate、remediation、ports | 检查义务、任务 DAG、结果聚合、覆盖证明、问题生命周期与修复任务;尽量纯函数 | +| runtime | process、snapshot、toolchain、cache、storage、workspace、telemetry | 操作系统进程、Git/文件观察、工具解析、有界资源、持久记录与租约 | +| adapters | java、rust、python、node、shell、… | 专用工具探测、计划、报告解析、覆盖核对、修复计划 | + +初版采用编译期适配器注册。任意脚本、动态库插件或远程规则执行不是默认扩展入口,避免将字符串命令表原样升级为新的执行系统。 + +## 5. 一次检查的完整路径 + +```mermaid +flowchart TD + Request[解析请求与入口协议] --> Resolve[定位项目与输入内容身份] + Resolve --> Contract[解析可信策略和全部必需义务] + Contract --> Discover[发现语言 / 模块 / 依赖清单 / 工具配置] + Discover --> Plan[适配器生成有依赖关系的计划] + Plan --> Ready{前置条件可验证?} + Ready -- 否 --> Incomplete[记录未完成原因与恢复动作] + Ready -- 是 --> Run[复用有效缓存或执行原生工具] + Run --> Parse[工具专用解析器保留全部有效发现] + Parse --> Coverage[核对目标覆盖 / 配置 / 输入身份] + Incomplete --> Gate[门禁聚合] + Coverage --> Gate + Gate --> Output[JSON / 人类报告 / SARIF 与私有证据] +``` + +解析、规划、执行、报告各阶段都可失败;失败必须产生具体原因及未完成义务。某个检查失败不抹掉其他已确认发现。可独立执行的任务继续;依赖失败的任务标为依赖未满足,不能被删掉。 + +`check all` 建立项目全部适用类别的义务;`lint java` 只选择 Java lint。项目没有使用某语言与某语言检查器缺失是不同事实,后者不能伪装成不适用。 + +## 6. 结果与门禁的分离 + +单个检查结果至少包含三个维度: + +- 执行完整性:`complete / incomplete / not_applicable`。 +- 发现:零条或多条带规则、位置、工具证据的 finding;不随执行失败清空。 +- 门禁影响:由有效策略判定,而不是工具退出码或模型的主观置信度决定。 + +```mermaid +flowchart LR + C[完整性] --> G[交付判定] + F[已确认违规] --> G + P[有效质量策略] --> G + G --> A[满足全部义务: allow] + G --> B[存在阻断违规: deny] + G --> I[必需义务未完成: incomplete] +``` + +`incomplete` 与 `deny` 都不满足交付要求,但反馈不同:前者修工具链、配置或证据,后者修代码或依赖。混合情况报告全部发现,顶层优先显示未完成,不能暗示“只是工具故障,没有问题”。 + +注释检查不能被格式化检查替代;`cargo fmt --check` 不等于 `clippy`;CVE 扫描不等于源代码安全检查;Maven `verify` 成功不证明未绑定的 P3C/Checkstyle/Javadoc 已执行。Maven 生命周期通过插件绑定具体目标,必须验证实际执行项。[Maven 官方生命周期说明](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html) + +## 7. 低误报机制 + +| 误报来源 | 工程控制 | 必需证据 | +|---|---|---| +| Java/Python/SQL/Shell 方言和版本错误 | 读取项目目标版本及构建上下文,验证工具支持矩阵 | 解析依据、工具版本、命令与配置摘要 | +| 工具自身崩溃被当成代码问题 | 工具专用退出码与结构化报告契约 | 原始 rc、故障类型、有效报告片段 | +| 风格默认值与项目约定冲突 | 显式选择版本化 rulepack,解释规则来源 | effective config、规则差异及策略权威 | +| 子模块、生成代码、依赖目录误入 | 模块图、源集、批准排除与覆盖核对 | 义务清单、排除理由、扫描目标集合 | +| 重复或历史发现误处理 | 按稳定规则/位置/内容去重,基线只标 new/existing | 不消失的原始发现及归并映射 | +| CVE 包匹配或严重度不确定 | 真实解析依赖版本、来源与漏洞库元数据 | package identity、依赖路径、advisory、时间与摘要 | +| 正则对注释和安全语义误判 | 优先语言工具/AST 和可测试规则 | 正反例;无证据的语义猜测不成为确定违规 | + +无法确定的检测仍是待解决的覆盖或证据问题,不能通过“低置信度就删除”降低误报率。规则是否阻断由事先确认的策略决定,运行时不动态调低阈值。 + +注释的语法、公共 API 文档存在性、参数/返回值对应关系可用确定性工具检查;“注释是否真正解释业务动机”不能无证据宣称已验证。明确的中文注释要求可作为专用规则,但必须处理专有名词、代码片段、继承文档与生成代码,不能按单字符语言判断粗暴拦截。 + +## 8. 性能与正确性 + +Rust 可减少启动和编排开销,但总耗时通常受外部工具、构建和漏洞库影响;“性能最高”必须通过同范围、同规则、同缓存条件的测量验证。 + +性能手段是复用构建结果、任务 DAG、有界并发、精确缓存和合并相同扫描。增量执行必须证明未重跑义务的输入与配置没有变化,且适配器支持这种复用;证明不了就扩大扫描。文件数从 50 到 51 只能改变调度方式,不能改变问题是否阻断。 + +同一个 target/build 目录和不可并发工具使用资源锁。超时、输出上限、网络状态或缓存故障均不得变成 PASS。保存事件采用较短预算只提供反馈,交付入口使用完整预算;二者结果不能不加区分复用。 + +## 9. 权威与安全边界 + +```mermaid +sequenceDiagram + participant A as 智能体 + participant C as codeguard + participant P as 可信策略源 + participant T as 原生检查器 + participant G as 受保护 CI + A->>C: check all,绑定内容身份 + C->>P: 读取批准的规则与工具锁 + C->>T: 执行计划 + T-->>C: 原生报告与退出状态 + C-->>A: 发现 / 未完成原因 / 门禁状态 + A->>C: 修复代码后复检 + G->>C: 独立加载可信策略并检查待交付内容 + C-->>G: 可核对的最终证据 +``` + +不能从 agent 的“用户已经同意”文本、环境变量、Git config 或可修改的本地 JSON 得出政策授权。策略调整是独立、可审查的变更;合并后的可信修订才作为 CI 的有效版本。例外必须绑定规则、范围、内容身份、原因、批准身份和到期时间,保留原始违规与非通过检查状态;外部系统可记录“经批准例外交付”,不得显示普通 PASS。 + +CLI 与用户同权限,无法防止用户替换二进制、绕过本地 hook 或修改磁盘。完整约束依赖受保护的 CI、可信策略来源及受控状态检查。进程隔离快照不等于系统沙箱;运行项目构建脚本有实际执行能力。 + +受保护 CI 还必须隔离可信验证端与被测项目的执行区:项目脚本不能改写可信策略、工具锁、验证器或最终状态凭据。执行产物是待核验输入;可信端验证输入/规则/覆盖并独立签发结果,不能直接上传项目脚本生成的绿色收据。无法提供这种隔离的环境不宣传不可自降级保证。 + +## 10. 迁移原则 + +先固定契约与真实样本,再逐工具实现适配;先对照验证,再切换宿主;所有原 stable 条目都完成验收后,才宣布全量迁移。迁移对照必须区分“旧实现 bug 修正”和“新实现回归”,不能要求复制旧豁免结果。 + +保存、提示等反馈事件不承担完整交付认证。真正的 Git pre-commit/pre-push 和 CI 优先绑定真实输入;宿主 Shell 预测仅作有边界的前置检查,不重新建造完整 Shell 解释器。 + +现有点前缀忽略策略保持:普通扫描不检查、不逐文件报告点前缀路径;入库安全和配置发现例外不变。报告只记录生效策略及汇总覆盖,不把已排除路径重新变成告警。若未来要检测 `.github/workflows` 等内容,需要单独确认策略变更。 + +新增持久工作区只精确排除生成器拥有的运行/记录文件,改由工作区 schema 与脱敏校验;不会把整个 `codeguard/` 变成免检目录,入库安全仍覆盖这些文件。任务文件只是投影,删除任务、勾选完成或编辑历史都不能替代真实 gate。 + +所有实施任务和待验证项统一维护在 [tasks.md](../../openspec/changes/introduce-rust-codeguard-cli/tasks.md)。 diff --git a/docs/rust-cli/coverage-and-acceptance.zh-CN.md b/docs/rust-cli/coverage-and-acceptance.zh-CN.md new file mode 100644 index 0000000..8d17faa --- /dev/null +++ b/docs/rust-cli/coverage-and-acceptance.zh-CN.md @@ -0,0 +1,167 @@ +# Codeguard Rust CLI 语言迁移与验收设计 + +状态:待执行。语言清单取自 `03ebb24` 的 [scripts/languages.json](../../scripts/languages.json),共 57 项:54 stable、3 planned。表中工具是旧注册声明或迁移调查入口,**不是已核验的新适配器能力**。不存在的 Rust 实现不计完成;只有 formatter 的旧 stable 也必须补足真实只读检查。 + +## 1. 每种语言的能力账本 + +每一 language/module 都生成 `lint/comments/cve/security/build` 五个槽位,分别记录 `implemented / gap / not_applicable`,并附 adapter、规则、工具版本、适用性证据及验收记录。 + +- `implemented` 只表示具备通过验收的实现;一次运行仍可能 incomplete。 +- `gap` 表示有义务但没有可靠能力,交付不能通过。 +- `not_applicable` 必须有结构性理由,例如完整依赖发现证明没有第三方组件,或配置文件语言没有独立编译步骤。缺工具、缺规则、扫描失败不能作为理由。 +- 外部 ecosystem 扫描可满足多个相关语言的 CVE 义务,但需要可追溯映射。纯 Markdown 的 build 可能不适用,其链接站点构建、文档规则仍按项目声明产生义务。 +- 宣传能力矩阵由同一注册数据生成;明确语言/类别/平台覆盖,不能只有“支持 57 种语言”的单一数字。 + +## 2. 全量迁移清单 + +波次仅是执行顺序:A 先跑通端到端,B/C 补齐现有 stable,P 保留原 planned 路线图。全量迁移必须完成 A/B/C;P 不强制虚构实现,但如果目标项目要求 P 对应能力,仍不能通过。 + +| language ID | 旧状态 | 旧 lint 声明/调查入口 | 波次 | 必须验证的差异 | +|---|---|---|---|---| +| java | stable | Maven verify | A | P3C、注释、安全、CVE 各自取证;Maven/Gradle/JDK 矩阵 | +| rust | stable | cargo clippy | A | workspace/features/targets、rustdoc、依赖审计 | +| typescript | stable | ESLint | A | JS/TS 别名、parser、tsconfig、type-aware 上下文 | +| python | stable | Ruff | A | 目标 Python、docstring 规则、依赖图 | +| go | stable | go vet | B | 多模块、build tags、依赖与文档规则 | +| csharp | stable | dotnet format --verify-no-changes | B | analyzer、solution、目标 framework、XML 文档 | +| kotlin | stable | Gradle detekt | B | Kotlin/JVM 版本、Gradle 项目与 KDoc | +| swift | stable | SwiftLint | B | Swift/Xcode 工具链、平台与文档规则 | +| php | stable | php -l | B | 语法检查不能代表完整编码规范;配置与 PHPDoc | +| ruby | stable | RuboCop | B | Ruby 版本、cop 配置、依赖与注释 | +| scala | stable | scalafmt --check | B | 格式之外的诊断义务及 Scaladoc | +| shell | stable | ShellCheck | A | sh/bash/zsh 方言分别建模;不得静默抛弃 zsh | +| dockerfile | stable | Hadolint | A | Dockerfile 变体、配置安全与镜像依赖范围 | +| yaml | stable | yamllint | B | 原生配置与 YAML 方言、模板文件 | +| elixir | stable | Credo | B | Mix project、依赖、模块文档 | +| css | stable | Stylelint | B | CSS 方言及插件,不能将任意预处理器当 CSS | +| c | stable | clang-tidy | B | compile_commands、目标平台、头文件上下文 | +| cpp | stable | clang-tidy | B | 编译数据库、模板、标准版本、头文件 | +| objc | stable | clang-tidy | B | Objective-C/Objective-C++ 与 SDK | +| dart | stable | dart analyze | B | SDK、analysis_options、pub 依赖 | +| vue | stable | ESLint | B | SFC parser、template/script/style 覆盖 | +| svelte | stable | ESLint | B | Svelte parser/plugin 与编译上下文 | +| astro | stable | ESLint | B | Astro parser/plugin、模板与内嵌语言 | +| solidity | stable | Solhint | B | pragma/compiler、静态安全和依赖 | +| terraform | stable | TFLint | B | provider/module 版本、IaC 安全与锁 | +| nix | stable | deadnix | B | dead-code 不代表完整安全;Nix 表达式和锁 | +| html | stable | HTMLHint | B | 模板方言显式适用,不能正则过滤后假装全覆盖 | +| sql | stable | SQLFluff | B | dialect、templater、项目配置 | +| graphql | stable | ESLint | B | schema/operation/plugin,上下文关联 | +| protobuf | stable | buf lint | B | module/workspace 与依赖 | +| markdown | stable | markdownlint-cli2 | B | 项目规则、代码块、MDX 等方言 | +| toml | stable | Taplo | B | TOML/schema 规则与项目归属 | +| haskell | stable | HLint | C | GHC extensions、项目依赖、文档规则 | +| ocaml | stable | ocamlformat --check | C | 原命令可执行性、Dune/opam、格式之外义务 | +| fsharp | stable | dotnet fantomas --check | C | F# 项目/工具锁与编译诊断 | +| perl | stable | Perl::Critic | C | Perl 版本、profile、POD | +| groovy | stable | npm-groovy-lint | C | Groovy/Gradle 方言与只读执行模式 | +| clojure | stable | clj-kondo | C | namespace/classpath、配置与文档元数据 | +| powershell | stable | PSScriptAnalyzer | C | PowerShell 版本、对象报告与 comment help | +| zig | stable | zig fmt --check | C | 格式与编译义务分离、Zig 版本 | +| nim | stable | nim check | C | 命令真实输入、项目入口与依赖 | +| crystal | stable | Ameba | C | Crystal 版本、shards、文档规则 | +| julia | stable | lint 为空;旧表有 format 能力 | C | 找到并验证只读检查器,不以 formatter 宣称 lint 完成 | +| elm | stable | elm-review | C | review 项目配置、依赖与锁定规则 | +| lua | stable | Luacheck | C | Lua 版本/globals、文档约定 | +| luau | stable | luau-analyze | C | Luau 类型环境,不能复用 Lua 语义 | +| pascal | stable | lint 为空;旧表有 format 能力 | C | 编译器/方言与只读检查能力补齐 | +| r | stable | lintr | C | R 版本、包项目、对象报告与文档 | +| cfml | stable | CFLint | C | CFML 引擎、模板和报告版本 | +| cobol | planned | 无 | P | 显式 capability gap,保留迁移记录 | +| vbnet | stable | dotnet format --verify-no-changes | C | VB analyzer、solution、XML 注释 | +| erlang | stable | Elvis | C | OTP/rebar、规则与文档 | +| arkts | planned | 无 | P | SDK/编译器可用性未知,不能标成功 | +| metal | planned | 无 | P | Apple SDK 能力未知,不能标成功 | +| liquid | stable | theme-check | C | 主题结构、Liquid/HTML 边界 | +| cuda | stable | clang-tidy | C | CUDA toolkit、host/device、编译数据库 | +| ansible | stable | ansible-lint | C | collections、playbook、依赖及安全规则 | + +旧表只有 lint 声明并不代表其它类别不需要做;每行必须附完整五槽位记录后才可以验收。`java`/`kotlin`/`scala` 等可共享 Maven/Gradle 依赖图,不能重复扫描造成重复 CVE,也不能漏掉另一构建根。 + +## 3. 必备场景库 + +每个新 stable adapter 至少含以下 fixture 类型;工具模拟只测执行边界,最终必须用真实二进制完成正反例。 + +| 编号 | 场景 | 验收 | +|---|---|---| +| F01 | 正确源码,正确配置与版本 | 完整、零阻断违规;实际目标非空 | +| F02 | 一条确定违规与对应最小修复 | 检出规则和正确位置;复检不再有该 finding | +| F03 | 缺命令/运行时、版本不兼容、坏配置 | incomplete,违规数不因工具故障虚增 | +| F04 | 原生非零但没有有效报告 | incomplete,不靠通用 rc 推断违规 | +| F05 | 有效违规后超时/崩溃/输出截断 | 保留 finding,同时 incomplete | +| F06 | exit 0 但空/畸形/陈旧/矛盾报告 | incomplete,不能假 PASS | +| F07 | 模块/方言/生成代码/配置继承 | 按批准规则准确覆盖;不能自行扩大或缩小 | +| F08 | 字符编码、特殊路径、空格、换行、非 UTF-8 | 身份可逆;无注入、无静默丢目标 | +| F09 | 内容或原生配置同大小同 mtime 替换 | 缓存失效;检查绑定实际输入 | +| F10 | 规则/工具/漏洞库更换 | 重新计算,不复用旧通过 | +| F11 | 50/51 文件、顺序变化、并发度变化 | findings 和 gate 等价;只有耗时变化 | +| F12 | 历史问题在未修改文件 | existing 分类,仍按相同规则阻断 | +| F13 | Agent 尝试 skipGate/env/suppression/提高阈值 | 不能获得新交付 allow,留下策略差异 | +| F14 | formatter 成功但无变化/失败后部分变化 | 分开执行成功、内容变化与已修复 | +| F15 | 原始诊断含凭据,日志目录/文件 symlink | 公共报告脱敏,目标不被意外覆盖 | +| F16 | index 与工作树不同、多 ref/non-HEAD push | 检查真实内容;不改 index | +| F17 | 所有发现为零但有一个必需类别缺能力 | exit 3,不能签发交付通过 | +| F18 | 点前缀源码/配置/.env | 普通面不扫描不逐文件报告;配置发现及入库安全例外保留 | +| F19 | Java verify 没绑定 P3C/Javadoc | 不得据 verify 成功声称全部质量义务完成 | +| F20 | 库过期/离线、模糊包匹配、未知严重度 | CVE 不伪造安全通过,也不伪称确定高危 | +| F21 | stdout/stderr 洪流、fork 子孙进程、取消 | 有界结束、清理、部分证据、无孤儿进程 | +| F22 | 同一请求 CLI/MCP/Hook/CI 输出转换 | 语义一致,协议退出码按各自表映射 | +| F23 | 多工具重复发现或同规则多次出现 | 归并可追溯,次数不丢失,不跨文件抵扣 | +| F24 | 项目脚本禁用 analyzer 或自定义命令仅 echo | 覆盖不足,不能被命令 exit 0 欺骗 | + +## 4. 误报与漏报的评测方法 + +采用两层语料:确定性合成正反例用于边界与回归;脱敏的真实项目样本用于规则适用性、方言、依赖与报告准确性。样本按语言、类别、工具版本和项目规模分层,保留独立 holdout,不能只挑工具最容易通过的项目。 + +每条裁定记录由人工或经过确认的 oracle 给出:应有 finding、规则依据、位置/依赖定位、应有执行状态、覆盖义务。争议样本单独统计,不靠模型投票充当真值。样本来源、版本、内容摘要、工具锁、裁定理由随报告归档。 + +| 指标 | 定义 | 防止指标失真 | +|---|---|---| +| Precision | TP / (TP + FP) | 附 finding 样本量;无发现时记不可估计,不能报 100% | +| False discovery rate | FP / (TP + FP) | 不与传统 FPR 混称 | +| Recall | TP / (TP + FN) | 使用已知真问题语料,保留漏报原因 | +| 工具故障误判率 | 被错误标成代码违规的故障样本 / 全部故障样本 | 单列,不与代码 FP 混在一起 | +| 完整率 | 完成且覆盖匹配的必需义务 / 全部适用必需义务 | 删除义务、增加排除会进入差异审计 | +| 假通过数 | incomplete、坏证据或已知阻断违规却 allow 的样本数 | 必须为零;未知不能算通过 | +| 稳定性 | 相同输入/工具/规则/库身份重跑结果一致比例 | 按规则定位不稳定来源 | +| 性能 | 同环境冷/热启动、p50/p95、峰值内存、缓存命中 | 对比覆盖/规则/工具完全一致 | + +建议发布门槛(目标,不是已达成指标):确定性必备场景零假通过、零工具故障误判;新增 adapter 必须检出其全部预置真问题;关键安全/CVE 回归零漏报;每个有足够裁定样本的 adapter/category 的 precision 95% Wilson 下界至少 0.98。没有足够样本的能力不得借全局平均值升级为充分验证;预览版本标明证据不足。 + +统计门槛调整属于人工可审查的验收标准变化,智能体不得为了完成任务调低。每个分层至少列出 n、TP、FP、FN、区间、规则数和项目数;CI 跑固定离线集,周期性真实工具/漏洞库运行单独跟踪,避免网络波动污染确定性回归。 + +性能先建立基线,再由维护者冻结绝对预算;本次不编造毫秒级承诺。验收首先要求优化前后发现集合、完整性、覆盖身份等价,才比较启动、扫描和缓存性能。 + +## 5. 发布与迁移验收矩阵 + +| 层次 | 证据 | 不可替代它的证据 | +|---|---|---| +| Rust 源码 | fmt/clippy/test、依赖方向、schema/fixture 测试 | 文件存在或编译成功 | +| 工具适配 | 固定工具链真实正反例、故障与修复复检 | mock 输出解析通过 | +| 全语言覆盖 | 54 stable 五槽位验收 + 3 planned 明示 | Java/Python/Rust/TS 演示 | +| Git/CI | 实际 index、多 ref push、受保护策略测试 | Shell 字符串静态识别 | +| 制品 | target triple、摘要/签名、安装及离线 doctor | Release 页面存在 | +| 插件接入 | Codex/ZCode/Kimi 每种事件真实调用新二进制 | manifest 语法正确 | +| 市场与安装 | source/lock/vendor/market/installed version/digest 对齐 | 本地单元测试通过 | +| 产品结果 | 固定语料 precision/recall/完整率与历史差异裁定 | “误报已经很低”的主观描述 | + +验收记录建议包含 `requirement_id / task_id / fixture_or_project / tool_lock / source_digest / expected / actual / artifact_refs / reviewer / date`。任务只有对应证据可定位且通过时才勾选。 + +## 6. 规范到任务追踪 + +| 规格能力 | 任务阶段 | 主要场景 | +|---|---|---| +| unified-cli-contract | S01、S02、S11 | F17、F22、CLI 错参/空输入 | +| native-tool-adapters | S05、S06、S07、S08 | F01–F08、F19、F20、F23、F24 | +| rulepack-governance | S04、S05、S12 | F07、F10、F12、F13、F18 | +| verdict-integrity | S02、S12 | F03–F06、F12、F17 | +| execution-kernel | S03、S10、S12 | F08–F11、F14–F16、F21 | +| language-gate-commands | S05–S08、S12 | F03、F07、F11、F19 | +| hook-protocol | S11、S12 | F13、F16、F22 | +| binary-distribution | S11、S13 | 摘要不匹配、无网络、版本回滚、三宿主实机 | +| remediation-workflow | S09、S10、S11、S12 | 重复发现归并、跨类别同步、attempt 预算、复检关闭/重开、租约冲突 | +| scan-scope-policy | S04、S09、S12 | 自有产物不递归扫描、用户源码仍检查、入库安全不豁免 | + +## 7. 完成边界 + +当前仅完成设计文件;上述表格无一构成适配器已实现证明。实施顺序与核对框统一位于 [OpenSpec tasks](../../openspec/changes/introduce-rust-codeguard-cli/tasks.md)。旧实现对照仅用于解释差异,不作为新系统必须复制错误的依据。 diff --git a/docs/rust-cli/remediation-workflow.zh-CN.md b/docs/rust-cli/remediation-workflow.zh-CN.md new file mode 100644 index 0000000..3259a19 --- /dev/null +++ b/docs/rust-cli/remediation-workflow.zh-CN.md @@ -0,0 +1,214 @@ +# Codeguard 持久问题与修复工作流 + +Codeguard 在项目根创建 `./codeguard/`,将“发现问题 → 形成任务 → 指导修复 → 原工具复检 → 关闭或重开”变为可恢复的工作流。目录既服务人,也服务智能体;检查器负责事实,工作流负责下一步,批准策略决定交付。 + +状态:待实现设计。本篇补充 [架构](architecture.zh-CN.md) 和 [技术方案](technical-design.zh-CN.md),规范为 [remediation-workflow](../../openspec/changes/introduce-rust-codeguard-cli/specs/remediation-workflow/spec.md)。已有 OpenSpec 管理 Codeguard 自身开发规格;项目内 `codeguard/tasks/` 管理被检查项目的修复任务,两者不能互相充当完成证明。 + +## 1. 为什么需要持久工作区 + +一次终端报错容易丢失上下文:智能体看不到此前尝试、修复是否改变了问题、是否缺环境,最终可能重复相同动作或绕过门禁。持久工作区应能回答六个问题: + +1. 当前还有哪些真实问题和未完成检查? +2. 这个问题来自哪个工具、规则和哪份内容? +3. 应该改源码、依赖、工具链,还是提交规则配置变更? +4. 下一步具体执行什么,允许改哪些文件? +5. 上次尝试为何失败,是否适合继续自动修复? +6. 用什么复检证据证明已解决,哪些交付义务仍未完成? + +## 2. 目录及版本控制 + +```text +project/ +├── codeguard.json # 已有配置入口,继续唯一 +├── codeguard.lock.json # 工具/adapter/rulepack 锁 +└── codeguard/ + ├── .gitignore # 忽略下面的本地运行数据 + ├── README.md # 本项目工作流说明与常用命令 + ├── workspace.json # 工作区 ID/schema/受管路径清单 + ├── findings/ + │ └── CG-/ + │ ├── finding.json # 脱敏、稳定的问题事实 + │ └── events/ + │ └── .json # 发现、尝试、复检、关闭/重开的审计事件 + ├── tasks/ + │ └── CG-.md # 面向人/智能体的修复任务投影 + ├── decisions/ + │ └── .json # 外部批准决策的引用,不是本地授权旗标 + ├── reports/ # 本地标准报告与复检报告 + ├── runs/ # 本地原始输出、进程证据 + ├── cache/ # 本地工具/扫描缓存索引 + ├── worktrees/ # 本地隔离扫描或修复副本 + └── state/ # 本地锁、领取租约、队列索引和恢复日志 +``` + +默认 `.gitignore`: + +```gitignore +/reports/ +/runs/ +/cache/ +/worktrees/ +/state/ +``` + +README、workspace manifest、脱敏 findings/events、任务文档与决策引用默认可入 Git,便于团队审查和交接;原始日志、缓存、临时源码与领取状态不提交。工具全局缓存可继续在用户缓存目录,本地 cache 只保存所需索引,不要求复制所有工具。 + +记录中不能包含密钥、原始环境、带令牌 argv 或不必要的源码片段。finding.json 保存最小定位、规则依据与不可变证据摘要;详细原文留本地私有 runs。另一台机器缺少本地证据时,可以根据稳定信息重新检查,不能凭仓库里的“已关闭”获得交付认证。 + +`workspace.json` 不是第二份质量配置。它只描述此工作区 schema、ID 和受管文件集合;质量要求仍来自 `codeguard.json` 引用的可信策略和 lock。任何普通文件中的“approved”都不构成授权。 + +项目尚未初始化时,check 的原始报告写入私有用户缓存,不自行创建未被 Git 忽略的 codeguard/runs;初始化后才按固定目录路由本地证据。现有文件进入受管范围需要精确的初始化计划,不能仅靠同名目录推定所有权。 + +### 防止 Codeguard 检查自己的运行产物 + +`codeguard/` 不是点目录,不能依赖现行点前缀规则排除。新增明确的“工具自身产物”范围契约:初始化清单绑定的 reports/runs/cache/worktrees/state 与生成 finding/task 文件不进入被测项目的普通源码发现,而由 Codeguard 自己进行 schema、路径和脱敏校验。 + +不得简单忽略所有名为 codeguard 的目录;已有 `codeguard/src/`、用户文件和未声明路径照常检查。受管路径必须来自经过校验的生成器清单,不接受 agent 任意扩张排除。入库安全检查仍覆盖拟提交的记录,不能借工具产物身份提交密钥。此新增规则须随本 change 明确更新 scan-scope-policy,现行点前缀规则保持不变。 + +## 3. 三种记录分别解决什么问题 + +| 记录 | 来源 | 用途 | +|---|---|---| +| RunReport | 一次真实检查 | 描述本次内容、执行、全部发现和未完成义务 | +| Finding/Blocker | 多次检查归并的稳定对象 | 追踪代码问题或工具环境阻塞的生命周期 | +| RepairTask | 从对象、规则修复知识和项目上下文生成 | 告诉执行者下一步、限制和验收方式 | + +代码违规创建 `kind=finding`,工具缺失/配置错误/数据库不可用创建 `kind=blocker`。blocker 可以解除后继续原检查,但不能把它伪装成“代码问题已修复”。纯运行噪声、重复日志不各自生成任务。 + +多个 findings 可由同一根因修复时生成一个任务组,逐项保留关闭条件;例如十个模块缺同一 JDK,先生成一个工具链准备任务,并使其依赖检查等待。不能为降低任务数而把不同规则的证据合并掉。 + +## 4. 状态机 + +```mermaid +stateDiagram-v2 + [*] --> open: 首次有效发现 + open --> ready: 修复目标和验收已明确 + open --> blocked: 缺环境或需人工决策 + ready --> in_progress: 领取租约 + in_progress --> awaiting_verification: 修复产物已准备 + awaiting_verification --> resolved: 绑定证据复检确认 + awaiting_verification --> ready: 同一问题仍存在 + awaiting_verification --> blocked: 复检未完成 + in_progress --> blocked: 重复失败或超出自动修复范围 + blocked --> ready: 前置条件恢复 + resolved --> open: 新内容重新检出 +``` + +`resolved` 必须有关闭原因:`code_fixed`、`dependency_fixed`、`environment_restored`、`target_removed` 或 `policy_resolved`。后两者不能被报告成代码已修复:真实删除需要 Git 内容/范围证据;规则正式变更需要可信批准修订和迁移映射。新增 ignore、移动到排除目录、未知规则映射或未验证删除不能自动关闭。 + +`accepted_exception` 是独立处置标签,保留未解决事实与例外到期条件;不是 resolved,不计入修复成功率。规则被新版批准策略移除时,旧记录可以 policy_resolved,但原证据及策略变更历史保留。没有新证据仅手工改状态、勾选 Markdown,最多表示执行者声称完成,正式状态仍 awaiting_verification。 + +任务完成与交付通过独立:关闭一项只证明那一项;`check all`/`gate` 仍需检查完整 contract,发现修复引入的新问题。全部任务被删除或勾选也不能生成 allow。 + +## 5. 稳定身份、事实源与协作 + +首次发现生成稳定 issue ID,后续使用工具/规则 ID、仓库相对路径、符号/内容锚点及类别匹配。CVE 使用 ecosystem/component/advisory/依赖定位。行号是展示信息,不能作为唯一身份。文件重命名使用 Git 关联与锚点匹配;匹配不确定时保留候选关系,不错误关闭旧问题。 + +`finding.json` 保存初始事实和身份;每次有意义的状态变化追加一个唯一 event 文件,携带 parent event ID、原内容/政策/工具身份、run evidence digest、actor、时间和变化原因。派生状态和 tasks Markdown 可以重建,不能作为新的事实权威。 + +事件文件是可审查历史,不是密码学授权或无需复检的证明。用户同权限可以修改 Git 文件,因此 CI 独立读取可信策略并复检代码。事件冲突、丢失父节点或相互矛盾的关闭事件产生 `reconciliation_required`,不采用“最后一个写入 wins”关闭问题。 + +`state/` 中使用跨进程锁和带到期的 task lease;claim/heartbeat/release 通过 CLI 完成。租约只防止同工作区重复劳动,不授权改质量规则。不同机器通过独立分支提交事件,合并后按父关系核对;远程全局调度不在初版保证内。 + +同一问题在同一内容/策略下反复扫描,只更新本地 last_seen 和 runs,不反复改 tracked finding/task 文件。发现集合、修复状态或重要上下文变化才生成持久事件,减少 Git 噪声。保存 Hook 不自动把一整份持久清单刷进用户工作树。 + +## 6. CLI 工作流 + +```bash +codeguard init . --dry-run +codeguard init . --apply + +codeguard check all . --format json +codeguard work sync . +codeguard status . +codeguard next . --format json +codeguard task show CG-abc123 . +codeguard task claim CG-abc123 . --owner agent-session-1 +codeguard task attempt start CG-abc123 . --owner agent-session-1 + +# 智能体按照任务修改源码或修复环境 +codeguard task attempt finish CG-abc123 . --owner agent-session-1 --outcome ready-to-verify +codeguard task verify CG-abc123 . --format json +codeguard task release CG-abc123 . --owner agent-session-1 +codeguard check all . --format json +``` + +`init --dry-run` 输出创建/冲突清单;`--apply` 只创建自有工作区文件,不覆盖已有目录内容或修改质量阈值。已存在同名用户目录时合并计划须精确到文件,冲突路径拒绝写入;不以 `--force` 吞掉用户文件。 + +`work sync` 消费与当前工作区绑定、schema 有效的本地报告,生成/归并记录与任务。默认按 run_id 游标幂等消费所有尚未导入的匹配报告;同一内容先 lint 再 CVE 的报告必须合并,不能只取最后一份。每份报告分别验证内容、政策、工具与检查范围;已过时的报告可保留历史,但不能导入为当前事实。部分报告只能增加有效发现/阻塞,不能据未出现自动关闭旧问题,完整报告也不能跨未覆盖范围关闭。 + +`status/next/task show` 只读;`next` 不自动领取,返回最适合推进的任务和选择理由。`claim/release/heartbeat` 只改本地 lease。`task verify` 运行该问题及受影响范围的真实检查并按证据追加事件;不等于全项目 gate。CLI 不提供无需证据的 `task close --force`。 + +`task attempt start/finish` 登记尝试 ID、动作指纹、执行者、前后内容/patch 摘要与结果;finish 的枚举是 ready-to-verify、no-change、failed、blocked,不能直接 resolved。受控 `fix` 自动写这些事件;自由源码修改由插件在修复回合前后调用。中断未 finish 的尝试在租约恢复时记 abandoned,不捏造成功;无修改和复检前失败也进入 prior_attempts/预算。agent 自述原因作为备注,实际内容变化由 CLI 观察,不能由 agent 自报成功关闭。 + +普通独立 `check` 默认写本地报告,不更新 tracked backlog;用户可显式 work sync。`init --apply` 的变更计划明确启用插件修复工作流,并固定受管写入范围;启用后,插件每次扫描必须执行“记录 run → 幂等 sync → 返回 status/RepairBrief”,包含保存反馈,不能只回显原始报错。相同问题不改 tracked 文件;新问题或状态变化才落事件。同步失败时保留 run 和原 gate,明确 backlog_update_failed 与恢复步骤,不假称任务已生成。 + +未初始化项目的插件首次反馈提供初始化计划及本次临时 RepairBrief,不静默写受管目录。启用持久工作流后,回合恢复先 status/next,修复前后记 attempt,复检后再 next。工作流错误不放宽检查;其状态单独报告,实际检查门禁不靠任务是否写盘决定。 + +## 7. 给智能体的 RepairBrief + +`next --format json` 应返回结构化、可直接执行的任务上下文: + +| 字段 | 内容 | +|---|---| +| identity | issue/task ID、workspace、源内容与策略身份 | +| kind / state | finding 或 blocker;当前状态、阻塞依赖 | +| evidence | 检查器、规则、位置/包、脱敏原生诊断、证据引用 | +| expected_behavior | 规则依据与最小可观察修复目标 | +| repair_recipe | 已版本化的工具/规则修复步骤、正例和反例 | +| allowed_changes | 本任务允许修改的源码/依赖范围;额外改动要重新规划 | +| constraints | 必需规则、阈值、测试与禁止降级项 | +| suggested_actions | 结构化 argv 或源码修复建议,说明作用和前提 | +| verification | 要运行的 adapter/规则/受影响范围与完成条件 | +| prior_attempts | 尝试摘要、失败原因、当前 patch 身份 | +| escalation | 何时停止自动重试、需要哪类决策 | + +recipe 来源于经版本化、可测试的规则包/技能;诊断和仓库文本属于不可信数据,不能将它们直接插值到可执行 Shell 或当成指令。AI 可以补充解释和补丁建议,但不能改写工具的 finding 事实。 + +任务 Markdown 是上述信息的人类投影,可保留明确分隔的人工备注区。勾选只能表达工作意图;生成器对受管区修改进行提示或重建,不据其降低策略。任务应有“问题 → 依据 → 修复步骤 → 复检 → 完成证据”,不能只有“运行某工具直到绿”。 + +## 8. 不重复报错、不逃逸的控制 + +```mermaid +flowchart TD + Scan[原生工具检查] --> Sync[归并发现与环境阻塞] + Sync --> Next[选择一个可推进任务] + Next --> Brief[提供规则依据 / 修复步骤 / 验收命令] + Brief --> Fix[智能体修复源码或环境] + Fix --> Verify[原工具复检与身份核对] + Verify -->|问题解决| Close[追加 resolved 证据] + Verify -->|新问题或原问题仍在| Update[更新任务与失败原因] + Verify -->|检查未完成| Block[环境阻塞任务] + Update --> Budget{有新的有效修复策略?} + Budget -->|有| Next + Budget -->|无或超预算| Escalate[明确需要的人工决策] + Block --> Next + Close --> Gate[完整 contract 交付门禁] +``` + +调度先修阻断大量检查的工具链前提,再按已批准严重度、依赖关系、可修复性选择源码任务。严重安全问题仍保持可见,不因前提任务排前而降低级别。相同 finding/content/patch 连续出现且无新信息时,不重复执行同一动作;记录尝试并提出具体恢复建议。 + +自动重试预算是执行保护,耗尽后转 blocked/needs_decision,不会让门禁通过。默认可设连续两次无进展后停止该任务自动重试,继续其它独立任务;预算值是运行选项,不能修改质量要求。需要用户时,问题应具体到缺失的工具安装授权、业务行为选择或有证据的规则缺陷,不能直接建议“跳过 Codeguard”。 + +“疑似误报”创建规则/adapter 缺陷调查任务,带最小复现与原生输出;在正式修复规则或批准策略变更之前,原检查状态不凭主观判断消失。工具误判可修复适配器并重新运行;不能借修改被测项目 tests 或忽略规则实现闭环。 + +## 9. 闭环验收 + +| 场景 | 必须观察到的结果 | +|---|---| +| 同一问题扫描十次 | 一个稳定 issue,运行记录可查,无十份重复任务 | +| 同内容先 lint 再 CVE 后同步 | 两份未消费报告均进入清单,重复同步幂等 | +| 缺 JDK 导致多个模块未检查 | 一个可复用前置 blocker,依赖检查完整列出 | +| 手工将任务勾为完成 | 无复检则正式状态不关闭,gate 不变 | +| 修复后工具超时 | awaiting_verification/blocked,保留原 finding,不自动关闭 | +| 未完成批次没再输出旧问题 | 不据缺失推断 resolved | +| 移动文件或新增 ignore | 无可证明等价覆盖时不能关闭旧问题 | +| 真正修复后同规则复检干净 | 记录内容/规则/工具/范围与 resolved 事件 | +| 已修问题重新出现 | 同一匹配 issue 重开,保留历史尝试 | +| 两个智能体同时领取 | 同工作区仅一份有效 lease;租约过期可恢复 | +| 修复在复检前失败或无修改 | attempt 与预算仍记录,next 不再推荐耗尽且无新信息的相同动作 | +| 不同分支出现冲突关闭事件 | reconciliation_required,不能取最后写入作为真值 | +| 删除全部 tasks/findings | 新检查仍发现真实问题;无法靠清空目录通过 | +| 受管目录含 secret 或用户源码 | secret 仍经入库安全;未声明源码正常扫描 | + +这套目录应成为智能体的修复工作台。最终判断始终回到真实内容、有效政策和原生检查器,文件记录帮助推进工作,不能替代检查本身。 diff --git a/docs/rust-cli/technical-design.zh-CN.md b/docs/rust-cli/technical-design.zh-CN.md new file mode 100644 index 0000000..181d0fe --- /dev/null +++ b/docs/rust-cli/technical-design.zh-CN.md @@ -0,0 +1,292 @@ +# Codeguard Rust CLI 技术方案 + +状态:待实现;命令、字段与文件布局是目标协议,不代表当前 Python CLI 已支持。规范性事实源为 [本次 OpenSpec](../../openspec/changes/introduce-rust-codeguard-cli/proposal.md)。 + +## 1. 命令模型 + +```text +codeguard [path] + [--format human|json|sarif] [--output PATH] + [--jobs N] [--timeout DURATION] [--offline] +codeguard plan [path] +codeguard detect [path] [--format human|json] +codeguard capabilities [language] [--format human|json] +codeguard doctor [language|all] [path] [--format human|json] +codeguard rules list [--format human|json] +codeguard config [path] [--format human|json] +codeguard fix [path] --category + <--dry-run|--apply> [--format human|json] +codeguard gate [path] [--format human|json] +codeguard tools [--format human|json] +codeguard tools install --lock PATH +codeguard init [path] <--dry-run|--apply> +codeguard work sync [path] +codeguard status [path] +codeguard next [path] [--format human|json] +codeguard task [path] [--format human|json] +codeguard task [path] --owner ID +codeguard task attempt [path] --owner ID [--outcome VALUE] +codeguard mcp serve +codeguard compat legacy-v1 <旧子命令与参数> +``` + +检查命令的 language 必填,path 默认当前目录。canonical ID 沿用现有注册表,别名由同一注册表解析;`all` 是发现所有适用语言及类别,不是对所有注册语言强制安装工具。未知语言在执行前返回用法错误。 + +`gate pre-push` 接受 Git hook 的 remote 参数与 stdin ref/OID 元组;`gate ci` 接收 CI 明确提供的不可变 commit/ref 集合。实际参数 schema 在 S02 固化,禁止通过默认 HEAD 推测所有推送目标。 + +```bash +codeguard lint java . --format json +codeguard comments java . +codeguard cve all . --offline +codeguard check all . --format json --output codeguard-report.json +codeguard plan check all . +codeguard doctor java . +codeguard fix java . --category lint --dry-run +``` + +检查命令默认不修改源码、配置、Git index,也不自动修复;构建产物和工具缓存写入隔离运行区。`plan` 不执行构建、扫描、下载或安装,只读取计划所需信息;无法静态确定的部分记为待解析。`doctor` 可执行有界版本探测,但不执行安装。`tools install` 是独立显式动作,不由扫描隐式触发。 + +`check` 的类别是 lint、comments、cve、security、build;项目不适用的类别须有结构性依据。build 的测试执行是独立参数:沿用现有 Java 静态构建默认不运行测试;报告必须写明 test_execution=false,不能声称测试通过。若批准策略要求测试,必须运行,CLI 不提供自动跳过路径。 + +持久修复工作区及新增命令的写入边界见 [修复工作流](remediation-workflow.zh-CN.md)。check 写本地报告,work sync 显式更新脱敏问题/任务;next 只读并返回 RepairBrief,task verify 依据复检追加状态事件,不能手工强制关闭。 + +## 2. 退出码、请求通过与交付通过 + +| 新 CLI 退出码 | 条件 | +|---|---| +| 0 | 请求选中的全部适用义务已完成且无阻断违规 | +| 1 | 请求已完成,存在阻断违规 | +| 2 | 参数语法、未知子命令/语言等用法错误;未启动检查 | +| 3 | 必需义务未完成:缺工具、无能力、坏配置、超时、无效报告、覆盖不全等 | +| 4 | Codeguard 内部故障或无法建立可信结果 | +| 130 | 用户取消;已有结果保留为部分结果 | + +聚合优先级:取消 > 内部故障 > 未完成 > 违规 > 请求通过;用法错误只在执行前产生。单检查执行故障不等于进程原生 exit=3;`raw_exit_code` 单独保存,进程未启动或由 signal 终止时可为 null,并有 `termination_reason`。 + +`lint java` 可以 exit 0,但报告 `delivery_gate.decision=not_evaluated`、`eligible=false`。只有 `check all` 或交付入口完成整份质量 contract,且内容与可信策略匹配时才可 `allow`。完整扫描无适用对象时可以 exit 0,但结果为 `not_applicable`,不能显示“全部已验证”,也不能用空输入签发交付认证。显式 `lint java` 找不到任何 Java 目标则 exit 3,避免目录选错变绿。 + +`check java` 完成全部 Java 义务;除非计划已证明该项目整份 contract 只含 Java 且所有跨语言依赖义务也覆盖,否则不签发全项目通过。实现初版可统一限制为 `check all`/`gate` 才计算交付 allow。 + +## 3. 数据模型与协议 + +`schema_version` 使用 major.minor 字符串,初始目标为 `1.0`;二进制版本、adapter 版本、rulepack 版本分别记录。消费者必须拒绝未知 major,允许兼容 minor 的可选扩展;禁止宽松读取未知枚举后按通过处理。 + +| 对象 | 关键字段 | 不变量 | +|---|---|---| +| CheckRequest | command、selection、root、content_source、operational_options | 用户选择与批准策略分开 | +| PolicyIdentity | source、revision、digest、authority | digest 证明一致性,不单独证明授权 | +| ContentIdentity | source_kind、repository/worktree identity、commit/index/tree digest、manifest digest | 不能仅用路径/mtime/size | +| CheckObligation | id、module、language、category、required、targets、rule_ids | 先有义务,再选择工具,缺工具不能删除义务 | +| CheckPlan | obligations、tasks、dependency_edges、resource_keys、scope_digest | DAG 无环;未执行任务有解释 | +| ToolExecution | executable identity、argv、cwd、env fingerprint、times、raw rc、artifacts | 原始敏感参数只进私有证据 | +| CheckResult | obligation_id、completion、reason、findings、execution_refs、coverage | findings 与 completion 正交 | +| Finding | id、native_rule_id、category、severity、message、location、evidence_refs、baseline_state | 保留原生严重度;不能由自然语言反推规则 | +| Coverage | expected、observed、unresolved、excluded_policy_digest、proof_kind | 数量相等不等于集合相等 | +| GateDecision | decision、eligible、blocking_finding_ids、incomplete_obligation_ids、policy_digest | 未完成不能 allow | +| RunReport | version、run_id、request、policy/content identity、results、gate、summary | human/json/sarif 派生自同一对象 | + +状态域:`completion=complete|incomplete|not_applicable`;`delivery_gate.decision=allow|deny|incomplete|not_applicable|not_evaluated`。内部故障另置 `run_status=internal_error`,交付决策保持 incomplete。 + +输出示意(**字段节选,不是可直接通过 schema 的完整样本**): + +```json +{ + "schema_version": "1.0", + "request": {"command": "check", "selection": "all"}, + "results": [ + { + "obligation_id": "java:app:lint:p3c", + "completion": "complete", + "findings": [{"id": "f-1", "native_rule_id": "native-rule-id", "severity": "error"}] + }, + { + "obligation_id": "maven:app:cve", + "completion": "incomplete", + "reason": "advisory_database_unavailable", + "findings": [] + } + ], + "delivery_gate": { + "decision": "incomplete", + "eligible": false, + "blocking_finding_ids": ["f-1"], + "incomplete_obligation_ids": ["maven:app:cve"] + }, + "exit_code": 3 +} +``` + +结构化 stdout 只能有一个 JSON 文档;进度、下载提示、工具输出写 stderr 或私有日志。`--output` 成功时将相同序列化报告原子写入目标文件,stdout 仍遵守所选格式;写入失败返回 3,保留已有结果。SARIF 转换保留 tool/rule/location,并用运行通知与 invocation 成功状态表达 incomplete,禁止导出零 findings 就隐去未完成;标准 JSON 始终是完整协议。 + +源位置使用仓库相对路径;文件名含非 UTF-8 字节时保留可逆编码标识和展示名,不用有损字符串进行身份比较。多位置、跨文件数据流或依赖发现允许 primary location 缺省,但必须存在包/模块定位。归并只对同工具或经过显式规则映射的等价发现进行,保留原始记录和次数;不同工具不能只按文案去重。 + +## 4. 核心类型、接口与依赖 + +建议技术栈为 clap、Tokio、serde/serde_json、thiserror;Git 初版使用受控 argv 调用已安装 Git 并校验字节协议。依赖版本与 MSRV 在建仓时实测固定到 Cargo.lock,不在设计文档引用浮动 latest 作为构建锁。 + +领域类型放 `codeguard-core/src/model/`,每个主要类型单文件;计划、门禁、策略为纯逻辑。核心协调器通过 ports 请求 I/O。接口设计示意: + +```text +Adapter.descriptor() -> CapabilityDescriptor +Adapter.discover(observations) -> ApplicabilityEvidence +Adapter.resolve(context, policy, toolchain) -> ResolvedCheck +Adapter.plan(resolved_check) -> AdapterPlan +Adapter.parse(raw_artifacts, termination) -> ParsedEvidence +Adapter.verify_coverage(plan, evidence) -> CoverageAssessment +Adapter.plan_fix(finding_set, context) -> FixPlan + +ExecutionPort.run(process_spec, cancellation) -> ToolExecution +SnapshotPort.capture(content_request) -> SnapshotLease +EvidenceStore.persist(raw_evidence) -> EvidenceRef +CachePort.lookup(validated_identity) -> VerifiedCacheEntry | Miss +``` + +这些是逻辑签名,不是待拷贝编译的 Rust stub。runtime 做观察和执行;适配器只能解释注入的数据、提出命令,不私下 `spawn`、联网或修改文件。core 根据计划协调任务,CLI 负责依赖注入。MCP 放在 cli 的入口模块,复用同一核心 API;不得通过调用自身命令拼另一套 verdict。 + +## 5. 工具适配器协议 + +每个适配器发布描述包含:adapter ID/version、语言及方言、检查类别、平台/运行时版本范围、所需配置、输入单位、报告格式版本、原生退出语义、缓存前提、修复能力、网络要求和资源锁。 + +稳定 ID 如 `java.p3c`、`java.checkstyle`、`java.javadoc`、`rust.clippy`、`python.ruff`、`typescript.eslint`。Node 工具可复用实现,但 Vue/Svelte/Astro/GraphQL 的 parser/plugin/context 必须分别验证,不能靠同一个 eslint 二进制宣布所有能力可用。 + +优先 JSON/XML/SARIF 等结构化报告。只提供文本的工具必须固定兼容版本、locale、语法及 golden fixtures;解析失败保留原文和未完成状态。禁止通用“包含 error 就失败”或“exit 1 就违规”的策略。比如 ESLint 的退出 1 与退出 2 表达不同类别结果,适配器必须按它的契约解释。[ESLint CLI](https://eslint.org/docs/latest/use/command-line-interface#exit-codes) + +有效 findings 与环境故障可能同时存在:保留已验证 findings,completion=incomplete。工具 exit 0 也必须核对报告、执行义务、被扫描对象与 freshness;有副作用的检查器不得把改写后的通过当作原始内容通过。 + +注册表只登记有证据的能力。`format` 存在但没有只读检查能力时,不能自动算作 lint;有原生 check/diff 模式时需证明无修改且报告可解释。无成熟注释检查器的语言可实现确定性 AST 规则,但需独立 capability/正反例验收;否则明确缺口。 + +## 6. Java 专项 + +Java 是第一条端到端实现线,不代表只支持 Java。适配计划必须区分以下义务: + +| 类别 | 工具/机制候选 | 验证要点 | +|---|---|---| +| 阿里规范 | 官方 P3C PMD 规则实现 | P3C/PMD/JDK 的兼容组合和实际规则加载 | +| 格式与通用规范 | Checkstyle;项目已有格式检查目标 | XML 结构、规则版本、源码级别、只读模式 | +| 注释规范 | Checkstyle Javadoc 规则、Javadoc/doclint | 公共 API 范围、参数返回、继承文档、Lombok/record/生成代码 | +| 安全静态分析 | 项目已接入并经验证的安全分析器,例如 SpotBugs 安全规则或规则扫描器 | 所需字节码、classpath、规则清单与报告完整性 | +| CVE | 经验证的 Maven 依赖扫描适配器 | 解析实际依赖图与版本,漏洞库身份和匹配证据 | +| 构建 | Maven/Gradle 的批准生命周期和目标 | 编译及必要质量任务是否真的执行;测试执行单独声明 | + +官方 P3C 包含 PMD 实现;本仓 Checkstyle XML 不应被重命名或包装成“官方 P3C 已通过”。具体兼容版本在真实样本矩阵中确定。[P3C 官方仓库](https://github.com/alibaba/p3c) + +Java 解析顺序:项目 wrapper 与声明运行时 → effective model/启用 profile → 模块图/源集/依赖 → 已绑定质量任务 → 缺失义务的受控适配计划。读取 effective model 可能执行构建扩展,因此不是纯静态 `plan` 的隐式行为,需进入执行阶段并留证据。 + +对于未配置质量工具的项目,使用已批准、已锁定的 rulepack 构造隔离执行配置;不改用户 POM/Gradle 文件。必要集成不能可靠构造时返回 `configuration_required`,提供具体补丁建议,不能自动永久加入或删去质量插件。`java.commands` 只能提供实际命令,仍需覆盖映射,不能以 `echo ok` 满足 P3C。 + +Maven 父子模块、dependencyManagement、profile、toolchains、annotation processor、测试源集和 Gradle 动态脚本都影响结论。静态模型不完整时保守扩大范围;扩大后仍不能证明规则执行,则未完成。已有 Java 影响闭包可迁移,但版本变动不得自动以 validate/help 替代未证明等价的 lint/CVE 义务。 + +## 7. CVE、注释、安全类别 + +CVE 的单位是依赖生态和解析后的组件图,不是源码后缀。Maven/Node/Python/Rust/Go 与通用扫描器按清单能力选择;原生缺失不得偷偷 fallback 成不同覆盖的通过。多个语言共享同一依赖图可以共享证据,但每份义务必须可追溯。 + +漏洞记录保留 CVE/GHSA/OSV 等原标识、别名、包坐标或 purl、版本、传递依赖路径、匹配方式、受影响区间、修复版本、严重度来源和漏洞库时间/摘要。只有模糊 CPE 匹配时,不伪装成精确包证据;待复核项仍可导致完整性不足,不静默丢弃。 + +缺 lockfile 不必一律失败:适配器若能解析并固化实际依赖图可继续,否则未完成。漏洞库过期、网络断开或数据库未验证不能产生安全 PASS;离线只使用满足批准 freshness 的本地库。未知严重度不能映射为 LOW 或删除:若策略无法比较则未完成;批准“所有已知漏洞阻断”时可直接判违规。 + +注释规则绑定语言语法及 API 范围。规则包应明确是否允许继承文档、接口/实现重复说明、生成代码排除和 test fixture 的专用规则;这些是预先确认的语义,不是扫描失败后的临时豁免。 + +安全分为源代码规则、配置/IaC、敏感内容及入库路径策略。`.env`/`*.pem` 等命中现行禁止入库模式时报告 `repository_policy_violation`,不得仅凭文件名宣称发现私钥;内容扫描的 secret finding 另有规则和脱敏证据。公共证书等例外需要正式策略变更,AI 不可自动放行。 + +## 8. 配置、规则包和工具链锁 + +保留 `codeguard.json` 作为项目入口,通过显式 schema migration 导入新协议。新增 `codeguard.lock.json` 绑定工具、适配器与规则摘要;不读取旧字段后静默丢弃。 + +| 配置平面 | 内容 | 覆盖规则 | +|---|---|---| +| 运行参数 | jobs、终端格式、日志位置、预算 | CLI > 环境变量 > 项目默认;不足只产生未完成 | +| 质量策略 | required、rulepack、severity、源集、批准排除、freshness、测试要求 | 可信组织策略及已批准项目修订;运行参数不可弱化 | +| 工具锁 | 工具版本/摘要、JDK、adapter、rulepack、下载来源 | 按 lock 验证;不使用 latest 自动升级 | +| 项目原生配置 | eslint/ruff/checkstyle 等 | 作为实际规则输入;必须与批准策略要求对齐 | + +`config explain` 显示每一有效值的来源与覆盖原因。原生配置中已有 disable/suppressions 必须列入策略解释和数量/摘要审计;不能把原生配置当作不可审查的隐藏降级入口。PR 同时修改代码与检查配置时,CI 使用可信基线策略评估政策差异;未经批准的新策略不能给本 PR 自行签发通过。 + +规则包目录设计: + +```text +rulepacks/// + manifest.json + rules.json + tool-configs/ + compatibility.json + fixtures/ +``` + +manifest 记录来源、许可、分类、规则严重度、工具兼容约束、内容摘要和变更说明。每个 native rule ID 映射稳定的 Codeguard rule ID;删除、改义或改变默认阻断级别属于可审查规则迁移,不靠扫描时随机选择。 + +默认选择“已存在且匹配锁的项目工具 → 已验证的受管缓存 → 匹配锁的系统工具”;版本不匹配即未完成。安装需要独立 `tools install`,提供下载清单、校验和与安装范围;扫描期间不能用 npx/pip/curl 偷偷安装。可能下载依赖的构建命令须在计划中说明网络模式与锁约束,`--offline` 不得联网;在线模式也不得自动升级规则或工具。 + +`--offline` 是执行约束,不能只转发工具的 offline flag。adapter 声明可验证的离线条件,运行时使用经平台验证的网络禁止环境执行可能联网的 wrapper/扩展;无法保证时在启动前返回 `offline_isolation_unavailable`,不声称已离线。安装沙箱或容器工具仍是独立准备动作,扫描不得隐式安装。验收必须包含会主动联网的真实子进程。 + +## 9. 执行器与资源控制 + +`ProcessSpec` 包含 executable、argv、cwd、受控 env overlay、stdin 模式、deadline、输出预算、资源键、网络策略声明和预期产物。默认 stdin=null;pre-push 输入先由 CLI 读取为结构化请求,不把宿主 stdin 交给扫描器。 + +Tokio 并发读取 stdout/stderr,有界缓冲加私有文件记录;超限必须标识不完整。取消或超时先请求停止,再终止进程组/Windows Job Object 并 wait/reap;只丢弃 Child 不等于可靠结束进程。[Tokio process 文档](https://docs.rs/tokio/latest/tokio/process/) + +任务 DAG 中相互独立的检查继续收集证据;同一模块构建目录、数据库更新或非并发扫描器通过 resource key 互斥。deadline 是整次请求预算,包含排队/探测/执行/解析/清理;子进程不能各自重置全部预算。 + +检查计划只读源码:准确内容物化到隔离目录,生成产物写独立区域;执行前后检查源码身份,意外变更导致未完成。禁止跟随路径逃逸 symlink;Git symlink/gitlink/LFS 对象按类型记录,无法获得所需真实源码时不当作普通文件通过。日志私有、原子写,证据索引保留摘要与保留期限;MCP/终端不默认输出密钥或原始环境。 + +受保护 CI 的可信协调/验证进程、策略、工具锁和最终状态凭据与被测脚本执行域隔离,采用只读挂载、独立身份或等效平台机制,不把签发凭据传入构建进程。执行区仅可写临时构建输出及待核验产物;可信端重新校验内容、报告来源、执行范围和规则覆盖后生成最终结果。单纯不同目录不能满足这个保证;不具备隔离能力时报告能力限制,不宣传抗任意脚本篡改。该要求不会将普通本地模式虚构成安全沙箱。 + +重试只限可识别的瞬时 I/O 故障,固定次数并保留全部尝试;不重试真实违规以期待随机变绿,不在重试中换规则、目录或工具版本。工具内部编程错误不无限重试。 + +## 10. 内容快照、缓存及增量证明 + +| 入口 | 权威内容 | +|---|---| +| 显式 check | 本次请求冻结的工作树清单和内容,检查后复核身份 | +| Git pre-commit | Git 实际传入环境下的 index,尊重 GIT_INDEX_FILE,包括部分提交 | +| Git pre-push | stdin 各 local ref/OID 与 remote ref/OID,支持多 ref、删除 ref、非 HEAD ref | +| CI | 调用方明示且解析为不可变 OID 的待交付内容;合并结果与分支头不能混用 | +| 宿主 PreToolUse | 有边界的预测内容;不声称等价于完整 Shell 执行 | + +Git 原始路径使用 NUL 协议,校验对象数量、类型、长度、模式和哈希;SHA-1/SHA-256 按仓库对象格式处理。index 冲突、多仓、初始提交、worktree、submodule、删除/重命名以及输入并发变化均有 fixtures。预测失败阻止认证并提示真实 Git 接入或拆分动作,不扫描另一个仓库充数。 + +缓存键至少包含:协议/adapter 版本、工具二进制与运行时、规则包、有效配置、目标内容与完整依赖输入集合、模块图、content_source、任务参数、平台、locale、CVE 库身份和 freshness。字节相同但 source kind/授权上下文不同也不得直接复用交付收据。 + +只缓存完整可验证结果,PASS 和真实违规都可缓存;incomplete/取消/内部故障不提供完成证据。完整缓存记录包含报告摘要和覆盖证明;缓存损坏视为 miss。交付入口不得消费软反馈缓存,可复用满足相同严格身份和可信来源的检查结果,再独立重算交付 gate。本地可写缓存不能作为受保护 CI 的可信外部证据。 + +增量算法先生成全量义务,再为每项选择执行或复用。无依赖隔离证明的 AST/类型/构建检查扩大到模块或项目;改变规则、编译选项、锁文件或父配置使相关闭包失效。不能以“文件未变”证明依赖它的检查结果未变。 + +## 11. 修复协议 + +```mermaid +sequenceDiagram + participant U as 调用方 + participant C as Codeguard + participant F as 原生修复器 + U->>C: fix --dry-run + C-->>U: 精确目标 / 内容身份 / 补丁或计划 / 副作用说明 + U->>C: fix --apply,既有修复授权 + C->>C: 核对目标身份与授权范围 + C->>F: 在隔离副本执行支持的修复 + F-->>C: 退出状态及变化 + C->>C: 校验范围,安全应用,再按相同策略复检 + C-->>U: 执行成功 / 实际变化 / 复检结果分别报告 +``` + +`fixed=true` 需要证明目标变化与该修复有关并完成复检;formatter exit 0 不够。应用 patch 前比较前置内容哈希;并发编辑时停止应用,不能覆盖用户修改。失败有部分变化时保留补丁及真实状态;只回滚自己可证明拥有且未被后续改动的部分。 + +不自动修改 lint 配置、阈值、忽略、测试或基线来修复 finding;CVE 升级引入行为变化时将依赖变更作为独立修复计划,完整复扫和受影响验证后才声明解决。 + +## 12. 插件、MCP 与分发 + +插件 lock 绑定 CLI version、target triple、artifact digest、协议 major、rulepack lock;安装时和运行前验证绑定。原生二进制是统一入口,但 Java/Node/Python 等扫描工具仍需要各自运行时,`doctor` 必须说明。 + +初始发布候选目标:macOS arm64/x86_64、Linux x86_64/aarch64、Windows x86_64;Linux libc/最低系统版本和每工具平台能力在 S10 实测固化。未验证组合不标稳定。下载原子落盘后切换;校验失败不得使用随机 PATH 版本或静默回退 Python,报告未完成。 + +MCP 工具参数映射到相同 CheckRequest,保留旧工具名时通过显式协议适配。宿主 Hook exit=2 可能表示阻断,而新 CLI exit=2 是用法错误,必须按报告语义映射,不透传数字。取消、超时、二进制不可用也要有宿主特定测试。 + +旧通道 `compat legacy-v1` 保持各旧子命令自己的退出协议:常见 check/CVE 为通过0、未验证1、违规2、用法3;混合优先级按旧入口原约定,不把 Dockerfile/CVE/check 的历史差异硬套成一张表。新报告与旧字段同时出现时以明确 protocol 标签解释,保留所有发现。兼容通道不能被新交付 CI 当作等价认证。 + +切换流程:协议与适配器测试 → 真实仓对照 → 显式指定新二进制 → 宿主/CI 按语义接入 → 全语言与平台验收 → 旧入口弃用。回滚只能回到满足相同严格契约的已验证 Rust 版本;若只能运行旧 fail-open 通道,则交付状态保持未认证,不能为恢复可用性伪造通过。 + +## 13. 可观测性与度量 + +每次 run/obligation/task/attempt 使用关联 ID,记录工具耗时、启动/解析/快照耗时、扫描范围、缓存原因、原始退出和未完成原因。日志默认本地,不收集源码遥测;如需远程统计需单独明确采集内容。 + +低误报评价至少同时展示:finding precision、漏报率、工具故障误判率、必需义务完成率、被排除/豁免比例、各类别覆盖、p50/p95 耗时及样本数。把规则关闭后 precision 变高视为覆盖退化。指标定义和真实验收见 [coverage-and-acceptance](coverage-and-acceptance.zh-CN.md)。 + +本技术方案没有声称已达到某个误报率或性能增益;这些必须由任务中的固定语料、人工裁定与真实工具运行证明。 diff --git a/kimi.plugin.json b/kimi.plugin.json index 4414ec0..6174bfc 100644 --- a/kimi.plugin.json +++ b/kimi.plugin.json @@ -1,6 +1,6 @@ { "name": "codeguard", - "version": "0.16.0", + "version": "0.16.1", "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/introduce-rust-codeguard-cli/.openspec.yaml b/openspec/changes/introduce-rust-codeguard-cli/.openspec.yaml new file mode 100644 index 0000000..74e9ae7 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-09-24 +goal: 通过统一 Rust CLI 恢复全语言传统静态工具质量守卫,降低误报且禁止自动降级门禁 diff --git a/openspec/changes/introduce-rust-codeguard-cli/design.md b/openspec/changes/introduce-rust-codeguard-cli/design.md new file mode 100644 index 0000000..02a2df0 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/design.md @@ -0,0 +1,69 @@ +# Design:统一 Rust Codeguard CLI + +## Context + +动机见 [proposal](proposal.md)。当前运行时是 Bash/Python、57 个语言注册条目、独立技能 vendor 与多宿主 Hook。源码基线 `03ebb24`;现有 OpenSpec 仍描述旧 fail-open、skipGate、基线豁免与旧退出码。本 change 是显式契约升级,不是对旧策略做行为保持的逐行翻译。 + +完整设计见 [架构文档](../../../docs/rust-cli/architecture.zh-CN.md)、[技术方案](../../../docs/rust-cli/technical-design.zh-CN.md);覆盖和指标见 [迁移验收](../../../docs/rust-cli/coverage-and-acceptance.zh-CN.md)。这些文档展开设计,本目录 `specs/` 是规范性事实源,`tasks.md` 是唯一任务状态。 + +## Goals / Non-Goals + +**Goals**:单一二进制与领域内核;独立的执行完整性和违规证据;不可由 AI 自行降级的质量 contract;全语言逐类别覆盖;可测试的工具适配与准确内容身份;渐进替换宿主入口。 + +**Non-Goals**:本次文档交付不建立 Rust 源码仓或发布产物;不重写所有原生分析工具;不采用 LLM 作为最终裁判;不承诺本地同权限环境不可绕过;不把动态 Shell 全解析作为交付正确性的基础。 + +## Decisions + +| 决策 | 选择及理由 | 考虑过的替代 | +|---|---|---| +| D01 workspace | 使用用户确定的四 crate,core 定义 ports,runtime/adapters 单向依赖 core | 单 crate 易混职责;每工具独立 crate 初期增加发布成本 | +| D02 执行权威 | Rust 统一编排,工具提供 finding,受控策略决定 gate | AI 重判缺可复现证据;通用 rc/文本判定丢语义 | +| D03 结果模型 | findings 与 completion 正交,混合结果全部保留 | 单 PASS/FAIL/UNKNOWN 枚举容易吞掉部分真实发现 | +| D04 完整性门禁 | 必需检查未完成不能交付;基线只分类 | fail-open/默认存量豁免延续用户明确否定的方向 | +| D05 覆盖 | 先建立完整义务,再选择执行/缓存;语言 × 类别 × 工具验收 | 按文件后缀或已注册命令计数无法证明能力 | +| D06 工具管理 | 显式 provision、固定锁、兼容组合测试 | 扫描时自动安装 latest 导致不可复现与运行副作用 | +| D07 配置权威 | 运行参数与质量策略分离;CI 使用可信已批准政策 | CLI/env 覆盖一切使智能体可自行关闭门禁 | +| D08 Git | 真正 Git 事件绑定 index/ref;宿主预测有明确边界 | 继续扩充 Shell 模拟仍不能覆盖任意程序间接调用 | +| D09 发布兼容 | 新协议明确版本,旧入口具名兼容且不作为新认证 | 同一个 rc 静默改义会破坏 CI/宿主 | +| D10 初版扩展 | 编译期适配器,版本化数据规则包 | 动态任意脚本插件会扩大信任和测试边界 | +| D11 测试执行 | 保留静态构建默认等级并明确报告,策略要求时执行测试 | 自动替用户将所有 verify 改为跑测试,超出已确认范围 | +| D12 修复工作区 | `./codeguard/` 持久存储脱敏问题、事件与任务,本地日志/缓存 Git 忽略,原工具复检关闭 | 仅终端报错缺少恢复上下文;自由 Markdown 勾选会制造无证据完成 | + +修复工作区详见 [持久修复设计](../../../docs/rust-cli/remediation-workflow.zh-CN.md)。状态、任务和历史帮助执行者推进,但最终 gate 独立检查源码与可信政策;任务记录不能成为新的跳过开关。 + +## Risks / Trade-offs + +- [严格门禁暴露工具环境欠账] → `doctor` 给出具体恢复路径,先完成工具链准备;不能通过 fail-open 消除欠账。 +- [官方 P3C、PMD、JDK 版本兼容] → 固定组合并运行真实样本;不把同名 Checkstyle 配置视为替代。 +- [跨语言 comments/security 能力差异] → 缺口显式记录;可新增确定性规则,不能把缺能力标成不适用。 +- [构建脚本和原生配置可执行/可降级] → 隔离运行、身份核对、可信策略 diff;快照不等于恶意代码沙箱。 +- [全量检查较慢] → 基于等价证明缓存和增量,不改变义务;性能预算以实测设定。 +- [同一 change 涉及未来多个仓] → 当前规格暂驻插件仓;未来 CLI 仓引用此事实源,迁移事实源须独立批准,禁止双写冲突任务。 +- [已有规格很多旧行为要求] → 在 delta 中给旧运行时明确协议范围;迁移后入口受 Rust 契约管辖,并更新宿主协议说明。 + +## Migration Plan + +```mermaid +flowchart LR + S1[契约和语料 S01-S02] --> S2[运行时与治理 S03-S05] + S2 --> S3[Java及首批工具 S06-S07] + S3 --> S4[全语言补齐 S08] + S3 --> S5[修复与宿主 S10-S11] + S2 --> SW[持久修复工作区 S09] + S4 --> S6[真实验收 S12] + S5 --> S6 + SW --> S6 + S6 --> S7[发布与规格同步 S13] +``` + +对照运行保留旧/新报告与差异原因,不直接将旧结论作为真值。已确认的历史错误行为需要新规范的正反例替代旧期望,其它兼容性用具名 legacy 测试保留。切换后不因 Rust 未完成自动回落旧 Python 放行。 + +回滚以满足相同严格契约的上一 Rust release 为目标;没有这种版本时保持交付未认证,反馈入口可恢复旧工具但不得显示新门禁通过。市场、安装和真实运行证据分层记录。 + +## Open Questions + +以下是实施阶段必须验证的技术参数,不改变上述需求与任务范围:P3C/PMD/JDK 兼容版本;各罕见语言可用的只读诊断工具;平台最低 ABI/MSRV;签名发布身份和新仓远程地址;代表性语料的确切项目清单及脱敏方式;逐工具性能基线。对应任务未完成前,不将候选参数写成发布承诺。 + +## Completion Boundary + +本次完成标准仅为架构、技术方案、覆盖验收设计、OpenSpec proposal/design/specs/tasks 的一致性和校验。所有代码、真实工具、宿主、发布任务保持未完成;不执行 apply/sync/archive。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/proposal.md b/openspec/changes/introduce-rust-codeguard-cli/proposal.md new file mode 100644 index 0000000..9ee477c --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/proposal.md @@ -0,0 +1,43 @@ +# Proposal:统一 Rust Codeguard CLI + +## Why + +Codeguard 的目标是调用传统静态工具,完成多语言代码规范、注释规范、依赖漏洞、安全和构建质量检查。当前实现把工具故障、基线豁免和宿主放行混在交付路径中,智能体容易通过降低检查要求消除报错;需要统一执行与证据契约,恢复“准确检查、完整交付”的产品边界。 + +## What Changes + +- 建立独立 Rust workspace `codeguard-cli/`,四个 crate 分别负责入口、领域契约、执行基础设施、工具适配;发布可执行文件 `codeguard`。 +- 提供 `lint/comments/cve/security/build/check `,CLI、MCP、Git 与宿主入口复用同一检查内核。 +- **BREAKING**:新 CLI 协议采用 0 通过、1 违规、2 用法错误、3 未完成、4 内部故障、130 取消;旧协议通过显式兼容入口保留,禁止根据数字猜测状态。 +- **BREAKING**:迁移后的交付门禁要求必需检查全部完成;工具故障不认定为代码违规,也不再允许被当作交付通过。 +- **BREAKING**:新流程不接受智能体自行设置 skipGate、降低阈值、增加 suppression 或使用存量基线豁免;基线仅分类问题。 +- 用版本化 rulepack、工具链锁、结构化报告、检查覆盖账本和真实样本评测降低误报;保留全部真实发现与不确定性。 +- 在项目内初始化 `./codeguard/`,将脱敏问题、环境阻塞、修复任务和复检事件持久化;原始报告与缓存由目录内 .gitignore 忽略,通过 next/task verify 指导智能体闭环修复。 +- 迁移当前注册表全部 57 个条目,逐项验收 54 个 stable 的实际能力,保留 3 个 planned 的真实状态;禁止用少数语言演示代替全量迁移。 +- 保留项目既有点前缀忽略及入库安全、配置发现例外;各检查类别明确适用条件和能力缺口。 + +## Capabilities + +### New Capabilities + +- `unified-cli-contract`:统一命令、选择范围、版本化报告、CLI/MCP 协议。 +- `native-tool-adapters`:工具适配、类别覆盖、Java 原生工具链和全语言迁移。 +- `rulepack-governance`:规则版本、可信策略、禁止自动弱化、例外和基线治理。 +- `binary-distribution`:二进制与插件版本绑定、兼容迁移、发布验收。 +- `remediation-workflow`:持久问题和待办、修复简报、任务租约、真实复检关闭与重开。 + +### Modified Capabilities + +- `verdict-integrity`:新增 Rust 双维结果和完整性交付门禁;明确旧入口的协议边界。 +- `execution-kernel`:新增 Rust crate 边界、进程生命周期、准确缓存与快照约束;显式隔离旧实现兼容要求。 +- `language-gate-commands`:区分旧流程与新 CLI 的前置条件、范围及工具故障语义。 +- `hook-protocol`:限定旧 fail-open/skipGate 契约,规定迁移后交付入口的阻断和宿主映射。 +- `scan-scope-policy`:为受管 Codeguard 产物添加精确自扫描边界,用户源码和入库安全不受影响。 + +## Impact + +未来代码归属:`codeguard-cli` 持有 Rust 内核、适配器、规则和协议;本插件持有宿主入口、运行时版本绑定;`codeguard-skills` 持有技能事实源。现有 `skills.lock.json` 管理边界不变。 + +当前交付仅为设计及执行任务,不建立 Rust 源码仓、不修改 Python 运行行为、不升级安装、不发布。正式规格以本 change 的 `specs/` 为准;实现完成前不 sync/archive。旧 OpenSpec change 的宿主副本治理另行保留,不并入或伪称已完成。 + +相关说明:[架构文档](../../../docs/rust-cli/architecture.zh-CN.md)、[技术方案](../../../docs/rust-cli/technical-design.zh-CN.md)、[语言迁移与验收](../../../docs/rust-cli/coverage-and-acceptance.zh-CN.md)。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/binary-distribution/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/binary-distribution/spec.md new file mode 100644 index 0000000..b41bd89 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/binary-distribution/spec.md @@ -0,0 +1,33 @@ +## Purpose + +定义统一 Rust 二进制与宿主插件的制品绑定、兼容入口、迁移和发布证据,使源码、发布版本、实际安装和运行能力能够分别验证,避免未验证回退产生假通过。 + +## ADDED Requirements + +### Requirement: Plugin execution SHALL bind a verified runtime artifact + +插件 MUST 绑定 CLI 版本、平台、制品摘要及协议版本;不匹配或不可用时 MUST 报未完成,不得随机使用 PATH 版本或自动回退旧 Python 产生通过。doctor MUST 明示原生扫描器的 JDK/Node/Python 等外部依赖。 + +#### Scenario: Runtime checksum is invalid +- **WHEN** 安装二进制摘要不符合插件锁 +- **THEN** 拒绝将其作为可信检查器,交付未完成 + +### Requirement: Compatibility SHALL be explicit and versioned + +旧 CLI/MCP/Hook 兼容 MUST 具名并按旧入口分别映射协议;禁止透传新退出码或隐式改变旧语义。新交付门禁 MUST 不接受 legacy-v1 的 fail-open 结果作为新认证。兼容移除 MUST 有发布迁移说明。 + +#### Scenario: Legacy caller expects findings to exit two +- **WHEN** 旧调用经 legacy-v1 接入 +- **THEN** 使用该旧入口的退出约定,同时不声称已满足新门禁 + +### Requirement: Release claims SHALL require layered evidence + +stable 发布 MUST 分别提供真实工具、平台、Git/CI、宿主安装与运行验收;不以编译、mock 测试、tag 或市场清单替代。全语言完成声明 MUST 满足语言迁移要求。回滚 MUST 不降低已确认的交付契约。 + +#### Scenario: Release artifact exists but host cannot run it +- **WHEN** GitHub release 有二进制但某宿主无法启动 +- **THEN** 该宿主验收保持未完成,不宣称已交付 + +#### Scenario: Only a legacy fail-open rollback is available +- **WHEN** 新运行时故障且没有符合新契约的旧 Rust 版本 +- **THEN** 交付保持未认证,不能以旧放行行为恢复绿色门禁 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/execution-kernel/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/execution-kernel/spec.md new file mode 100644 index 0000000..a24571d --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/execution-kernel/spec.md @@ -0,0 +1,69 @@ +## MODIFIED Requirements + +### Requirement: Existing entry points SHALL remain compatible + +显式 legacy-v1 兼容入口 MUST 保留原 CLI 子命令、四个 MCP 工具、五类 Hook 路径/JSON/退出约定及旧 Python 导入接口,除非另有弃用发布。用户配置 MUST 经显式转换,语言注册与外部受管技能所有权不变。 + +本规范中原有 Python 模块路径、顺序遇错终止、软缓存不供硬门禁、基线豁免、Java 轻量版本检查、fail-open 和旧退出码要求 MUST 限于 legacy-v1。Rust 新入口 MUST 遵守本 change 的统一义务、缓存证明、基线仅分类、独立任务继续及严格交付契约。其余原始证据、字节校验、内容身份、隐私与不修改用户 index 等正确性要求 MUST 延续。执行内核不得导入宿主 SDK。 + +#### Scenario: Legacy script invocation +- **WHEN** 旧 manifest 从原路径执行兼容脚本 +- **THEN** 使用显式旧协议,不静默改写数字语义,也不能签发新交付认证 + +#### Scenario: Rust execution encounters an independent failed task +- **WHEN** 一个任务失败且其它任务不依赖它 +- **THEN** 仍收集其它任务证据,失败依赖项保留未完成,不继承旧顺序短路作为全批通过依据 + +## ADDED Requirements + +### Requirement: Rust execution SHALL bound and close process lifecycles + +执行 MUST 使用字面 argv、明确 cwd/env/stdin、总预算及有界输出,记录真实终止原因。超时/取消/超限 MUST 停止并回收受控进程树,保留部分证据;同一构建目录的冲突任务 MUST 互斥。工具规则解释 MUST 不属于通用进程执行层。 + +#### Scenario: Child process outlives its parent +- **WHEN** 工具创建子孙进程后发生取消 +- **THEN** 在声明的平台保证内终止并回收整棵受控进程树,不能仅丢弃句柄 + +### Requirement: Rust snapshots SHALL bind the actual delivery input + +真实 pre-commit MUST 尊重 GIT_INDEX_FILE;pre-push MUST 读取每条 ref/OID 元组,不能假定 HEAD;CI MUST 绑定明确不可变内容。字节完整性、类型、哈希、路径和执行前后身份 MUST 校验。快照失败或源内容被检查器修改 MUST 未完成,不改真实 index;宿主预测 MUST 不冒充完整 Shell 语义。 + +#### Scenario: Push targets a non HEAD ref +- **WHEN** 用户推送的 local OID 与当前 HEAD 不同 +- **THEN** 检查实际 local OID 及指定推送范围,不能用 HEAD 通过替代 + +#### Scenario: Partial commit uses an alternate index +- **WHEN** Git hook 环境含 GIT_INDEX_FILE +- **THEN** 检查该 index,保留真实工作树及 index 内容 + +### Requirement: Rust cache reuse SHALL prove obligation equivalence + +缓存 MUST 绑定内容、依赖闭包、工具/运行时、adapter、规则、配置、平台、来源和 CVE 库身份/时效。只有完整有效结果可复用;硬门禁不得直接消费软反馈缓存,必须核验严格身份和可信来源并重算 gate。缺少依赖隔离证明 MUST 扩大扫描或未完成。 + +#### Scenario: Configuration changes with identical size and time +- **WHEN** 配置等长替换且 mtime 未变 +- **THEN** 缓存不命中,不能按元数据复用旧 PASS + +### Requirement: Rust fixes SHALL preserve ownership and verify outcomes + +fix MUST 区分 dry-run 与 apply,检查目标身份及范围;应用前置条件不符 MUST 不覆盖用户编辑。执行成功、观察到变化、复检确认修复 MUST 分开报告。修复 MUST NOT 自动降低规则、阈值、忽略或修改测试期待以消除违规。 + +#### Scenario: User edits during fix planning +- **WHEN** 修复计划生成后原文件发生变化 +- **THEN** 不覆盖用户内容,重新规划或报告未完成 + +#### Scenario: Formatter succeeds without changes +- **WHEN** 原生 formatter exit 0 且内容未变 +- **THEN** 只声明执行成功,不声称 fixed=true + +### Requirement: Offline and trusted CI execution SHALL enforce their declared boundaries + +离线执行 MUST 在可验证的不联网条件下运行,包括被调工具的子进程;无法提供条件时 MUST 在执行前报告未完成。可信 CI 的策略、工具锁、验证器和状态签发凭据 MUST 与被测脚本执行区隔离;项目产物 MUST 经可信端核验,不得让项目脚本直接签发或篡改认证。普通快照目录 MUST NOT 被宣传为满足该隔离。 + +#### Scenario: Wrapper attempts network access in offline mode +- **WHEN** 被调 wrapper 主动发起网络访问 +- **THEN** 声明支持的离线环境阻止访问;无法保证的平台拒绝开始并报告能力不足 + +#### Scenario: Project script tries to rewrite policy or receipts +- **WHEN** 被测项目脚本尝试改写可信策略、工具或最终状态 +- **THEN** 受保护执行边界拒绝写入,脚本生成的收据不能直接作为 gate 认证 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/hook-protocol/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/hook-protocol/spec.md new file mode 100644 index 0000000..c17f8e0 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/hook-protocol/spec.md @@ -0,0 +1,71 @@ +## MODIFIED Requirements + +### Requirement: The cheat-sheet SHALL document fail-open for uncaught exceptions + +`hooks/__protocol__.md` MUST 按运行时和事件区分:legacy-v1 未捕获异常保留 stderr 诊断和 exit 0 的旧兼容行为;普通保存/提示反馈不承担交付阻断。迁移到 Rust 严格交付模式的 Git/CI/PreToolUse 入口遇到内部故障 MUST 依宿主协议阻断并说明未完成,不能 fail-open。宿主不支持可靠阻断时 MUST 明示能力边界并依赖真实 Git/CI,不得声称该宿主已提供硬门禁。 + +#### Scenario: A hook hits an unexpected exception +- **WHEN** 显式旧兼容 Hook 顶层异常 +- **THEN** 记录诊断并按旧 exit 0,不能称为新认证成功 + +#### Scenario: A migrated delivery hook hits an unexpected exception +- **WHEN** 严格交付入口内部失败 +- **THEN** 按宿主阻断协议拒绝交付并报告未完成 + +### Requirement: Soft and hard gates SHALL share the skipGate escape and neither may fall back to scanning outside a git repository + +共享 `codeguard.skipGate` 的豁免 MUST 仅存在于 legacy-v1,维持旧计数和 Stop 汇总。Rust 交付 MUST 不接受此字段作为授权,例外遵循 rulepack-governance。非 Git 目录的 Git 事件 MUST 不回退扫描整个 workspace;普通显式 CLI 项目扫描不受该 Git 前提限制。 + +#### Scenario: SkipGate set, user asks to commit +- **WHEN** legacy-v1 设置了 skipGate 且消息触发提交意图 +- **THEN** 按旧约定退出和记账,并标明不提供新认证 + +#### Scenario: SkipGate set with a secret on the commit face +- **WHEN** 仓库已设 skipGate 且用户消息触发提交意图,待提交面含敏感模式文件 +- **THEN** 软门禁注入入库安全报告(非阻断);硬门禁在同一提交面 exit 2 + +#### Scenario: Rust agent sets skipGate +- **WHEN** 新交付入口发现同名 Git config 或链式设置 +- **THEN** 不作为质量豁免,仍按完整 contract 检查 + +#### Scenario: Prompt fires outside any git repository +- **WHEN** Git 提交提示发生于非 Git cwd +- **THEN** 说明没有目标仓,不扫描整个 workspace,也不称交付已通过 + +### Requirement: Fail-open uncertainty SHALL be visible + +legacy-v1 的 PreToolUse 放行工具故障 MUST 继续通过 additionalContext 明示未验证。Rust 交付入口 MUST 根据报告的 gate decision 映射宿主动作,必需 incomplete 与 deny 均阻断;普通 UserPromptSubmit/PostToolUse 反馈 MUST 保持可见未完成,不得宣称 all passed 或承担未实现的阻断。新 CLI exit 2 是用法错误,MUST NOT 直接当作所有宿主的阻断语义透传。 + +#### Scenario: A tool cannot run at the Git gate +- **WHEN** 旧入口缺工具 +- **THEN** 按旧协议 exit 0 且明确未验证 + +#### Scenario: Tool cannot run at a migrated gate +- **WHEN** 新严格入口缺必需工具 +- **THEN** 映射为宿主阻断,说明恢复工具链所需动作,不假称代码有错 + +### Requirement: Skip-gate bypass values SHALL be parsed strictly + +legacy-v1 的 `CODEGUARD_SKIP_GATE` MUST 只认 1/true/yes(大小写不敏感);0/false/空串不豁免,提示词保持原值域与传递边界说明。Rust 新流程 MUST 不将任何该变量值解释为授权,不能仅因保留旧环境变量而失去严格门禁。 + +#### Scenario: Zero and false values do not bypass +- **WHEN** 旧入口变量为 0 或 false +- **THEN** 旧门禁照常检查 + +#### Scenario: Accepted values bypass consistently +- **WHEN** 显式旧入口变量为 1/true/yes +- **THEN** 按旧协议豁免并审计,不签发新认证 + +#### Scenario: Rust receives an accepted legacy value +- **WHEN** 新入口继承 CODEGUARD_SKIP_GATE=true +- **THEN** 仍执行完整门禁,不把变量存在当批准 + +## ADDED Requirements + +### Requirement: Migrated host surfaces SHALL share the same report semantics + +CLI、MCP、宿主 Hook、真实 Git Hook 与 CI 对相同请求和证据 MUST 得到相同核心结果;差异仅为入口展示和协议映射。绑定二进制/协议失败 MUST 未完成;核心报告 MUST 不由宿主或智能体重写。迁移 MUST 为三宿主提供真实调用证据。 + +#### Scenario: Mixed findings and incomplete evidence reach three hosts +- **WHEN** 同一标准报告包含违规及未完成项 +- **THEN** 各宿主均保留二者,严格交付入口阻断,保存反馈不冒充交付通过 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/language-gate-commands/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/language-gate-commands/spec.md new file mode 100644 index 0000000..59ff705 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/language-gate-commands/spec.md @@ -0,0 +1,71 @@ +## MODIFIED Requirements + +### Requirement: Languages may declare a configuration prerequisite + +注册表 MUST 允许声明配置前置条件,避免采用不适合项目的默认规则。legacy-v1 缺配置时维持未接入/未验证且旧 Hook 放行语义。Rust 新入口 MUST 使用已批准 rulepack 或已接入的项目配置;如果不能可靠满足前置条件,必需义务为 incomplete,不能通过“未接入”自动免除。 + +#### Scenario: 已声明前置条件且项目未接入 +- **WHEN** legacy-v1 项目缺少声明配置 +- **THEN** 按旧协议说明未验证并兼容放行,不签发新认证 + +#### Scenario: Rust required configuration is missing +- **WHEN** 新入口没有项目配置且无法使用批准规则包建立可靠计划 +- **THEN** 返回 configuration_required 和未完成,不报代码违规、不满足交付门禁 + +#### Scenario: 已声明前置条件且项目已接入 +- **WHEN** 原生配置存在且满足批准策略 +- **THEN** 按有效配置执行,保留配置来源和覆盖证据 + +#### Scenario: 未声明前置条件的语言不受影响 +- **WHEN** 工具契约不要求额外配置 +- **THEN** 正常按已批准规则和工具锁执行,不凭空新增项目配置障碍 + +### Requirement: Gates in git repositories SHALL default to changed-file scope + +本条标题描述 legacy-v1:旧门禁 MUST 保留 delta 默认、gate_scope=repo、FULL_SCAN_EXCLUDES、index/预测暂存/HEAD 的历史输入规则与完整诊断;准确快照不复用旧软缓存。Rust 新门禁 MUST 建立全部适用必需义务,增量仅作为等价执行优化,不能默认豁免未修改文件中的问题。新真实 Git 入口 MUST 使用 execution-kernel 的实际 index/ref 契约。 + +#### Scenario: A committed legacy issue is untouched by a clean change +- **WHEN** legacy-v1 改动仅文档且项目检查确实不适用 +- **THEN** 明示无须执行,不伪称完成项目验证 + +#### Scenario: A new staged file introduces a problem +- **WHEN** index 内容违规但工作树已修好 +- **THEN** 检出 index 违规,不借用工作树通过 + +#### Scenario: A bad commit made outside the gate must not slip through the push +- **WHEN** 实际待推送内容违规但工作树已修好 +- **THEN** 检查待推送内容并保留违规 + +#### Scenario: Push with no resolvable upstream does not crash +- **WHEN** 旧入口没有可解析上游 +- **THEN** 保留 HEAD 全树回退,不因空基线跳过 + +#### Scenario: A chained commit-and-push takes the wider face +- **WHEN** 宿主可可靠建模同仓 commit→push +- **THEN** 覆盖拟提交内容和已有待推送内容;不可建模时明示未完成 + +#### Scenario: Rust optimization leaves an obligation unproven +- **WHEN** 增量计划没有执行某必需任务且无等价缓存证明 +- **THEN** 不能据 delta 干净签发交付 allow + +### Requirement: A toolchain crash SHALL be recorded as unverified, not as a lint failure + +检查器退出码 MUST 结合工具契约判定,工具故障 MUST 不认作代码违规。legacy-v1 保留 UNVERIFIED、CLI 1、Hook 可见放行。Rust MUST 将该义务标 incomplete、CLI 3,必需检查不满足交付;混合有效发现仍保留。不能统一把所有工具 exit 2 当配置错误,也不能把所有 exit 1 当违规。 + +#### Scenario: An npx tool crashes with exit 2 +- **WHEN** ESLint 因配置问题以 exit 2 退出 +- **THEN** 旧接口为 UNVERIFIED;新接口为 incomplete,均不能称为 lint 违规或通过 + +## ADDED Requirements + +### Requirement: Rust command plans SHALL preserve explicit build levels + +Rust build MUST 记录编译、静态检查和测试执行的区别。Java 默认静态构建保留不运行测试的等级;批准策略要求测试时 MUST 执行,智能体不得自行追加 skip。自定义命令 MUST 通过义务覆盖核对,不能只凭 exit 0 替代质量证据。 + +#### Scenario: Policy requires tests +- **WHEN** 项目批准策略要求构建时运行测试 +- **THEN** 新计划包含测试执行,运行参数不能静默排除 test + +#### Scenario: Default static build completes +- **WHEN** 默认静态构建成功但未运行测试 +- **THEN** 报告 test_execution=false,不声称测试通过 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/native-tool-adapters/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/native-tool-adapters/spec.md new file mode 100644 index 0000000..454daba --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/native-tool-adapters/spec.md @@ -0,0 +1,73 @@ +## Purpose + +定义传统原生工具在 Codeguard 内的适用性、执行、结果解释与覆盖证明,使语言注册、工具安装、诊断能力和实际检查完成不再被混为同一种支持状态。 + +## ADDED Requirements + +### Requirement: Adapters SHALL publish verifiable capability contracts + +每个 adapter MUST 声明语言/方言、类别、工具和运行时兼容范围、配置前提、输入范围、报告格式及退出语义。语言每个类别 MUST 明确 implemented、gap 或有证据的 not_applicable。formatter 存在 MUST NOT 自动证明 lint/comments 能力。 + +#### Scenario: A stable legacy entry has only a formatter +- **WHEN** 迁移 Julia 或 Pascal,原 lint 命令为空 +- **THEN** 补充经过真实验收的只读检查能力前保持能力缺口,不能据旧 stable 标签通过 + +#### Scenario: Shell dialect is unsupported +- **WHEN** 项目含 zsh,而选定 ShellCheck 不支持该方言 +- **THEN** 为其保留能力缺口或选择已验证的专用适配器,不静默删除义务 + +### Requirement: Native evidence SHALL be interpreted per tool contract + +adapter MUST 根据工具版本的报告及退出语义解析结果;MUST NOT 以通用非零、exit 1、关键词或模型主观判断替代。无效、截断、陈旧、与计划不符或内部矛盾的报告 MUST 产生未完成。有效部分发现 MUST 保留。 + +#### Scenario: Valid finding precedes a crash +- **WHEN** 工具先产生有效违规,随后发生配置或进程故障 +- **THEN** 保存该 finding 并记录 incomplete,不覆盖为纯工具错误或完整违规结果 + +#### Scenario: Tool exits zero with a stale report +- **WHEN** 报告属于此前内容或当前规则未执行 +- **THEN** 拒绝将其作为本次通过证据 + +### Requirement: Java checks SHALL prove each required native obligation + +Java MUST 分别建立 P3C、通用 lint、注释、安全、依赖漏洞和构建的适用义务。官方 P3C MUST 使用经验证的 P3C PMD 实现和兼容工具链;Checkstyle 配置名 MUST NOT 充当官方 P3C 的执行证据。Maven/Gradle 生命周期成功 MUST 结合实际绑定/执行证据,不能覆盖未运行的质量任务。 + +#### Scenario: Verify succeeds without quality plugins +- **WHEN** `mvn verify` 成功但未运行所需 P3C/Javadoc 检查 +- **THEN** 对应义务仍未完成,`check java` 不能显示全部通过 + +#### Scenario: JDK and PMD are incompatible +- **WHEN** P3C 规则运行因 JDK/PMD 组合失败 +- **THEN** 报告具体工具链未完成,不记为代码违规,不自动换规则或跳过 + +### Requirement: Comment and security categories SHALL retain their own evidence + +注释检查 MUST 根据语言与规则验证其确定性要求,不得以格式化代替。源代码安全、配置安全、入库策略和 CVE MUST 分开归类;仅命中禁止文件模式 MUST 报策略违规,不得无内容证据声称检测到真实私钥。未实现的语义能力 MUST 明示。 + +#### Scenario: Public API lacks required documentation +- **WHEN** 有效规则要求文档且源码缺少对应注释 +- **THEN** 原生或经验证的确定性规则输出注释违规,普通 formatter 成功不消除该义务 + +#### Scenario: Public certificate matches a forbidden path rule +- **WHEN** 文件命中现行禁止入库模式但没有私钥内容证据 +- **THEN** 按策略违规报告,不能将文件名命中写成已确认密钥泄露 + +### Requirement: CVE checks SHALL bind actual dependency and database identity + +Rust CVE 检查 MUST 绑定实际解析的组件版本、依赖来源、advisory 标识和数据库身份/时效。无可靠组件匹配、数据库过期、离线无合格库、缺失必要严重度时 MUST 保留发现与不确定性,不产生无依据的安全通过或确定高危。原生工具缺失 MUST NOT 静默换覆盖不同的通用工具。共享扫描 MUST 可追溯到全部被满足的义务。 + +#### Scenario: Offline database is too old +- **WHEN** 离线数据库超过批准的 freshness +- **THEN** CVE 未完成,不以零匹配判安全 + +#### Scenario: Report has no comparable severity +- **WHEN** 已知漏洞没有策略阈值需要的严重度 +- **THEN** 保留 finding;无法按策略判定时未完成,不预过滤 UNKNOWN + +### Requirement: Migration SHALL account for every legacy language entry + +迁移 MUST 逐项登记基线注册表全部 57 项,包括 54 stable 和 3 planned;前者完成五类别适用性和真实 adapter 验收前 MUST NOT 宣称全量迁移完成。planned MUST 保持显式状态,不能为凑数量创建空实现。注册表变化 MUST 重新做差异核对。 + +#### Scenario: Four major languages are implemented +- **WHEN** 仅 Java/Rust/Python/TypeScript 已可用 +- **THEN** 只能声明这部分能力完成,剩余迁移任务仍未完成 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/remediation-workflow/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/remediation-workflow/spec.md new file mode 100644 index 0000000..f889452 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/remediation-workflow/spec.md @@ -0,0 +1,93 @@ +## Purpose + +定义项目内持久的问题、环境阻塞、修复任务和复检事件,使智能体能够恢复上下文并逐步完成修复,同时保证任务文件、人工勾选和删除记录不能替代原生工具的验收证据。 + +## ADDED Requirements + +### Requirement: Projects SHALL have an explicitly initialized remediation workspace + +Codeguard MUST 支持 `./codeguard/` 及其中的 .gitignore、工作区清单、脱敏 findings/events、任务与决策引用;原始 reports/runs/cache/worktrees/state MUST 默认 Git 忽略。初始化 MUST 提供 dry-run/apply,不覆盖已有用户文件,不创建第二份质量策略。 + +#### Scenario: User already has files under codeguard +- **WHEN** 初始化遇到同名用户目录或冲突文件 +- **THEN** 给出精确合并/冲突计划,不强制覆盖,不全目录忽略用户源码 + +### Requirement: Findings and environment blockers SHALL produce distinct actionable tasks + +持久同步 MUST 区分真实 finding 与检查未完成的 blocker,按稳定身份归并重复发现、保留原规则与证据。部分报告 MUST 只能增加有效发现/阻塞,不能因缺失旧 finding 自动关闭。任务 MUST 包含问题依据、允许改动、修复步骤、前置依赖、复检条件及历史尝试。 + +同步 MUST 按 run_id 幂等消费所有未导入且身份匹配的报告,不能仅取最新一份而丢弃其他类别发现。过时报告只能成为历史,完整报告也不得跨未覆盖范围关闭问题。 + +#### Scenario: Ten scans report the same issue +- **WHEN** 同一内容/策略下多次检出同一个问题 +- **THEN** 保留一个稳定 issue 和本地运行历史,不生成十个任务或无意义 tracked 文件变化 + +#### Scenario: Missing JDK prevents several checks +- **WHEN** 多个模块因同一 JDK 缺失未完成 +- **THEN** 生成工具链 blocker 与依赖关系,不虚构多条代码违规 + +#### Scenario: Lint and CVE reports await import +- **WHEN** 同一内容先产生 lint 报告再产生 CVE 报告 +- **THEN** 同步纳入两者,重复同步不重复创建问题或事件 + +### Requirement: Closing a task SHALL require verified resolution evidence + +正式 resolved MUST 由真实复检或有批准依据的 target_removed/policy_resolved 事件产生,并绑定问题、内容、规则/工具、策略和覆盖身份。代码修复、依赖修复、环境恢复、政策处置 MUST 分开归因。手工勾选、编辑状态、删除记录、增加忽略或未完成报告中不再出现 MUST NOT 关闭问题或改变 gate。例外 MUST 保留未解决事实。 + +#### Scenario: Agent checks a task as done without verification +- **WHEN** 任务 Markdown 被标为完成但无有效复检 +- **THEN** 正式状态仍待验证,交付判定不变 + +#### Scenario: Verification times out after a patch +- **WHEN** 修复后的原检查器超时 +- **THEN** 原 finding 保留,转为待验证/阻塞,不自动 resolved + +#### Scenario: A resolved finding recurs +- **WHEN** 新内容再次检出同一可匹配问题 +- **THEN** 重开 issue 并保留此前修复历史 + +### Requirement: Repair briefs SHALL guide bounded progress without changing authority + +`status/next/task show` MUST 提供只读视图;next MUST 返回含依据、范围、步骤、约束、验证和历史尝试的 RepairBrief。recipe MUST 有受控来源,原生诊断和仓库文本 MUST 当作不可信数据。无进展重试超预算 MUST 阻塞该任务或请求具体决策,不能降低门禁;其它独立任务仍可推进。 + +MUST 提供 attempt 开始/结束协议,受控 fix 自动登记,自由修复回合由插件登记;尝试 MUST 绑定动作和前后内容摘要、结果及租约。复检前失败、无修改和中断均进入历史与预算,next MUST 不重复推荐耗尽且无新信息的动作。 + +#### Scenario: The same failed patch is attempted repeatedly +- **WHEN** 同一问题和补丁连续无进展达到预算 +- **THEN** 停止相同自动尝试,保留阻断并给出下一步诊断,不推荐跳过规则 + +#### Scenario: Tool output contains an instruction +- **WHEN** 诊断文本要求执行 Shell 或关闭规则 +- **THEN** 仅作为证据数据展示,不升级为动作授权 + +#### Scenario: Repair fails before verification +- **WHEN** 尝试没有代码变化或在调用检查器前失败 +- **THEN** attempt 仍持久计数,不能因没有 verify 报告而无限重复 + +### Requirement: Enabled plugin workflows SHALL connect scans to actionable briefs + +显式初始化并启用工作流后,插件扫描 MUST 经过保存 run、幂等 sync、返回状态/RepairBrief 的路径;仅有重复报错而没有任务指引 MUST 不作为完成的插件接入。未初始化时 MUST 提供临时简报与初始化计划,不静默写受管目录。同步失败 MUST 保留原结果并明示恢复动作;不能假称持久任务已生成或改变质量 gate。 + +#### Scenario: Enabled save hook finds a new issue +- **WHEN** 已启用的项目保存检查产生有效 finding +- **THEN** 创建或更新相应任务并返回下一步,重复同一 finding 不制造 tracked 文件噪声 + +#### Scenario: Backlog storage is unavailable +- **WHEN** 检查已完成但持久目录不可写 +- **THEN** 保留原 finding/gate,说明 backlog_update_failed 和恢复动作,不伪造任务路径 + +### Requirement: Workflow persistence SHALL preserve collaboration and gate independence + +finding 身份、事件父关系、证据引用与工作区 schema MUST 校验。并发领取 MUST 使用带期限租约和进程锁;不同分支状态冲突 MUST 显式协调,不能最后写入覆盖关闭。任务 Markdown MUST 是可重建投影;本地历史文件 MUST 不作为可信 CI 认证。完整 gate MUST 独立检查全部义务。 + +#### Scenario: All tasks are deleted +- **WHEN** 用户或智能体删除持久任务和发现 +- **THEN** 新检查仍按源码与批准策略产生发现,不能因此获得 allow + +#### Scenario: Two agents claim the same task +- **WHEN** 同一工作区并发领取一个未分配任务 +- **THEN** 只有一个有效 lease,另一个收到当前归属与恢复信息 + +#### Scenario: Branches disagree on resolution +- **WHEN** 合并事件存在相互矛盾的关闭状态 +- **THEN** 标记 reconciliation_required 并重新核对,不能自动选最后写入关闭 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/rulepack-governance/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/rulepack-governance/spec.md new file mode 100644 index 0000000..b0347a4 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/rulepack-governance/spec.md @@ -0,0 +1,57 @@ +## Purpose + +定义质量策略、原生配置、工具链和规则包的权威及变更流程,确保降低误报依靠准确证据而不是智能体自行关闭检查,并保留可复核的例外与历史问题记录。 + +## ADDED Requirements + +### Requirement: Quality policy SHALL be independent from operational options + +必需检查、规则、阈值、排除、测试要求及漏洞库时效 MUST 来源于批准策略。CLI/env 运行参数 MUST NOT 弱化它们;同一 PR 的策略修改 MUST NOT 在未经批准时为自身代码签发通过。config explain MUST 显示原生配置及 suppression 的有效来源和差异。 + +#### Scenario: Agent raises the severity threshold +- **WHEN** 智能体通过参数、环境或本地配置把批准阈值提高 +- **THEN** 新交付门禁不接受该弱化,记录策略差异而非通过 + +#### Scenario: A checker disables all native rules +- **WHEN** 原生配置改为禁用必需规则或以自定义空命令替代 +- **THEN** 覆盖校验不能满足义务,不以命令成功通过 + +### Requirement: Rulepacks and toolchains SHALL be versioned and locked + +rulepack MUST 有版本、规则来源/许可、稳定规则映射、内容摘要及工具兼容范围。检查 MUST 验证工具/运行时/配置/规则锁;扫描 MUST NOT 隐式安装、升级或改写项目配置。安装 MUST 是单独显式动作,离线检查 MUST 不联网。 + +#### Scenario: Tool version differs from the lock +- **WHEN** PATH 工具与锁不匹配 +- **THEN** 未完成并给出准备动作,不自动下载 latest + +### Requirement: Historical findings SHALL remain findings + +基线 MUST 只用于 new/existing 分类和趋势,不得默认豁免存量违规。所有阻断规则 MUST 对新旧发现一致生效。基线不可读或无法比较 MUST 不删除当前发现。 + +#### Scenario: A harmless edit leaves an old violation +- **WHEN** 完整检查在未修改源码中检出已有的阻断违规 +- **THEN** 可标 existing,但仍阻断,不因历史身份或 diff 位置豁免 + +### Requirement: Exceptions SHALL require independently verifiable authority + +例外 MUST 同时绑定规则、范围、内容身份、到期时间、原因和可核验的批准身份,不能采用永久无期限例外。agent 声称获批、skipGate、可写 JSON 或普通 Git config MUST NOT 本身构成授权。例外 MUST 保留原始发现及非通过检查状态;外部批准交付必须与普通 PASS 区分。文档 MUST 明示同权限本地进程不可提供不可绕过保证。 + +#### Scenario: Agent writes a local approval flag +- **WHEN** 智能体写 `approved=true` 或 `codeguard.skipGate=true` +- **THEN** 新交付门禁不接受为授权,不改变违规或未完成状态 + +#### Scenario: A real exception expires +- **WHEN** 经批准例外超过有效期或不匹配当前范围 +- **THEN** 不应用该例外,保留完整门禁要求 + +#### Scenario: Expiry is absent or content changes +- **WHEN** 例外没有到期时间或当前内容不匹配获批身份 +- **THEN** 拒绝应用例外,不能沿用此前批准关闭问题 + +### Requirement: Existing dot-prefix scope SHALL remain explicit + +本变更 MUST 保留普通检查面点前缀默认忽略、不逐路径告警,以及入库安全和配置发现两项例外。覆盖报告 MUST 记录有效策略与汇总范围,不能宣称排除内容已检查。新增扫描或放宽禁止入库策略 MUST 经独立策略变更。 + +#### Scenario: Dot configuration and secret coexist +- **WHEN** 项目含点前缀 linter 配置、普通点前缀源码和拟入库 `.env` +- **THEN** 配置仍发现、普通源码按策略忽略、入库安全照常运行,三者不混为同一排除 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/scan-scope-policy/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/scan-scope-policy/spec.md new file mode 100644 index 0000000..3461d76 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/scan-scope-policy/spec.md @@ -0,0 +1,17 @@ +## ADDED Requirements + +### Requirement: Managed remediation artifacts SHALL have a bounded self-scan policy + +初始化清单验证通过的 Codeguard 自有运行产物及生成问题/任务文件 MUST 从普通被测源码发现中排除,并接受工作区 schema、路径及脱敏校验。排除 MUST 精确到生成器拥有的路径集合,不得仅因目录名 codeguard 忽略整棵树;用户源码、未声明文件及入库安全检查 MUST 保留。此要求 MUST 不改变既有点前缀策略及两项例外。 + +#### Scenario: Managed files coexist with user code +- **WHEN** `codeguard/tasks` 有生成任务且 `codeguard/src` 有用户源码 +- **THEN** 生成任务走工作区校验,用户源码照常检测,不出现递归扫描运行副本 + +#### Scenario: A managed record contains a secret +- **WHEN** 拟提交的持久记录意外包含密钥 +- **THEN** 入库安全仍检查并阻断,受管产物不能豁免敏感内容 + +#### Scenario: Agent expands the ownership manifest +- **WHEN** agent 将普通源码路径添加进受管排除清单 +- **THEN** 生成器所有权校验不接受扩张,不能据可写 manifest 关闭扫描 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/unified-cli-contract/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/unified-cli-contract/spec.md new file mode 100644 index 0000000..f95b412 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/unified-cli-contract/spec.md @@ -0,0 +1,57 @@ +## Purpose + +定义 Rust Codeguard 的统一请求、语言选择、报告、退出码及多入口转换协议,使局部检查成功与完整交付认证具有明确边界,调用方无需猜测原生工具状态。 + +## ADDED Requirements + +### Requirement: Unified commands SHALL select explicit check obligations + +Rust CLI MUST 提供 `lint/comments/cve/security/build/check [path]`;`check` 包含全部适用类别。语言别名 MUST 来源于统一注册表,未知语言在执行前拒绝。部分命令 MUST 明示 selection,不得据局部通过声明项目全部通过。`plan` MUST 仅规划,不执行构建、扫描、下载、安装或修改源码。 + +#### Scenario: Java lint succeeds in a mixed project +- **WHEN** Java lint 完成,但项目仍有 Python、安全或 CVE 义务未在该请求执行 +- **THEN** 请求可成功,交付状态为 not_evaluated,不能宣称全项目通过 + +#### Scenario: User requests a plan +- **WHEN** 调用 `codeguard plan check all .` +- **THEN** 返回义务与待解析前置条件,不执行外部构建或扫描 + +#### Scenario: A language identifier is unknown +- **WHEN** 用户输入未注册的语言 ID +- **THEN** 执行前返回用法错误,列出规范 ID/别名,不启动检查器 + +### Requirement: Rust CLI SHALL expose a versioned exit contract + +Rust CLI MUST 使用 0 请求通过、1 违规、2 用法错误、3 必需义务未完成、4 内部故障、130 取消。执行后的聚合优先级 MUST 为取消、内部故障、未完成、违规、通过;已有发现 MUST 保留。原生退出码 MUST 独立记录,不直接透传作为 CLI 结论。 + +#### Scenario: Findings coexist with an unavailable scanner +- **WHEN** lint 有已确认违规且必需 CVE 工具不可用 +- **THEN** 返回 3,报告同时包含违规和未完成项 + +#### Scenario: Native tool exits with code two +- **WHEN** 工具原生退出 2 +- **THEN** 依据该工具契约生成结果,不将其直接解释成 Codeguard 用法错误 + +### Requirement: Reports SHALL preserve semantics across public formats + +报告 MUST 带 schema_version、请求选择、内容/策略/工具身份、逐项完整性、发现、覆盖及交付判定。human/JSON/SARIF/MCP MUST 来源于同一语义报告。结构化 stdout MUST 不混入进度或原始日志;公开结果 MUST 脱敏,私有原始证据单独存储。未知协议 major MUST 拒绝消费。 + +#### Scenario: SARIF contains no findings but a required task timed out +- **WHEN** 一个必需检查超时且没有有效 finding +- **THEN** SARIF 仍表达未完成,标准报告不得变成干净通过 + +#### Scenario: Tool output contains a credential +- **WHEN** 工具 argv 或诊断含凭据 +- **THEN** MCP 与公开报告不回显原文,保留可用的私有证据引用 + +### Requirement: Empty selection SHALL NOT certify delivery + +显式语言请求无目标 MUST 为未完成。全部发现后证明确无适用义务时 MAY 返回请求成功,但 MUST 标明 not_applicable,MUST NOT 生成交付 allow。缺失工具或 adapter MUST NOT 被当成无适用目标。 + +#### Scenario: Java command runs in the wrong directory +- **WHEN** 显式 `lint java` 未发现 Java 目标 +- **THEN** 返回 3 并说明无匹配目标,不能报告 Java 已通过 + +#### Scenario: Empty project has no applicable checks +- **WHEN** 完整发现证明项目无适用检查对象 +- **THEN** 标为 not_applicable 且 eligible=false,不签发交付通过 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/verdict-integrity/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/verdict-integrity/spec.md new file mode 100644 index 0000000..669c1b0 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/verdict-integrity/spec.md @@ -0,0 +1,37 @@ +## MODIFIED Requirements + +### Requirement: Verdicts SHALL preserve uncertainty across interfaces + +显式 legacy-v1 入口 MUST 保持 PASS、FAIL、UNVERIFIED、SKIPPED、PLANNED 及原聚合退出约定:通常 FAIL=2、无 FAIL 但有未验证/计划项=1、已验证或无须检查=0;各旧子命令的已记录差异由兼容层保持。仅真实成功检查允许 passed=true,无法验证不得触发自动修复或 all passed。 + +Rust 新入口 MUST 使用 unified-cli-contract 的版本化协议,MUST 将执行完整性与 findings 分离;混合未完成和违规保留两者并退出 3。CLI/MCP/Hook 转换 MUST 根据协议和结构化语义,不能根据同一个数字猜测通过。此版本边界同样约束旧 CVE、Dockerfile 等结果在新入口的映射。 + +#### Scenario: Configuration error survives MCP serialization +- **WHEN** 旧入口检查器因配置错误退出且没有有效违规结论 +- **THEN** 旧 CLI 退出 1,旧 MCP 返回 passed=false、UNVERIFIED 和原因 + +#### Scenario: Configuration error survives Rust MCP serialization +- **WHEN** 新入口同一检查因配置错误未完成 +- **THEN** Rust CLI 退出 3,MCP 保留 incomplete,不能通过协议转换生成 PASS + +## ADDED Requirements + +### Requirement: Required completeness SHALL gate Rust delivery + +Rust 交付 allow MUST 同时满足全部适用必需义务完成、覆盖匹配、内容与批准策略身份有效、无阻断违规。缺工具、无实现、坏报告、超时、缓存身份失败或输入变化 MUST NOT 成为 allow。未选择的类别与有理由不适用的类别 MUST 明确区分。 + +#### Scenario: No findings but a required tool is missing +- **WHEN** 已运行检查无违规但另一必需工具缺失 +- **THEN** 交付 incomplete,不制造代码违规,也不允许交付 + +#### Scenario: Finding remains in a historical file +- **WHEN** 必需规则检出历史文件中的违规 +- **THEN** 保留 finding 并按同一策略阻断,基线分类不能改变结果 + +### Requirement: Coverage SHALL not change with scheduling strategy + +Rust MUST 在调度前建立完整义务;分批、并发、缓存和增量 MUST 保持等价覆盖与判定,未执行义务必须具有有效复用证明,否则未完成。文件数量阈值 MUST NOT 改变历史问题是否阻断。 + +#### Scenario: A clean extra file crosses a batching boundary +- **WHEN** 目标从 50 个增加至 51 个文件但已有违规未变 +- **THEN** 原违规的门禁影响不变,不能因切换扫描方式而豁免或新增归因 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md new file mode 100644 index 0000000..e620a70 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -0,0 +1,300 @@ +# Tasks:统一 Rust Codeguard CLI + +本文件跟踪**后续实现**,当前文档交付不勾选任何实现任务。规格事实源为本 change 的 `specs/`;设计展开在 [design](design.md)。每项完成需要关联代码、测试和实际证据,不以存在文件、编译或模拟报告替代。 + +实施按红→绿→相关回归推进;每个批次先重新检查 Git/AGENTS/现有完成度。仓库创建、分支切换、工具安装、规则权威变更和发布按当时用户授权执行,不从本任务清单推断已经授权这些动作。不得修改本插件外部受管技能副本。 + +## 1. S01 契约与工程基线 + +依赖:无。覆盖:unified-cli-contract、native-tool-adapters、binary-distribution。 + +- [ ] 1.1 核对最新旧源码与 57 项清单差异,建立“保留正确行为 / 明确纠偏 / legacy 兼容”表;验收:每条差异关联 spec 和 fixture。 +- [ ] 1.2 在获准位置建立用户指定 `codeguard-cli/` 四 crate workspace,锁定 MSRV/依赖/Cargo.lock;验收:可运行 `codeguard --version`,无空适配器充数。 +- [ ] 1.3 建立 crate 依赖方向检查;验收:core→runtime、adapters→runtime、宿主 SDK 入 core 等反例均被拒绝。 +- [ ] 1.4 创建真实语料登记、脱敏和 oracle 格式,收录当前误判/假通过最小样本;验收:源码/工具/规则身份可复现,争议样本独立标记。 +- [ ] 1.5 定义 language×category×platform 能力 schema 和生成文档入口;验收:54 stable/3 planned 全覆盖,formatter-only 不能伪装 lint。 + +## 2. S02 CLI、结果模型与门禁 + +依赖:S01。覆盖:unified-cli-contract、verdict-integrity。 + +- [ ] 2.1 实现统一命令语法、canonical ID/别名及只读 plan;验收:错参无进程副作用、plan 不运行构建/扫描。 +- [ ] 2.2 定义请求/计划/报告/工具锁 JSON schemas 与兼容版本策略,提供完整正反例;验收:未知 major/非法枚举不按 PASS 消费。 +- [ ] 2.3 实现 findings 与 completion 双维聚合及新退出码;验收:混合违规/缺工具返回 3 并保留全部发现,取消/内部异常优先级正确。 +- [ ] 2.4 实现义务账本和交付决策;验收:类别命令、空目标、缺 adapter、未执行任务均不能签发项目 allow。 +- [ ] 2.5 实现统一 human/JSON/SARIF 渲染与私有证据引用;验收:结构化 stdout 纯净、SARIF 未完成可见、公开报告不泄露凭据。 +- [ ] 2.6 建立具名 legacy-v1 协议映射表和参数测试;验收:逐旧入口验证数字/混合优先级,不把兼容通过当新认证。 + +## 3. S03 运行时、快照与缓存 + +依赖:S02。覆盖:execution-kernel、verdict-integrity。 + +- [ ] 3.1 实现字面 argv/cwd/env/stdin 的受控进程执行;验收:空格/分号/命令替换字面值无额外执行,探测不消费 stdin。 +- [ ] 3.2 实现并发流读取、总 deadline、输出预算和私有原子日志;验收:洪流/编码错误/日志 symlink 场景无假完整、无越界写。 +- [ ] 3.3 实现 Unix 进程组与 Windows Job Object 的取消/终止/回收;验收:子孙进程、超时、Ctrl-C、排队取消有平台实测。 +- [ ] 3.4 实现任务 DAG、资源锁及依赖失败传播;验收:独立任务继续、共享 build 目录互斥、失败依赖未完成可见。 +- [ ] 3.5 实现工作树/index/ref 快照与原始字节校验;验收:SHA-1/SHA-256、坏批响应、特殊文件、symlink/gitlink/LFS 均明确处理。 +- [ ] 3.6 实现 GIT_INDEX_FILE、初始提交、worktree、多 ref/non-HEAD/删除 ref push 输入;验收:真实临时 Git 仓检查准确且 index 不变。 +- [ ] 3.7 实现执行前后内容身份复核和源码副作用检测;验收:并发编辑或检查器改源码导致 incomplete。 +- [ ] 3.8 实现严格缓存及义务等价证明;验收:同 mtime/size 内容替换、规则/依赖/工具/库变化必失效,软缓存不可直接认证。 + +## 4. S04 规则与策略权威 + +依赖:S02。覆盖:rulepack-governance。 + +- [ ] 4.1 实现运行配置与批准质量策略的独立解析及 config explain;验收:CLI/env 无法弱化 required/threshold/exclude。 +- [ ] 4.2 实现旧 codeguard.json 显式迁移和原生 suppressions 差异解释;验收:未知/损坏字段不静默丢弃或按默认通过。 +- [ ] 4.3 实现 rulepack manifest、来源/许可、稳定规则 ID、摘要和兼容锁;验收:内容篡改/不兼容组合不能执行认证。 +- [ ] 4.4 实现基线仅分类 new/existing;验收:未修改文件中的存量违规仍阻断,基线失败不消除 finding。 +- [ ] 4.5 实现可信政策修订和例外输入校验;验收:agent 自写批准、无到期、过期、错内容或错范围凭据不生效,批准例外不显示普通 PASS。 +- [ ] 4.6 保留点前缀默认策略及两项例外,生成汇总覆盖;验收:F18 全部成立,无未授权的新排除。 + +## 5. S05 工具适配框架与准备 + +依赖:S03、S04。覆盖:native-tool-adapters、language-gate-commands。 + +- [ ] 5.1 实现 capability/discover/resolve/plan/parse/coverage/fix 协议与编译期注册;验收:adapter 不绕开运行时启动进程或联网。 +- [ ] 5.2 实现工具解析、二进制身份、doctor 和 tool lock 验证;验收:wrapper/受管缓存/系统工具均匹配锁,缺工具返回恢复步骤。 +- [ ] 5.3 实现独立 tools install 流程、下载清单与校验;验收:普通 check/plan/doctor 不隐式安装;真实主动联网 wrapper 在离线隔离中被阻止,无法隔离则启动前 incomplete。 +- [ ] 5.4 实现报告产物 freshness、scope/rule 执行覆盖核对;验收:陈旧或伪造空成功报告不能通过。 +- [ ] 5.5 建立 adapter conformance harness;验收:F01–F10、F17 的有效与畸形报告可独立复用,mock 与真工具证据分层。 + +## 6. S06 Java 全链路 + +依赖:S05。覆盖:native-tool-adapters、language-gate-commands。 + +- [ ] 6.1 实现 Maven/Gradle/JDK/wrapper/profile/module/source-set 观察;验收:多模块、父 POM、动态未知范围与错误 JDK 有真实样本。 +- [ ] 6.2 确定并锁定官方 P3C/PMD/JDK 兼容组合,实现原生报告解析;验收:正反例证明规则实际加载,不能用 Checkstyle 名称替代。 +- [ ] 6.3 实现 Checkstyle/Javadoc 注释检查;验收:公共 API、参数返回、inheritDoc、record/Lombok/生成代码正反例。 +- [ ] 6.4 实现 Java 安全静态检查及 CVE 依赖图适配;验收:真实安全/CVE 样本、坏报告、库过期与模糊匹配分开记录。 +- [ ] 6.5 实现 build 等级和测试执行声明;验收:默认静态构建不谎称测试通过,要求测试的策略不可自动跳过。 +- [ ] 6.6 接入 Java 完整义务与影响闭包;验收:verify 未绑定质量任务、自定义 echo、50/51 文件、未修改调用方失败均不假通过。 +- [ ] 6.7 用代表性 Java 项目完成 check java/check all/doctor/修复前后对照;验收:每条旧新差异人工裁定并有产物引用。 + +## 7. S07 首批共享生态 + +依赖:S05;可与 S06 独立推进。覆盖:native-tool-adapters。 + +- [ ] 7.1 实现 Rust lint/rustdoc/build/依赖与安全义务;验收:workspace/features/targets 和所有 F01–F10 适用场景。 +- [ ] 7.2 实现 Python(python)Ruff/注释/依赖与安全义务;验收:目标 Python/配置继承/锁缺失解析有真实样本。 +- [ ] 7.3 实现 Node/TypeScript/JavaScript(typescript)adapter 基础与依赖审计;验收:parser/tsconfig/本地插件和 monorepo 范围正确。 +- [ ] 7.4 实现 Shell(shell)方言与 Dockerfile(dockerfile)/IaC 基础;验收:zsh 不静默丢弃、Hadolint 与配置安全报告不互相掩盖故障。 +- [ ] 7.5 实现 CVE 共享生态映射、依赖归并和数据库 freshness;验收:UNKNOWN/离线/原生缺失/重复依赖均符合规格。 + +## 8. S08 全语言补齐 + +依赖:S05,复用 S06/S07 已验收基础。覆盖:native-tool-adapters、language-gate-commands。 + +每组以 [57 项清单](../../../docs/rust-cli/coverage-and-acceptance.zh-CN.md) 为边界;每个语言必须有五槽位、工具锁、正反例、故障样本、平台与真实验收产物。每个语言分别拆为能力判定、lint/comments、其余类别与验收三项;每项内部有多工具时按实际 adapter 再细拆,不将未完成工具藏在汇总完成状态中。 + +- [ ] 8.1 为 go 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.2 实现 go 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.3 完成 go 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.4 为 csharp 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.5 实现 csharp 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.6 完成 csharp 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.7 为 kotlin 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.8 实现 kotlin 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.9 完成 kotlin 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.10 为 swift 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.11 实现 swift 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.12 完成 swift 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.13 为 php 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.14 实现 php 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.15 完成 php 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.16 为 ruby 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.17 实现 ruby 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.18 完成 ruby 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.19 为 scala 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.20 实现 scala 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.21 完成 scala 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.22 为 elixir 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.23 实现 elixir 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.24 完成 elixir 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.25 为 c 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.26 实现 c 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.27 完成 c 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.28 为 cpp 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.29 实现 cpp 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.30 完成 cpp 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.31 为 objc 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.32 实现 objc 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.33 完成 objc 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.34 为 dart 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.35 实现 dart 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.36 完成 dart 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.37 为 vue 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.38 实现 vue 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.39 完成 vue 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.40 为 svelte 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.41 实现 svelte 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.42 完成 svelte 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.43 为 astro 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.44 实现 astro 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.45 完成 astro 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.46 为 css 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.47 实现 css 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.48 完成 css 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.49 为 html 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.50 实现 html 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.51 完成 html 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.52 为 graphql 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.53 实现 graphql 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.54 完成 graphql 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.55 为 solidity 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.56 实现 solidity 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.57 完成 solidity 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.58 为 terraform 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.59 实现 terraform 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.60 完成 terraform 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.61 为 nix 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.62 实现 nix 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.63 完成 nix 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.64 为 sql 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.65 实现 sql 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.66 完成 sql 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.67 为 protobuf 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.68 实现 protobuf 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.69 完成 protobuf 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.70 为 yaml 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.71 实现 yaml 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.72 完成 yaml 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.73 为 markdown 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.74 实现 markdown 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.75 完成 markdown 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.76 为 toml 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.77 实现 toml 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.78 完成 toml 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.79 为 haskell 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.80 实现 haskell 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.81 完成 haskell 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.82 为 ocaml 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.83 实现 ocaml 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.84 完成 ocaml 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.85 为 fsharp 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.86 实现 fsharp 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.87 完成 fsharp 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.88 为 perl 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.89 实现 perl 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.90 完成 perl 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.91 为 groovy 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.92 实现 groovy 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.93 完成 groovy 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.94 为 clojure 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.95 实现 clojure 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.96 完成 clojure 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.97 为 powershell 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.98 实现 powershell 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.99 完成 powershell 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.100 为 zig 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.101 实现 zig 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.102 完成 zig 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.103 为 nim 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.104 实现 nim 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.105 完成 nim 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.106 为 crystal 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.107 实现 crystal 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.108 完成 crystal 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.109 为 julia 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.110 实现 julia 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.111 完成 julia 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.112 为 elm 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.113 实现 elm 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.114 完成 elm 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.115 为 lua 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.116 实现 lua 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.117 完成 lua 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.118 为 luau 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.119 实现 luau 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.120 完成 luau 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.121 为 pascal 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.122 实现 pascal 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.123 完成 pascal 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.124 为 r 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.125 实现 r 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.126 完成 r 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.127 为 cfml 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.128 实现 cfml 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.129 完成 cfml 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.130 为 vbnet 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.131 实现 vbnet 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.132 完成 vbnet 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.133 为 erlang 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.134 实现 erlang 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.135 完成 erlang 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.136 为 liquid 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.137 实现 liquid 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.138 完成 liquid 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.139 为 cuda 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.140 实现 cuda 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.141 完成 cuda 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.142 为 ansible 固化五类别适用性、候选工具/方言/版本及缺口;验收:每个槽位有实际依据,not_applicable 不得用缺工具解释。 +- [ ] 8.143 实现 ansible 的 lint/comments 适配与规则;验收:真实工具正确样本和违规样本、错误配置/版本/报告反例通过,格式化不能冒充注释检查。 +- [ ] 8.144 完成 ansible 的 CVE/security/build 适用能力及整体验收;验收:依赖生态映射和逐类别真实证据齐备,缺口未解决不升级 stable。 +- [ ] 8.145 为 cobol 保留显式 planned/gap;验收:项目要求该能力时返回未完成,不能用空实现充数。 +- [ ] 8.146 为 arkts 保留显式 planned/gap;验收:项目要求该能力时返回未完成,不能用空实现充数。 +- [ ] 8.147 为 metal 保留显式 planned/gap;验收:项目要求该能力时返回未完成,不能用空实现充数。 +- [ ] 8.148 机器核对最新注册表与全部验收条目;验收:54 个原 stable 五槽位与真实证据完整,3 planned 如实披露,无丢项/重复/别名漂移。 + +## 9. S09 持久问题与修复工作流 + +依赖:S02、S03、S04、S05;与后续修复和宿主接入共享本协议。覆盖:remediation-workflow、scan-scope-policy。 + +- [ ] 9.1 实现 init dry-run/apply 与工作区 schema/.gitignore/受管路径;验收:不覆盖用户文件,不重复建立质量配置,未初始化只用私有用户缓存存原始报告。 +- [ ] 9.2 实现自有产物精确范围规则与工作区校验;验收:运行副本不递归扫描,codeguard/src 用户源码正常检查,入库 secret 仍阻断。 +- [ ] 9.3 实现 finding/blocker 的稳定身份与重命名匹配;验收:十次相同扫描只有一个问题,行号变化不生成无意义重复,不确定匹配不误关闭。 +- [ ] 9.4 实现 run_id 游标、全量未消费报告幂等 sync;验收:先 lint 后 CVE 不丢问题,部分/过时报告不能跨范围关闭旧问题。 +- [ ] 9.5 实现 append-only 事件、父关系、状态机与可重建 Markdown 投影;验收:手改勾选无复检不关闭,缺父/分支冲突触发协调。 +- [ ] 9.6 实现任务依赖与 blocker 归并;验收:多个模块共缺 JDK 形成一个前置任务,各义务仍完整可见。 +- [ ] 9.7 实现 status/next/show 与 RepairBrief/版本化 recipe;验收:给出修复目标、范围、步骤和复检条件,诊断中的指令不可执行。 +- [ ] 9.8 实现 claim/heartbeat/release 跨进程租约;验收:同工作区只有一个有效领取者,过期和中断可恢复,不声称跨机器全局锁。 +- [ ] 9.9 实现 attempt start/finish、动作与 patch 身份、无进展预算;验收:复检前失败/no-change/abandoned 均计数,耗尽后不重复推荐同一动作。 +- [ ] 9.10 实现 task verify 的证据关闭与重开;验收:工具超时、加 ignore、移动到排除目录不自动 resolved,真修复有完整身份绑定。 +- [ ] 9.11 实现 code/dependency/environment/target/policy 的处置归因与例外标签;验收:policy_resolved 不计代码修复,例外保留未解决事实和期限。 +- [ ] 9.12 提供受控 fix 的 attempt 与验证事件接口;验收:noop、部分修改失败及并发编辑的事件样本分别记录且不生成假修复;真实 formatter 接线在 S10 完成。 +- [ ] 9.13 为插件提供启用后的 scan→sync→brief API;验收:新问题给下一步,重复无 Git 噪声,同步失败保留原 gate 并说明 backlog_update_failed。 +- [ ] 9.14 验证持久记录隐私、篡改和删除边界;验收:日志默认不入 Git,删除所有任务不影响真实 gate,跨机器无原始日志可重新复检。 + +## 10. S10 修复能力 + +依赖:S03、S05、S09 的事件/attempt 接口及对应已验收 adapter。覆盖:execution-kernel、rulepack-governance。 + +- [ ] 10.1 实现 dry-run 计划与隔离副本修复、变化清单;验收:只读请求不改源码,计划包含内容身份与副作用范围。 +- [ ] 10.2 实现前置哈希核对和受控 patch 应用;验收:用户并发编辑不被覆盖,路径逃逸不执行。 +- [ ] 10.3 实现同策略复检和修复归因;验收:noop/失败后部分修改/复检器副作用分别呈现,不虚报 fixed。 +- [ ] 10.4 将依赖升级和规则调整分开;验收:修 CVE 不自动放松阈值,修 lint 不关闭规则或更改测试真值。 + +## 11. S11 宿主与分发接入 + +依赖:S02、S03、S09,至少 S06/S07 已可真实运行。覆盖:hook-protocol、binary-distribution。 + +- [ ] 11.1 构建候选平台二进制,固化 ABI/MSRV/签名身份/校验清单;验收:各 target 运行 smoke,未测平台不标 stable。 +- [ ] 11.2 实现插件 runtime lock 和原子下载/切换;验收:摘要不符、缺二进制、离线均无静默 Python fallback。 +- [ ] 11.3 实现 MCP 相同核心 API 与版本化兼容工具;验收:F22、凭据脱敏与超时/取消正确。 +- [ ] 11.4 更新五类宿主入口及 hooks/__protocol__.md 的旧新模式表;验收:保存反馈与严格交付区分,skipGate/env 不能降级新模式。 +- [ ] 11.5 实现真实 Git pre-commit/pre-push 与 CI 入口;验收:alternate index、多 ref、非 HEAD、未知 Shell 边界正确。 +- [ ] 11.6 在独立 codeguard-skills 源仓更新调用与修复指引,再按 vendor 流程同步;验收:不直接编辑受管副本,不建议规避门禁。 +- [ ] 11.7 明确 legacy 弃用与安全回滚路径;验收:旧数字保持,但旧通过不能被新 CI 认证。 + +## 12. S12 质量评测与验收 + +依赖:S08、S09、S10、S11。覆盖:全部十个规格能力。 + +- [ ] 12.1 全量执行 F01–F24 适用矩阵与 schema/协议 golden 测试;验收:必备场景零假通过、零工具故障误判。 +- [ ] 12.2 冻结分层语料与统计门槛,执行独立 holdout;验收:按语言/类别公布 n/TP/FP/FN/区间,不用全局平均掩盖证据不足。 +- [ ] 12.3 完成真实旧新对照及人工差异裁定;验收:历史错误不作为新 oracle,兼容回归与意图纠偏分开。 +- [ ] 12.4 测量冷/热启动、p50/p95、内存和并发;验收:覆盖与 findings 等价后再比较性能,不以跳检提速。 +- [ ] 12.5 执行策略弱化、缓存污染、错误快照、报告缺项和修复越界测试;验收:本地边界如实记录,受保护 CI 不接受未批准策略;项目脚本无法改写可信政策、工具锁或签发收据。 +- [ ] 12.6 Codex/ZCode/Kimi 各做真实命令、MCP、保存与 Git 阻断流程;验收:绑定预期二进制,失效场景不能假通过。 +- [ ] 12.7 完整运行发现→同步→next→attempt→修复→verify→关闭/重开→全量 gate;验收:跨类别报告不丢失、失败可恢复、耗尽重试不逃逸、任务勾选或清空不改变真实门禁。 + +## 13. S13 发布、文档与规格收敛 + +依赖:S12 通过且获得相应发布授权。覆盖:binary-distribution、全部规格。 + +- [ ] 13.1 对照全部 requirement/scenario/tasks 建立最终证据索引;验收:缺证据的项目仍未完成,不为发布删除要求。 +- [ ] 13.2 完成 Rust fmt/clippy/tests、工具链回归、插件既有适用测试、vendor 离线与在线检查、OpenSpec strict;记录真实结果。 +- [ ] 13.3 形成 release notes、CLI/策略迁移指南和实际能力矩阵;验收:候选、stable、planned 与未验证平台表述一致。 +- [ ] 13.4 发布并逐层核对源码/tag/制品/插件 lock/market/installed runtime;验收:版本及摘要闭环,不以本地成功替代安装证据。 +- [ ] 13.5 完成受保护 CI 与已安装三宿主的最终运行复核;验收:实际检查链全部可追溯且未发生自动降级。 +- [ ] 13.6 只有全部实施验收完成后 sync/verify/archive 本 change;保留本次设计验证与后续运行验证的独立记录。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/verification.md b/openspec/changes/introduce-rust-codeguard-cli/verification.md new file mode 100644 index 0000000..f55d154 --- /dev/null +++ b/openspec/changes/introduce-rust-codeguard-cli/verification.md @@ -0,0 +1,38 @@ +# 本次设计交付验证 + +日期:2026-09-24。性质:架构与规格文档验证,**不是 Rust 实现、真实扫描器或发布验收**。 + +## 交付范围 + +- [设计文档入口](../../../docs/rust-cli/README.md):架构、技术方案、57 项语言迁移与验收、项目内持久修复工作流。 +- 本 change 的 proposal、design、10 个 capability delta 和可执行 tasks。 +- 持久工作区使用 `./codeguard/`,含自有 .gitignore;以问题、阻塞、任务和复检事件指导修复,规则和最终门禁不由任务文件决定。 + +## 实际检查 + +`openspec validate introduce-rust-codeguard-cli --strict --json --no-interactive` 已通过,无 issue。最初的校验发现 MODIFIED scenario 标题未保留,已恢复原 scenario 名称并显式限定旧协议范围,再次校验通过;未删除旧场景来绕过验证。 + +本次使用只读脚本核对 Markdown 本地链接、代码围栏、JSON 示例、语言 ID/状态、重复任务编号、proposal/spec 目录和规范任务追踪,最终退出 0、issues=[]。范围为 19 份 Markdown、40 个本地链接、57 个语言条目、10 个 capability、43 条 requirement、89 个 scenario、228 项未勾选实现任务。Mermaid 有 9 个代码块,已检查围栏与人工语法,未执行渲染器视觉验收。 + +`git diff --check` 与 `git diff --cached --check` 在本次文档路径范围均通过。任务初次语言 ID 机器核对发现三项仅写了大小写不同的展示名,已补充 canonical ID 并复核 57 项全部可追踪。 + +OpenSpec 的 planning artifacts 为 complete 只说明 proposal/design/specs/tasks 已齐备,不意味着实施完成。实现任务全部未勾选;不得据 status 的 isComplete 字段声称二进制可用。 + +## 独立读者检查 + +两轮只读审查识别并修订以下六项: + +1. 例外同时绑定范围、内容身份和到期时间,不允许无期限例外。 +2. 离线执行必须具备可验证的网络隔离条件,不能只转发参数。 +3. 可信 CI 验证端与被测脚本执行区隔离,防止项目脚本篡改策略或签发结果。 +4. work sync 幂等消费全部匹配未导入报告,不只取最后一次检查。 +5. 已启用插件工作流必须经过 scan→sync→RepairBrief;存储失败可见。 +6. attempt 开始/结束记录复检前失败和无修改尝试,避免预算失效。 + +语言任务已细分到具体 language ID;每种语言均有适用性、lint/comments、CVE/security/build 与真实工具验收要求,不用大组汇总掩盖缺项。 + +## 当前证据边界 + +源码观察基线为 `03ebb24`,读到的注册表为 54 stable/3 planned。工作过程中存在其他任务切换分支、暂存 release/manifest 文件;本次不创建或切换分支、不修改这些发布文件、不代替其他任务提交。文档最终实现基线须在实施开始时再次核对。 + +没有建立 Rust 源码仓、编译或执行新 CLI,没有安装工具、测量实际误报率、发布制品、同步市场或验收宿主;没有运行 openspec apply/sync/archive。既有代码全量测试不用于证明本次文档设计已运行。后续实现与发布证据按 tasks 逐项记录。 diff --git a/scripts/bump-plugin.mjs b/scripts/bump-plugin.mjs index 04f122a..80362d5 100644 --- a/scripts/bump-plugin.mjs +++ b/scripts/bump-plugin.mjs @@ -167,7 +167,12 @@ fs.writeFileSync(codexManifest, bumpCodex(fs.readFileSync(codexManifest, "utf8") // 写后回读:五文件版本必须与计划一致——写后不验等于没写(防半程假成功) for (const rel of plainManifestRels) { - const readback = JSON.parse(fs.readFileSync(path.join(repoDir, rel), "utf8")).version; + const data = JSON.parse(fs.readFileSync(path.join(repoDir, rel), "utf8")); + // 版本路径按清单形态分流:Claude 市场清单的版本在 plugins[0].version + // (bumpPlain 的文本替换同样只改那一处),其余清单在顶层 version。 + const readback = rel.startsWith(".claude-plugin/") + ? data.plugins?.[0]?.version + : data.version; if (readback !== newVersion) throw new Error(`${rel}: 写后回读 ${readback} != ${newVersion}`); } const codexReadback = fs.readFileSync(codexManifest, "utf8"); From 31d475c4d531982b5eb7e0a65809c6dcbd350a17 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Thu, 24 Sep 2026 02:55:48 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(security):=20skipGate=20=E8=B1=81?= =?UTF-8?q?=E5=85=8D=E5=8F=AA=E8=A6=86=E7=9B=96=E8=AF=AD=E8=A8=80=E9=97=A8?= =?UTF-8?q?=E7=A6=81=20+=20=E5=B7=A5=E5=85=B7=E9=93=BE=20PATH=20=E6=A0=B9?= =?UTF-8?q?=E5=9B=A0=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 豁免语义收窄:仓库配置/内联 -c/链式 skipGate 不再吞入库内容安全扫描, 恒执行;唯一完整逃生门为进程环境变量 CODEGUARD_SKIP_GATE(1/true/yes, 仅用户可设)。git_guard_application 暂存面计算移出 not-active 短路; prompt_application 豁免下仍注入安全报告;gate_directive 声明豁免范围 - 工具链 PATH 根因:paths.ensure_user_path 补 resolve 目录与 sysconfig scripts 目录(符号链接解释器机器不再误报工具未装);probe/run_gate 入口 先补 PATH 再采集缓存身份(check_identity 计入 PATH,事后改写使 ck≠after、 软缓存永不落盘);not_found 时以 python3 -m 试判未安装 vs 入口缺失 - 测试:新增 test_skip_gate_safety_scope.py 8 例;前缀内联豁免测试改写到 加固契约;架构白名单声明 paths/sys;全套件 626 passed - 文档:__protocol__/README×2/current-architecture 同步豁免范围 (doc-behavior-parity 14 绿);openspec 2026-09-24-harden-skip-gate-boundary 归档,hook-protocol 主规格 +1 ADDED +1 MODIFIED --- CHANGELOG.md | 7 + README.md | 2 +- README.zh-CN.md | 2 +- docs/current-architecture.md | 4 +- hooks/__protocol__.md | 9 +- .../design.md | 36 ++++ .../proposal.md | 27 +++ .../specs/hook-protocol/spec.md | 41 +++++ .../tasks.md | 22 +++ openspec/specs/hook-protocol/spec.md | 31 +++- scripts/check_architecture.py | 5 +- scripts/codeguard/gate.py | 4 + scripts/codeguard/git_guard_application.py | 27 +-- scripts/codeguard/prompt_application.py | 17 +- scripts/codeguard/reporting.py | 2 +- scripts/codeguard/toolchain.py | 21 ++- scripts/paths.py | 7 +- tests/test_git_guard_application.py | 5 +- tests/test_skip_gate_safety_scope.py | 174 ++++++++++++++++++ 19 files changed, 410 insertions(+), 33 deletions(-) create mode 100644 openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/design.md create mode 100644 openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/proposal.md create mode 100644 openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/specs/hook-protocol/spec.md create mode 100644 openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/tasks.md create mode 100644 tests/test_skip_gate_safety_scope.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 942554b..2dba32f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ - 测试可移植性与迁移债三批收口(P0 skipIf 守卫、requirements-dev、CI 矩阵 3.11/3.12/3.13;P1 shim 弃用通告、SCRIPT_ROLES 角色登记、双轨分工说明;P2 bump 歧义报错、init exit 0、README 计数锁定),615/615 单测与 run_all 144/144 全过。 - bump-plugin 写后回读支持 `.claude-plugin` 嵌套版本路径(`plugins[0].version`)。 - 汇流收口:test-portability 批次与起草中的统一 Rust CLI 变更骨架一并入 main。 +- skipGate 豁免范围收窄:代理可控豁免(仓库配置/内联 `-c`/链式)只覆盖语言门禁, + 入库内容安全扫描(密钥/凭据类)恒执行;唯一完整逃生门为进程环境变量 + `CODEGUARD_SKIP_GATE`(1/true/yes,仅用户可设)。软硬门禁与四份文档同批同步。 +- 工具链 PATH 根因修复:符号链接解释器机器上 `ensure_user_path` 补 resolve 目录与 + sysconfig scripts 目录;探活 not_found 时以 `python3 -m ` 试判「未安装」vs + 「入口缺失」;run_gate 入口先补 PATH 再采集缓存身份(check_identity 计入 PATH, + 事后改写会使 ck≠after、软缓存永不落盘)。 ## v0.16.0 — Claude 安装面与 Go CVE 生态 diff --git a/README.md b/README.md index ac4adf7..121a9dd 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Root codeguard.json may set gate_scope to delta or repo and customize extension/ 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). -The explicit escape hatch git config codeguard.skipGate true bypasses the hook gate and is recorded in session summaries. Shared hook state lives under CODEGUARD_HOME (default ~/.codeguard). +The explicit escape hatch git config codeguard.skipGate true bypasses the hook's language gate and is recorded in session summaries. It does not cover the commit-content safety scan (secret/credential path patterns): that scan runs regardless of config, inline `-c codeguard.skipGate=true` or chained escapes — only the process environment variable `CODEGUARD_SKIP_GATE` (1/true/yes, set by the user; inline assignment does not reach the hook process) suppresses it. Shared hook state lives under CODEGUARD_HOME (default ~/.codeguard). The Git gate statically inspects one readable shell-wrapper layer, including bare assignment, env, command and sudo prefixes; the same prefix rules apply to skipGate changes and one-shot bypasses. It does not execute or fully interpret shell scripts. ## External skills diff --git a/README.zh-CN.md b/README.zh-CN.md index 6693275..a7b4f25 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -120,7 +120,7 @@ python3 scripts/run_check.py --mcp /path/to/project 注册表含 **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)。 -显式逃生门 git config codeguard.skipGate true 会绕过钩子门禁,并在会话总结中记录。共享状态位于 CODEGUARD_HOME(默认 ~/.codeguard)。 +显式逃生门 git config codeguard.skipGate true 会绕过钩子的语言门禁,并在会话总结中记录。它不覆盖入库内容安全扫描(密钥/凭据类路径):该扫描不因仓库配置、内联 `-c codeguard.skipGate=true` 或链式豁免而跳过,恒执行;只有进程环境变量 `CODEGUARD_SKIP_GATE`(1/true/yes,仅用户可设,宿主内联赋值不会传入钩子进程)才能完整放行。共享状态位于 CODEGUARD_HOME(默认 ~/.codeguard)。 Git 门禁静态读取一层 Shell 包装命令,支持可解析的裸环境赋值、env、command 和 sudo 前缀;同一前缀规则也用于 skipGate 状态变更和单次豁免。它不执行或完整解释脚本。 ## 外部技能 diff --git a/docs/current-architecture.md b/docs/current-architecture.md index c4a3c58..6bb4b7e 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -34,7 +34,7 @@ Git 准确快照复用统一执行器的原始字节模式:路径列表默认 `run_fix` 的历史 `fixed` 字段仍表示 formatter 命令以 0 退出,供旧调用方决定是否复检;它本身不是内容变化证明。CLI `fix` 因而只报告 formatter 执行成功与文件变化未验证,不再把退出码 0 呈现为已确认修复。 Git 命令的仓库定位与拟暂存解析共用入口传入的 cwd;显式 `cd`/`git -C` 无法绑定 Git 工作树时,硬门禁给出目标未验证并阻断,不退回到调用者仓或无关子仓。未给出显式目标且调用目录非仓时仍保留 workspace 子仓兜底。静态命令解析不等于完整 Shell 执行模拟。 workspace 兜底产生的每个子仓是合成目标,暂存观察也绑定该子仓根目录;不能继续以非 Git 的 workspace cwd 解析,从而把脏子仓的已知违规变成未知放行。 -一条命令链中的 Git 副作用先绑定为仓库与操作的有序对,再按仓库聚合检查面:纯 push 不读取该仓未提交的 index,同仓 commit→push 则保留已有 HEAD 与拟提交内容的并集。仓库级、内联及链式 `skipGate` 均按目标仓与操作顺序生效;已存在的仓库配置也可被同链先行 `git config --unset` 取消。链式设置跨 `||`、`;` 或换行时无法证明生效,门禁要求拆分命令;写入其它文件的 config 设置也不构成本仓豁免。语法、仓库归属、间接脚本与拟暂存分析共用引号/转义感知切分;参数文本中的控制符不能合成豁免。解析能力仍限于受支持的静态 Shell 形态。 +一条命令链中的 Git 副作用先绑定为仓库与操作的有序对,再按仓库聚合检查面:纯 push 不读取该仓未提交的 index,同仓 commit→push 则保留已有 HEAD 与拟提交内容的并集。仓库级、内联及链式 `skipGate` 均按目标仓与操作顺序生效,但**只覆盖语言门禁**——入库内容安全扫描不被任何代理可控豁免覆盖,只有进程环境变量 `CODEGUARD_SKIP_GATE`(1/true/yes)可完整放行;已存在的仓库配置也可被同链先行 `git config --unset` 取消。链式设置跨 `||`、`;` 或换行时无法证明生效,门禁要求拆分命令;写入其它文件的 config 设置也不构成本仓豁免。语法、仓库归属、间接脚本与拟暂存分析共用引号/转义感知切分;参数文本中的控制符不能合成豁免。解析能力仍限于受支持的静态 Shell 形态。 一层 `bash`/`sh`/`zsh` 的 `-c`(含组合短选项)及可读脚本现在携带调用目录与正文进入同一 Git 操作计划;脚本内的 `git add` 可进入拟暂存范围,外层和内层 Git 操作按仓分别检查。识别到无法建模的间接 Git 操作、脚本内 `skipGate` 配置变更或跨解释器的暂存/提交关系时,明确以 Git 意图 UNVERIFIED 阻断,不借用调用者仓的检查结果。动态脚本、任意控制流及 Python/Node 中构造的 subprocess 仍未建模。 不含脚本或命令替换的直接静态命令链,暂存事件先按目标仓和执行次序记录,再投影到该仓最后一次 commit:其后的 add 不会倒算到先前提交或随后的 push;两个 commit 之间的 add 仍进入检查面。带一层间接脚本或 `$(...)`/反引号命令替换的链仍保守合并暂存意图,不声称已经建立跨层事件时钟。 直接 Git 命令、解释器入口及 `skipGate` 状态/内联豁免解析共用裸前缀归一化;可解析的环境赋值、`env FOO=1`、`command` 和无参数 `sudo` 不会遮蔽后面的 Shell 提交或豁免取消。带值 wrapper 参数和运行时生成命令仍不在静态保证范围内。 @@ -47,7 +47,7 @@ workspace 兜底产生的每个子仓是合成目标,暂存观察也绑定该 | `hooks/` | 宿主 JSON、会话事件、通知与退出协议 | 五类 Hook 将检查编排委托应用服务;入口保留旧导入兼容面及 fail-open 协议 | | `scripts/codeguard/check_application.py`、`language_check.py` | 语言选择、检查与修复请求、MCP 工具分发 | 不导入 MCP SDK;修复结果公开投影不复制内部原始命令/输出;旧 `run_per_language.py` 仅再导出 | | `startup_application.py`、`session_application.py`、`prompt_application.py`、`git_guard_application.py`、`save_application.py` | 五类 Hook 的盘点、状态消费、软提示、Git 守卫及保存反馈编排 | 返回结构化结果,不打印或调用宿主通知;Hook 控制输出、通知与 fail-open。Stop、软提示与保存反馈在 stdout 刷新后确认状态;Git 已知拦截在输出故障时仍拦截但不缓存。并发或交付重试可能重复反馈,不得丢掉未交付记录 | -| `gate.py`、`gate_checks.py`、`repository_policy.py`、`baseline.py` | Git 门禁、单语言结果、安全规则及有证据的存量比较 | 单个语言 worker 异常只标该语言 UNVERIFIED,保留其它结论;安全路径快照失败明确报告未验证,不能抹掉已确认拦截。只有准确基线失败且逐条诊断的文件归属、内容及次数覆盖当前结果才可豁免 | +| `gate.py`、`gate_checks.py`、`repository_policy.py`、`baseline.py` | Git 门禁、单语言结果、安全规则及有证据的存量比较 | 单个语言 worker 异常只标该语言 UNVERIFIED,保留其它结论;安全路径快照失败明确报告未验证,不能抹掉已确认拦截。只有准确基线失败且逐条诊断的文件归属、内容及次数覆盖当前结果才可豁免;入库内容安全扫描恒执行,不受 skipGate 类代理可控豁免覆盖 | | `java_build.py`、`java_impact.py`、`java_planning.py` 等 | 构建读取、纯影响闭包、命令选择与环境观察 | 默认保留跳过测试的行为;复杂构建保守扩大检查范围 | | `cve_reports.py`、`cve_policy.py`、`cve_scanners.py`、`cve.py` | 漏洞报告、阈值、进程适配与复扫编排 | 无有效结构化报告就没有安全通过结论 | | `dockerfile_reports.py`、`dockerfile.py` | hadolint/Trivy 结构化证据与逐文件扫描 | `dockerfile_security.py` 只解析参数和呈现报告 | diff --git a/hooks/__protocol__.md b/hooks/__protocol__.md index 7fdf605..4549211 100644 --- a/hooks/__protocol__.md +++ b/hooks/__protocol__.md @@ -93,7 +93,8 @@ if __name__ == "__main__": 放行,归一化修复被 roots 层击穿(0.8.2 实测:`git -C`/`FOO=1 git push`/ `sudo git push` 三种形态全部穿透);并且 2. 仓库级 `git config codeguard.skipGate true` 未设置(命中豁免时记账一次, - `gate_lib.record_skip_event`,Stop 汇总可见);并且 + `gate_lib.record_skip_event`,Stop 汇总可见;**该豁免只覆盖语言门禁**—— + 入库内容安全扫描不受其覆盖,见下方一致性约束);并且 3. 实际跑 linter 后存在非 skipped 的 failures。 出口内容(`gate_lib.gate_directive` 生成): @@ -116,6 +117,10 @@ Git 安全路径观察无法读取拟入库路径时,以 JSON additionalContex **取消无基线存量归因**:报错在未修改文件上也可能是本次 API 变化造成的,不能凭路径放行。 **一致性约束**:`UserPromptSubmit` 软门禁与本硬门禁共用同一条 skipGate 豁免, +且**豁免范围仅限语言门禁**:入库内容安全扫描(密钥/凭据类路径)不被任何 +代理可控豁免(仓库配置、内联 `-c codeguard.skipGate=true`、链式)覆盖,恒执行; +需要完整放行(含安全扫描)只能用进程环境变量 `CODEGUARD_SKIP_GATE`(1/true/yes, +仅用户可设——宿主内联赋值不会传入钩子进程)。软硬两门在豁免下也照常报告安全违规。 且**都不得在非 git 目录回退成"扫描 cwd"**——UPS 对非 git 目录输出一行 `_non_git_note` 说明并 exit 0(工作区根被回退扫描 = 上百无关仓的存量 lint 变成永久红,实测)。双副本事件去重键:PreToolUse 用 `tool_use_id + 命令`、 @@ -144,7 +149,7 @@ git UNVERIFIED,保持 exit 0 兼容放行;不能被描述为已准确验证 UserPromptSubmit 按提示词里的 `push/推送` 选 commit/push 面,但**不传 lanes = 三路宽口径**——软门禁没有待执行命令可预测,按"工作树有待提交改动就提醒"注入 (注入非阻断,多提醒不算错;硬门禁少拦才是底线),软硬两门只在 skipGate 豁免 -上严格一致。 +上严格一致(豁免只覆盖语言门禁;安全扫描两边都照常执行)。 PostToolUse 只运行可限定到单文件的检查和 formatter。append_files=false 的项目命令推迟到 显式 check/Git 门禁;保存不能触发整项目 formatter。工具异常不能自动修复。 diff --git a/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/design.md b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/design.md new file mode 100644 index 0000000..9740bc8 --- /dev/null +++ b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/design.md @@ -0,0 +1,36 @@ +## Context + +见 proposal.md。会话实测(2026-09-21/24):AI 在多仓以 `skipGate` 绕过存量 lint 债时,密钥入库检查被连带关闭;另见 `python3` 符号链接(`/usr/local/bin/python3` → Framework 3.13)使 `Path(sys.executable).parent` 指向不含 `ruff` 的目录,`ensure_user_path` 补错目录导致 6 项测试假失败(补 PATH 后 603/603 全过)。 + +## Goals / Non-Goals + +**Goals:** + +- 智能体可控的任何豁免都无法关闭密钥/凭据入库扫描。 +- 工具链发现在符号链接解释器、多 Python 并存机器上不再误报「未安装」。 + +**Non-Goals:** + +- 不改变 UNVERIFIED / fail-open 语义与退出码契约。 +- 不引入命令级 `python3 -m` 改写(探活通过而执行失败会自相矛盾)——PATH 层修复已覆盖同解释器安装的全部工具。 +- 不为 `check_paths` 的敏感模式表扩项。 + +## Decisions + +### 1. 逃生门一分为二:lint 豁免 vs 完整豁免 + +`skipGate` 系(仓库级 / 内联 `-c` / 链式赋值)均为智能体可执行的 git 命令形态,只豁免**语言门禁**;入库安全扫描照常执行。进程环境变量 `CODEGUARD_SKIP_GATE` 无法由智能体注入钩子进程(宿主命令内联赋值不传入),是用户专属开关,保留为覆盖二者的完整逃生门。理由:安全扫描的威胁模型是"智能体误提/被绕过提交密钥",而非人类用户;人类本来就可以在自己的终端直接提交。 + +### 2. 工具链修复落在 PATH 层而非 `-m` 回退 + +原建议是探活回退 `python3 -m `。根因分析后改为:`ensure_user_path` 补 `resolve().parent` 与 `sysconfig.get_path("scripts")`。理由:`-m` 回退只对 Python 模块工具有效且需同步改写执行命令(`ruff check .` 仍按裸名执行),探活通过而执行 127 会自相矛盾;PATH 修复对同解释器安装的全部工具(含非 Python 的 pipx / console scripts)生效,且零命令改写。`-m` 仅保留为 not_found 时的**诊断手段**(区分"未安装"与"入口缺失")。 + +### 3. 安全扫描在豁免路径下复用同一暂存面计算 + +`git_guard_application` 中暂存面(lanes/extra)计算移出 `if not active: continue` 短路,语言门禁按 `active` 决定是否执行,安全扫描恒执行。快照不可证明时沿用既有 UNVERIFIED 出口,不静默放行。 + +## Risks / Trade-offs + +- [存量 lint 债下以 skipGate 提交合法 fixture(如测试密钥素材)仍会被安全扫描拦] → 用户可用 `CODEGUARD_SKIP_GATE`(完整逃生门)或调整素材命名;报告文案显式指路。 +- [行为变更触及 hook-protocol 既有场景措辞] → MODIFIED 增量携带完整更新文本,`openspec validate --strict` 把关。 +- [探活重试多一次子进程] → 仅在 not_found 路径发生,成本可忽略。 diff --git a/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/proposal.md b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/proposal.md new file mode 100644 index 0000000..e52e1fb --- /dev/null +++ b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/proposal.md @@ -0,0 +1,27 @@ +## Why + +会话实测两处门禁缺陷:(1)`skipGate` 系豁免(仓库级 `git config codeguard.skipGate`、内联 `-c`、链式赋值)把**入库内容安全扫描**(密钥/凭据/依赖产物模式)连同语言门禁一并跳过——AI 提交 `id_rsa` / `.env` 类文件时密钥检查被静默关闭;(2)工具链发现把 `sys.executable` 的**符号链接目录**(如 `/usr/local/bin`)当脚本目录补入 PATH,真实脚本目录(Framework bin / `sysconfig.get_path("scripts")`)缺失,导致已安装工具被误报「工具不存在 (exit 127)」,全套件出现 6 项假失败。 + +## What Changes + +- **豁免范围收窄**:`skipGate` 系豁免只覆盖语言/lint 门禁;入库内容安全扫描在这些豁免下照常执行,违规仍硬拦截(PreToolUse)或注入上下文(UserPromptSubmit)。进程环境变量 `CODEGUARD_SKIP_GATE`(宿主命令内联赋值无法传入钩子进程,仅用户可设)保留为唯一覆盖二者的完整逃生门。 +- **工具链 PATH 根因修复**:`ensure_user_path` 改为补入 `Path(sys.executable).resolve().parent` 与 `sysconfig.get_path("scripts")`;`ToolchainProbe.probe` 与 `run_gate` 入口调用 `ensure_user_path`,使直调 `run_gate`(无钩子上下文)也能解析裸命令。 +- **探测诊断区分**:探活 not_found 时以 `python3 -m --version` 试判,把「未安装」与「模块已安装但脚本入口不在 PATH」区分开。 +- 文档与指令文案同步(`hooks/__protocol__.md`、README×2、`docs/current-architecture.md`、`gate_directive` 豁免段)。 + +## Capabilities + +### New Capabilities + +(无) + +### Modified Capabilities + +- `hook-protocol`: 豁免语义收窄——skipGate 系豁免仅覆盖语言门禁;入库安全扫描不可被智能体可控的豁免关闭;进程环境变量保留为唯一完整逃生门。 + +## Impact + +- 代码:`scripts/paths.py`、`scripts/codeguard/toolchain.py`、`scripts/codeguard/gate.py`、`scripts/codeguard/git_guard_application.py`、`scripts/codeguard/prompt_application.py`、`scripts/codeguard/reporting.py`。 +- 文档:`hooks/__protocol__.md`、`README.md`、`README.zh-CN.md`、`docs/current-architecture.md`。 +- 测试:新增 `tests/test_skip_gate_safety_scope.py`;既有豁免测试(空暂存面静默放行、内联豁免公告、env 逃生门)语义不变、保持绿色。 +- 兼容性:行为变更点仅在「豁免 + 暂存面含敏感文件」场景;正常提交路径与 UNVERIFIED 语义不变。 diff --git a/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/specs/hook-protocol/spec.md b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/specs/hook-protocol/spec.md new file mode 100644 index 0000000..dcd615b --- /dev/null +++ b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/specs/hook-protocol/spec.md @@ -0,0 +1,41 @@ +## ADDED Requirements + +### Requirement: The commit-content safety scan SHALL NOT be covered by any agent-controllable escape + +入库内容安全扫描(密钥/凭据/依赖产物模式)MUST NOT 被智能体可控的豁免(仓库级 `codeguard.skipGate`、内联 `-c codeguard.skipGate=true`、链式赋值)关闭。命中该类豁免时语言门禁跳过并记账,但安全扫描 MUST 照常执行;违规 MUST 以 exit 2 硬拦(PreToolUse)或注入安全报告(UserPromptSubmit)。唯一可同时豁免二者的逃生门是进程环境变量 `CODEGUARD_SKIP_GATE`(宿主命令内联赋值不传入钩子进程,仅用户可设),且 MUST 记账。 + +#### Scenario: Repository escape set and a credential file is staged + +- **WHEN** 仓库已设 `codeguard.skipGate` 为真值且暂存面含 id_rsa 类文件并执行 `git commit` +- **THEN** PreToolUse 仍 exit 2 输出入库安全报告;语言 lint 问题不再拦截 + +#### Scenario: Inline bypass with a secret staged + +- **WHEN** 以 `-c codeguard.skipGate=true` 形态执行提交且暂存面含 .env 类文件 +- **THEN** 安全扫描仍拦截;放行仅发生在无违规时,内联豁免公告照常注入 + +#### Scenario: Process-environment escape covers both gates + +- **WHEN** 钩子进程环境存在 `CODEGUARD_SKIP_GATE=1` 并执行含敏感文件的提交 +- **THEN** 语言门禁与安全扫描均跳过,记账一次(该逃生门仅用户可设) + +## MODIFIED Requirements + +### Requirement: Soft and hard gates SHALL share the skipGate escape and neither may fall back to scanning outside a git repository + +UserPromptSubmit 与 PreToolUse MUST 共用同一条仓库级豁免(`codeguard.skipGate`);该豁免及其内联/链式形态的覆盖范围 MUST 限定为语言门禁(见「commit-content safety scan 不被智能体可控豁免关闭」)。两门在命中豁免时 MUST 记账一次供 Stop 汇总。UserPromptSubmit 在当前目录不属于任何 git 仓库时 MUST 输出一行跳过说明并 exit 0,MUST NOT 把非 git 目录(尤其是多仓工作区根)回退为扫描对象。 + +#### Scenario: SkipGate set, user asks to commit + +- **WHEN** 仓库已设 skipGate 且用户消息触发提交意图,且待提交面无敏感模式文件 +- **THEN** 软门禁静默退出(与硬门禁一致),绕过计数 +1 + +#### Scenario: SkipGate set with a secret on the commit face + +- **WHEN** 仓库已设 skipGate 且用户消息触发提交意图,待提交面含敏感模式文件 +- **THEN** 软门禁注入入库安全报告(非阻断);硬门禁在同一提交面 exit 2 + +#### Scenario: Prompt fires outside any git repository + +- **WHEN** cwd 不在 git 仓库内且消息触发提交意图 +- **THEN** 输出“不是 git 仓库,提交门禁已跳过”的说明,不运行任何 linter、不扫描 cwd diff --git a/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/tasks.md b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/tasks.md new file mode 100644 index 0000000..3c3c25c --- /dev/null +++ b/openspec/changes/archive/2026-09-24-harden-skip-gate-boundary/tasks.md @@ -0,0 +1,22 @@ +## 1. 豁免语义收窄 + +- [x] 1.1 `git_guard_application`:暂存面计算移出 `not active` 短路,安全扫描恒执行,语言门禁按 `active` 执行。 +- [x] 1.2 `prompt_application`:skipGate 下跳过 `run_gate` 但保留 `check_commit_safety`,违规注入上下文。 +- [x] 1.3 `reporting.gate_directive` 豁免段写明范围(保留 `1/true/yes` 与「内联赋值不会传入」parity 字样)。 + +## 2. 工具链 PATH 根因修复 + +- [x] 2.1 `paths.ensure_user_path` 补 `Path(sys.executable).resolve().parent` 与 `sysconfig.get_path("scripts")`。 +- [x] 2.2 `ToolchainProbe.probe` 与 `run_gate` 入口调用 `ensure_user_path`。 +- [x] 2.3 探活 not_found 时以 `python3 -m --version` 试判,区分「未安装」与「模块在但入口缺失」。 + +## 3. 测试 + +- [x] 3.1 新增 `tests/test_skip_gate_safety_scope.py`:仓库级/内联豁免 + 敏感文件仍拦截;语言门禁仍豁免;软门禁注入安全报告;env 逃生门完整放行。 +- [x] 3.2 工具链探活在剥离 PATH 后自修复(裸 `ruff` 可解析)。 +- [x] 3.3 全量 pytest 通过,且**不**手工补 Framework bin 到 PATH(证明假失败根因已除)。 + +## 4. 文档与验证 + +- [x] 4.1 同步 `hooks/__protocol__.md`、`README.md`、`README.zh-CN.md`、`docs/current-architecture.md`。 +- [x] 4.2 `openspec validate --all --strict` 通过;ruff 对改动文件零告警。 diff --git a/openspec/specs/hook-protocol/spec.md b/openspec/specs/hook-protocol/spec.md index 66034dd..e7c3ba7 100644 --- a/openspec/specs/hook-protocol/spec.md +++ b/openspec/specs/hook-protocol/spec.md @@ -80,17 +80,22 @@ PreToolUse 硬拦截(exit 2)的 stderr 报告 MUST 包含一条整调用声 ### Requirement: Soft and hard gates SHALL share the skipGate escape and neither may fall back to scanning outside a git repository -UserPromptSubmit 与 PreToolUse MUST 共用同一条仓库级豁免(`git config codeguard.skipGate`);两门在命中豁免时 MUST 记账一次供 Stop 汇总。UserPromptSubmit 在当前目录不属于任何 git 仓库时 MUST 输出一行跳过说明并 exit 0,MUST NOT 把非 git 目录(尤其是多仓工作区根)回退为扫描对象。 +UserPromptSubmit 与 PreToolUse MUST 共用同一条仓库级豁免(`codeguard.skipGate`);该豁免及其内联/链式形态的覆盖范围 MUST 限定为语言门禁(见「commit-content safety scan 不被智能体可控豁免关闭」)。两门在命中豁免时 MUST 记账一次供 Stop 汇总。UserPromptSubmit 在当前目录不属于任何 git 仓库时 MUST 输出一行跳过说明并 exit 0,MUST NOT 把非 git 目录(尤其是多仓工作区根)回退为扫描对象。 #### Scenario: SkipGate set, user asks to commit -- **WHEN** 仓库已设 skipGate 且用户消息触发提交意图 +- **WHEN** 仓库已设 skipGate 且用户消息触发提交意图,且待提交面无敏感模式文件 - **THEN** 软门禁静默退出(与硬门禁一致),绕过计数 +1 +#### Scenario: SkipGate set with a secret on the commit face + +- **WHEN** 仓库已设 skipGate 且用户消息触发提交意图,待提交面含敏感模式文件 +- **THEN** 软门禁注入入库安全报告(非阻断);硬门禁在同一提交面 exit 2 + #### Scenario: Prompt fires outside any git repository - **WHEN** cwd 不在 git 仓库内且消息触发提交意图 -- **THEN** 输出"不是 git 仓库,提交门禁已跳过"的说明,不运行任何 linter、不扫描 cwd +- **THEN** 输出“不是 git 仓库,提交门禁已跳过”的说明,不运行任何 linter、不扫描 cwd ### Requirement: The commit face SHALL match the actual staging surface @@ -178,3 +183,23 @@ MUST 有回归测试锁定(收束/替换/追加三态 + 越界告警)。 - **WHEN** 环境变量值为 `1`、`true` 或 `yes`(任意大小写) - **THEN** 豁免生效并记入审计,与 git config 豁免的值词表一致 + +### Requirement: The commit-content safety scan SHALL NOT be covered by any agent-controllable escape + +入库内容安全扫描(密钥/凭据/依赖产物模式)MUST NOT 被智能体可控的豁免(仓库级 `codeguard.skipGate`、内联 `-c codeguard.skipGate=true`、链式赋值)关闭。命中该类豁免时语言门禁跳过并记账,但安全扫描 MUST 照常执行;违规 MUST 以 exit 2 硬拦(PreToolUse)或注入安全报告(UserPromptSubmit)。唯一可同时豁免二者的逃生门是进程环境变量 `CODEGUARD_SKIP_GATE`(宿主命令内联赋值不传入钩子进程,仅用户可设),且 MUST 记账。 + +#### Scenario: Repository escape set and a credential file is staged + +- **WHEN** 仓库已设 `codeguard.skipGate` 为真值且暂存面含 id_rsa 类文件并执行 `git commit` +- **THEN** PreToolUse 仍 exit 2 输出入库安全报告;语言 lint 问题不再拦截 + +#### Scenario: Inline bypass with a secret staged + +- **WHEN** 以 `-c codeguard.skipGate=true` 形态执行提交且暂存面含 .env 类文件 +- **THEN** 安全扫描仍拦截;放行仅发生在无违规时,内联豁免公告照常注入 + +#### Scenario: Process-environment escape covers both gates + +- **WHEN** 钩子进程环境存在 `CODEGUARD_SKIP_GATE=1` 并执行含敏感文件的提交 +- **THEN** 语言门禁与安全扫描均跳过,记账一次(该逃生门仅用户可设) + diff --git a/scripts/check_architecture.py b/scripts/check_architecture.py index 60d69f4..c14c031 100644 --- a/scripts/check_architecture.py +++ b/scripts/check_architecture.py @@ -53,7 +53,8 @@ "codeguard.registry": {"__future__", "json", "pathlib", "typing", "codeguard.registry_schema"}, "codeguard.config": {"__future__", "json", "re", "pathlib", "codeguard.registry"}, "codeguard.discovery": {"__future__", "fnmatch", "re", "pathlib", "codeguard.config", "codeguard.path_policy", "codeguard.registry"}, - "codeguard.toolchain": {"__future__", "pathlib", "threading", "codeguard.execution"}, + "codeguard.toolchain": {"__future__", "sys", "pathlib", "threading", "paths", + "codeguard.execution"}, "codeguard.language_check": {"__future__", "pathlib", "codeguard.config", "codeguard.discovery", "codeguard.execution", "codeguard.models", "codeguard.planning", "codeguard.registry", "codeguard.storage", "codeguard.verdict", @@ -96,7 +97,7 @@ "codeguard.discovery", "codeguard.registry", "codeguard.toolchain", "codeguard.execution", "codeguard.models", "codeguard.planning", "codeguard.reporting", "codeguard.verdict"}, - "codeguard.gate": {"__future__", "concurrent", "functools", "pathlib", "scope", "git_snapshot", + "codeguard.gate": {"__future__", "concurrent", "functools", "pathlib", "scope", "git_snapshot", "paths", "codeguard.config", "codeguard.discovery", "codeguard.registry", "codeguard.toolchain", "codeguard.cache", "codeguard.fingerprint", "codeguard.gate_checks", "codeguard.hook_state", "codeguard.models", "codeguard.spec_validation"}, diff --git a/scripts/codeguard/gate.py b/scripts/codeguard/gate.py index cb77ee4..fe744d8 100644 --- a/scripts/codeguard/gate.py +++ b/scripts/codeguard/gate.py @@ -5,6 +5,7 @@ from functools import partial from pathlib import Path +from paths import ensure_user_path from scope import changed_files from .cache import cache_path, load_result, store_result @@ -73,6 +74,9 @@ def run_gate( - failures: [(lang, 问题节选, 修复命令, install_hint)] - skipped: [str] 无法验证的说明(工具未装/超时),不阻塞 """ + # 必须先于缓存身份采集:check_identity 把 PATH 计入摘要, + # 采集后再改写 PATH 会使 ck != after,软观察缓存永不落盘。 + ensure_user_path() if exact: from git_snapshot import SnapshotError, validation_tree from scope import is_build_artifact diff --git a/scripts/codeguard/git_guard_application.py b/scripts/codeguard/git_guard_application.py index 7c88bc2..cbb3203 100644 --- a/scripts/codeguard/git_guard_application.py +++ b/scripts/codeguard/git_guard_application.py @@ -106,9 +106,9 @@ def evaluate_git_command(command: str, *, cwd: Path, load_config: Callable[[], d if bypass != "skipGate": label = "内联豁免 `-c codeguard.skipGate`" if bypass == "inline-skipGate" else "链式豁免" contexts.append(f"codeguard: {project_root} 的 {operation.mode} 已通过{label}放行(已审计记录)。") - if not active: - continue - root_mode = "push" if any(operation.mode == "push" for operation in active) else "commit" + # 豁免只覆盖语言门禁;入库安全扫描需要提交面,必须先算 lanes/extra 再恒执行 + mode_source = active or root_operations + root_mode = "push" if any(operation.mode == "push" for operation in mode_source) else "commit" pending_commit = any(operation.mode == "commit" for operation in root_operations) try: # workspace 兜底创建的是合成仓库目标;暂存观察也必须绑定该目标, @@ -140,16 +140,17 @@ def evaluate_git_command(command: str, *, cwd: Path, load_config: Callable[[], d contexts.append(f"codeguard: git UNVERIFIED:{message}") continue root_extra = list(extra) - failures, skipped = run_gate( - project_root, cfg, mode=root_mode, lanes=lanes, extra=root_extra, - exact=True, pending_commit=pending_commit, - ) - if failures: - reports.append(gate_directive(failures, project_root=project_root)) - unknown = [item for item in skipped if "本次改动未涉及" not in item and " SKIPPED:" not in item - and "markdown 风格告警" not in item] - if unknown: - contexts.append("codeguard: 存在未验证项,不能宣称全部通过:" + ";".join(unknown)) + if active: + failures, skipped = run_gate( + project_root, cfg, mode=root_mode, lanes=lanes, extra=root_extra, + exact=True, pending_commit=pending_commit, + ) + if failures: + reports.append(gate_directive(failures, project_root=project_root)) + unknown = [item for item in skipped if "本次改动未涉及" not in item and " SKIPPED:" not in item + and "markdown 风格告警" not in item] + if unknown: + contexts.append("codeguard: 存在未验证项,不能宣称全部通过:" + ";".join(unknown)) try: violations = check_commit_safety(project_root, root_mode, lanes=lanes, extra=root_extra, pending_commit=pending_commit) diff --git a/scripts/codeguard/prompt_application.py b/scripts/codeguard/prompt_application.py index 7c9e918..54f3196 100644 --- a/scripts/codeguard/prompt_application.py +++ b/scripts/codeguard/prompt_application.py @@ -48,9 +48,9 @@ def evaluate_prompt(user_text: str, *, project_root: Path | None, return PromptResult() if project_root is None or not (project_root / ".git").exists(): return PromptResult(non_git_note()) - if skip_gate_via_git_config(project_root): + lint_bypassed = skip_gate_via_git_config(project_root) + if lint_bypassed: record_skip_event("skipGate", project_root) - return PromptResult() cfg = load_config() detected = detect_languages(project_root) @@ -69,8 +69,14 @@ def current_event_key() -> str | None: # 软门没有待执行 git 命令,故保持三路宽口径;硬门另用预测暂存面。 mode = intent_mode(user_text) - failures, skipped = run_gate(project_root, cfg, languages=(subset or None), mode=mode) + failures, skipped = ( + ((), ()) if lint_bypassed + else run_gate(project_root, cfg, languages=(subset or None), mode=mode) + ) violations = check_commit_safety(project_root, mode) + if lint_bypassed and not violations: + # 豁免命中且无安全违规:软门禁静默退出(与硬门禁一致),绕过计数已 +1 + return PromptResult() def on_delivered() -> None: if event_key == current_event_key(): @@ -82,7 +88,7 @@ def on_delivered() -> None: first_issue = failures[0][1].splitlines()[0][:120] if failures and failures[0][1] else "详见对话" headline = (f"{failures[0][0]}: {first_issue}" if failures else f"提交安全: {violations[0][0]}") - parts = [gate_directive(failures)] + parts = [gate_directive(failures)] if failures else [] if violations: parts.append(format_safety_report(violations)) parts.append( @@ -91,7 +97,8 @@ def on_delivered() -> None: " fixture,确认误入库再 git rm --cached + 补 .gitignore;确认并修复后" "重新执行提交。" ) - return PromptResult("\n\n".join(parts), (summarize_failures(failures), headline), completion) + title = summarize_failures(failures) if failures else "提交安全" + return PromptResult("\n\n".join(parts), (title, headline), completion) skipped_langs = {item.split()[0] for item in skipped} checked = sorted(set(detect_languages(project_root)) - skipped_langs) diff --git a/scripts/codeguard/reporting.py b/scripts/codeguard/reporting.py index 85a0648..8b3a536 100644 --- a/scripts/codeguard/reporting.py +++ b/scripts/codeguard/reporting.py @@ -188,7 +188,7 @@ def gate_directive(failures: list, project_root: Path | str | None = None) -> st "(不落配置、无残留,推荐);**仓库级豁免**在该仓库执行 git config codeguard.skipGate true," "完成后 git config --unset codeguard.skipGate 恢复。环境变量 CODEGUARD_SKIP_GATE " "只认 1/true/yes 且须存在于钩子进程环境——宿主命令内联赋值不会传入钩子," - "设 0/false 不豁免(该变量不建议使用,优先单次豁免)。两种豁免都会记入会话审计明细。" + "设 0/false 不豁免(该变量不建议使用,优先单次豁免)。两种豁免都会记入会话审计明细。**豁免只覆盖语言门禁**:入库内容安全扫描(密钥/凭据类)不随之跳过;需要完整放行(含安全扫描)时使用进程环境变量 CODEGUARD_SKIP_GATE,该开关仅用户可设。" "注意:仓库级豁免对该克隆**所有分支**生效且跨会话残留,务必按上方说明 unset 恢复。", "─" * 60, ] diff --git a/scripts/codeguard/toolchain.py b/scripts/codeguard/toolchain.py index c9df369..a38a687 100644 --- a/scripts/codeguard/toolchain.py +++ b/scripts/codeguard/toolchain.py @@ -1,9 +1,12 @@ """工具探活:显式目标目录、统一执行边界、仅单轮检查内成功去重。""" from __future__ import annotations +import sys from pathlib import Path from threading import Lock +from paths import ensure_user_path + from .execution import execute BINARY_DEPENDENCIES = {"npx": ["node"]} @@ -45,6 +48,7 @@ def __init__(self, project_root: Path): def probe(self, cmd_def: dict, timeout: float = 10) -> tuple[bool, str]: """显式 probe 优先,否则逐一验证工具 --version;绝不读取宿主 stdin。""" + ensure_user_path() # 直调 run_gate 的路径(测试、run_check)没有钩子代补 PATH commands = ([cmd_def["probe"]] if cmd_def.get("probe") else [[binary, "--version"] for binary in extract_tool_binaries(cmd_def)]) for command in commands: @@ -59,7 +63,8 @@ def probe(self, cmd_def: dict, timeout: float = 10) -> tuple[bool, str]: if result.failure == "timeout": return False, f"探活超时: {' '.join(command)}" if result.failure: - return False, f"探活无法执行: {result.stderr}" + hint = _module_entry_hint(command) if result.failure == "not_found" else "" + return False, f"探活无法执行: {result.stderr}{hint}" if result.returncode: first = next((line for line in (result.stderr or result.stdout).splitlines() if line.strip()), "") @@ -73,3 +78,17 @@ def probe_toolchain(cmd_def: dict, timeout: int = 10, *, project_root: str | Path | None = None) -> tuple[bool, str]: """兼容单次调用;批次调用方显式持有 ToolchainProbe 以共享成功探活。""" return ToolchainProbe(Path(project_root) if project_root is not None else Path.cwd()).probe(cmd_def, timeout) + + +def _module_entry_hint(command: list[str]) -> str: + """裸命令缺失时试 `python3 -m --version`,区分「未安装」与「入口缺失」。 + + 仅作诊断提示,不改写执行命令——探活若以 `-m` 形式通过而后续命令仍按裸名 + 执行会自相矛盾;入口缺失的根因修复在 ensure_user_path 的 PATH 补齐。 + """ + if len(command) != 2 or "/" in command[0] or command[1] != "--version": + return "" + result = execute([sys.executable, "-m", command[0], "--version"], Path.cwd(), 10, stdin_null=True) + if not result.failure and result.returncode == 0: + return f"(检测到 Python 模块 `{command[0]}`:已安装但脚本入口不在 PATH)" + return "(也未检测到同名 Python 模块:可能确实未安装)" diff --git a/scripts/paths.py b/scripts/paths.py index 35b6df1..076962b 100644 --- a/scripts/paths.py +++ b/scripts/paths.py @@ -12,6 +12,7 @@ import os import subprocess import sys +import sysconfig import tempfile from pathlib import Path @@ -29,6 +30,10 @@ def ensure_user_path(from_login_shell: bool = False) -> None: static_dirs = [ # 运行本进程的解释器自己的 bin 目录:ruff 等工具常装在这里 # (anaconda、python -m pip install 等),不补就会"装了却报不在 PATH"。 + # 符号链接解释器(如 /usr/local/bin/python3 → Framework)取 .parent 得到的是 + # 链接目录,脚本入口在 resolve 后的真实 bin 与 sysconfig scripts 里(实测)。 + str(Path(sys.executable).resolve().parent), + sysconfig.get_path("scripts"), str(Path(sys.executable).parent), "/opt/homebrew/bin", "/usr/local/bin", str(Path.home() / ".local" / "bin"), @@ -39,7 +44,7 @@ def ensure_user_path(from_login_shell: bool = False) -> None: cur = os.environ.get("PATH", "") parts = cur.split(os.pathsep) for d in reversed(static_dirs): - if Path(d).exists() and d not in parts: + if d and Path(d).exists() and d not in parts: parts.insert(0, d) os.environ["PATH"] = os.pathsep.join(parts) diff --git a/tests/test_git_guard_application.py b/tests/test_git_guard_application.py index 11cc093..5fbdc39 100644 --- a/tests/test_git_guard_application.py +++ b/tests/test_git_guard_application.py @@ -431,7 +431,10 @@ def test_prefixed_inline_bypass_stays_audited_and_scoped(self): result = git_guard_application.evaluate_git_command( f"{prefix} git -c codeguard.skipGate=true commit -m x", cwd=repo, load_config=dict) - self.assertEqual(0, result.exit_code, result.stderr) + # 内联豁免只覆盖语言门禁;入库安全扫描仍拦 .env + #(harden-skip-gate-boundary:代理可控豁免不再吞密钥扫描) + self.assertEqual(2, result.exit_code, result.stderr) + self.assertIn("安全检查", result.stderr) self.assertTrue(any("内联豁免" in item for item in result.contexts)) gate.assert_not_called() record.assert_called_once_with("inline-skipGate", repo) diff --git a/tests/test_skip_gate_safety_scope.py b/tests/test_skip_gate_safety_scope.py new file mode 100644 index 0000000..5efee25 --- /dev/null +++ b/tests/test_skip_gate_safety_scope.py @@ -0,0 +1,174 @@ +"""skipGate 豁免范围与工具链发现回归(2026-09-24 harden-skip-gate-boundary)。 + +豁免一分为二:智能体可控的 skipGate 系(仓库级 / 内联 -c / 链式)只豁免语言门禁, +入库安全扫描(密钥/凭据/依赖产物模式)不随之跳过;进程环境变量 +CODEGUARD_SKIP_GATE(仅用户可设)是唯一完整逃生门。工具链发现在符号链接 +解释器机器上不再误报「工具不存在」。 +""" +from __future__ import annotations + +import os +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "scripts")) + +K = "codeguard." + "skipGate" # 命令文本避免出现连续触发字面量 +GC = "git " + "commit" + + +def _git(repo: Path, *args: str) -> None: + subprocess.run(["git", *args], cwd=repo, check=True, capture_output=True, text=True) + + +def _repo(tmp: Path) -> Path: + repo = tmp / "repo" + repo.mkdir() + _git(repo, "init", "-q") + _git(repo, "config", "user.email", "t@t") + _git(repo, "config", "user.name", "t") + (repo / "a.py").write_text("print(1)\n", encoding="utf-8") + _git(repo, "add", "-A") + _git(repo, "commit", "-qm", "base") + return repo + + +def _stage(repo: Path, name: str) -> None: + (repo / name).write_bytes(b"x") + _git(repo, "add", "-A") + + +def _guard(repo: Path, command: str, home: Path, extra_env: dict | None = None): + from codeguard.git_guard_application import evaluate_git_command + + env_backup = {k: os.environ.get(k) for k in ("CODEGUARD_HOME", "CODEGUARD_SKIP_GATE")} + os.environ["CODEGUARD_HOME"] = str(home) + os.environ.pop("CODEGUARD_SKIP_GATE", None) + for key, value in (extra_env or {}).items(): + os.environ[key] = value + try: + return evaluate_git_command( + command, cwd=repo, load_config=dict, bypass_env=bool((extra_env or {}).get("CODEGUARD_SKIP_GATE")), + ) + finally: + for key, backup in env_backup.items(): + if backup is None: + os.environ.pop(key, None) + else: + os.environ[key] = backup + + +def _prompt(repo: Path, home: Path, load_config=None): + from codeguard.prompt_application import evaluate_prompt + + os.environ["CODEGUARD_HOME"] = str(home) + return evaluate_prompt( + "请提交这些改动", project_root=repo, session_id="scope-test", + load_config=load_config or dict, + ) + + +class SkipGateScopeTests(unittest.TestCase): + """仓库级/内联豁免不覆盖入库安全扫描。""" + + def setUp(self): + self._td = tempfile.TemporaryDirectory(prefix="cg-scope-") + self.addCleanup(self._td.cleanup) + self.tmp = Path(self._td.name) + self.home = self.tmp / "home" + self.repo = _repo(self.tmp) + + def test_persisted_escape_still_blocks_secret_files(self): + _stage(self.repo, "id_rsa") + _git(self.repo, "config", K, "true") + result = _guard(self.repo, f"{GC} -m x", self.home) + self.assertEqual(2, result.exit_code) + self.assertIn("安全检查", result.stderr) + self.assertIn("id_rsa", result.stderr) + + def test_inline_escape_still_blocks_secret_files(self): + _stage(self.repo, ".env") + result = _guard(self.repo, f"git -c {K}=true {GC[4:]} -m x".replace(" ", " "), self.home) + self.assertEqual(2, result.exit_code) + self.assertIn(".env", result.stderr) + + def test_clean_face_stays_silent_under_escape(self): + (self.repo / "a.py").write_text("print(2)\n", encoding="utf-8") + _git(self.repo, "add", "-A") + _git(self.repo, "config", K, "true") + result = _guard(self.repo, f"{GC} -m x", self.home) + self.assertEqual(0, result.exit_code) + self.assertFalse(result.stderr and result.contexts) + + def test_env_escape_covers_both_gates(self): + _stage(self.repo, "id_rsa") + result = _guard( + self.repo, f"{GC} -m x", self.home, + extra_env={"CODEGUARD_SKIP_GATE": "1"}, + ) + self.assertEqual(0, result.exit_code) + self.assertFalse(result.stderr and result.contexts) + + def test_soft_gate_reports_secrets_under_escape(self): + _stage(self.repo, "id_rsa") + _git(self.repo, "config", K, "true") + result = _prompt(self.repo, self.home) + text = result.additional_context or "" + self.assertIn("安全", text) + self.assertIn("id_rsa", text) + + +class ToolchainDiscoveryTests(unittest.TestCase): + """符号链接解释器下 ensure_user_path 补齐真实脚本目录;探活诊断区分入口缺失。""" + + def test_ensure_user_path_adds_resolved_scripts_dir(self): + import sysconfig + + from paths import ensure_user_path + + saved = os.environ.get("PATH", "") + os.environ["PATH"] = "/usr/bin:/bin" + try: + ensure_user_path() + on_path = os.environ["PATH"].split(":") + finally: + os.environ["PATH"] = saved + scripts = sysconfig.get_path("scripts") + self.assertIn(str(Path(sys.executable).resolve().parent), on_path) + self.assertIn(scripts, on_path) + + def test_probe_recovers_when_scripts_dir_was_off_path(self): + from codeguard.toolchain import ToolchainProbe + + saved = os.environ.get("PATH", "") + os.environ["PATH"] = "/usr/bin:/bin" + try: + with tempfile.TemporaryDirectory() as td: + ok, reason = ToolchainProbe(Path(td)).probe( + {"probe": ["ruff", "--version"]} + ) + finally: + os.environ["PATH"] = saved + # 根因修复后:probe 入口自补 PATH,ruff 可解析(不是「工具不存在」) + self.assertTrue(ok, reason) + + def test_module_entry_hint_distinguishes_missing_entry(self): + from codeguard.toolchain import _module_entry_hint + + saved = os.environ.get("PATH", "") + os.environ["PATH"] = "/usr/bin:/bin" + try: + hint_missing_entry = _module_entry_hint(["ruff", "--version"]) + hint_absent = _module_entry_hint(["definitely-not-a-python-module-xyz", "--version"]) + finally: + os.environ["PATH"] = saved + self.assertIn("模块", hint_missing_entry) + self.assertIn("未安装", hint_absent) + + +if __name__ == "__main__": + unittest.main()