Skip to content

Commit b2bcb4d

Browse files
committed
补齐 Clippy 文档契约修复指引与原生边界验收
1 parent a968566 commit b2bcb4d

16 files changed

Lines changed: 5187 additions & 4 deletions

‎README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,3 +1093,6 @@ Java Gradle vulnerability checks now expose explicit original tasks through `cod
10931093

10941094

10951095
The standalone C/C++ Clang entry now distinguishes literal/comment hashes and raw strings from preprocessing directives, including alternate tokens and line splices. Leading non-ASCII recovery remains conservatively unresolved. This bounded verification does not qualify project syntax, documentation, development conventions, or CVE coverage. See [acceptance limits](tests/acceptance/clang-preprocessor-context.md).
1096+
1097+
1098+
Native Clippy documentation findings now receive specific Errors/Panics/Safety repair guidance through stable tasks and original-tool suppression rechecks. The installed Clippy accepts bare headings: zero diagnostics do not qualify detailed documentation and facts remain open. Ordinary checks do not enable pedantic implicitly. `comments rust` remains a local Rustdoc probe; complete documentation contracts are not unified or qualified. See [acceptance and gaps](tests/acceptance/clippy-documentation-contract.md).

‎README.zh-CN.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,3 +1070,6 @@ Java Gradle 漏洞检查新增显式原任务入口 `codeguard cve java`;JSON
10701070

10711071

10721072
C/C++ 独立 Clang 入口已修复字符串、注释和原始字符串中的井号误判,并补预处理替代记号/续行防护;行首非 ASCII 恢复仍保守未解析。仅局部验收,完整项目语法、文档规范、开发规范、CVE 四核心生产门槛保持开放。见 [验收边界](tests/acceptance/clang-preprocessor-context.md)。
1073+
1074+
1075+
Rust 的详细文档任务现对原生 Clippy `missing_errors_doc`、`missing_panics_doc`、`missing_safety_doc` 给出具体 Errors/Panics/Safety 修复指引,复用稳定任务及原工具抑制对照。已有 Clippy 实测接受空章节标题,因此零诊断不代表详细说明合格,事实继续 open;不会隐式启用 pedantic。`comments rust` 仍为 Rustdoc 局部探针,尚未统一完整文档契约。参见 [局部验收与缺口](tests/acceptance/clippy-documentation-contract.md)。

‎crates/codeguard-cli/src/next_command.rs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,20 @@ fn current_correction_refs(
17801780
}
17811781

17821782
fn finding_repair_step(checker_id: &str, rule: &str) -> &'static str {
1783+
if checker_id == "rust.cargo_clippy" {
1784+
match rule {
1785+
"clippy::missing_errors_doc" => {
1786+
return "核对实际 Result 错误类型与触发条件,补齐 # Errors 的具体说明;章节标题不能代替内容,不改变返回类型或错误行为迎合检查;按原任务原 Cargo 运行 task verify,零诊断不代表详细契约或可信关闭";
1787+
}
1788+
"clippy::missing_panics_doc" => {
1789+
return "核对实际 panic、unwrap、expect 及适用调用条件,补齐 # Panics 的具体说明;章节标题不能代替内容,不改变 API 行为迎合检查;按原任务原 Cargo 运行 task verify,隐式或跨调用 panic 仍需调查";
1790+
}
1791+
"clippy::missing_safety_doc" => {
1792+
return "核对 unsafe API 的调用前置条件与调用者责任,补齐 # Safety 的具体说明;章节标题不能代替内容,不删除 unsafe 或改变 API 逃逸;按原任务原 Cargo 运行 task verify,说明的准确性仍需语义核验";
1793+
}
1794+
_ => {}
1795+
}
1796+
}
17831797
if checker_id == "python.ruff" {
17841798
if let Some(doc) = codeguard_adapters::RuffDocumentationRule::from_code(rule) {
17851799
return doc.step;
@@ -2570,6 +2584,22 @@ mod python_action_tests {
25702584
use super::canonical_action_id;
25712585
use serde_json::json;
25722586
#[test]
2587+
fn clippy_documentation_guidance_requires_exact_checker_and_rule() {
2588+
for (rule, section) in [
2589+
("missing_errors_doc", "Errors"),
2590+
("missing_panics_doc", "Panics"),
2591+
("missing_safety_doc", "Safety"),
2592+
] {
2593+
let rule = format!("clippy::{rule}");
2594+
assert!(super::finding_repair_step("rust.cargo_clippy", &rule).contains(section));
2595+
assert!(!super::finding_repair_step("python.ruff", &rule).contains(section));
2596+
assert!(
2597+
!super::finding_repair_step("rust.cargo_clippy", &format!("{rule}_unknown"))
2598+
.contains(section)
2599+
);
2600+
}
2601+
}
2602+
#[test]
25732603
fn native_syntax_evidence_controls_action_even_when_budget_requires_decision() {
25742604
let mut brief = json!({"kind":"blocker","checker_id":"python.ruff","reason_code":"python_syntax_confirmation_needed","verification_observation":"still_present","disposition":"actionable"});
25752605
assert_eq!(canonical_action_id(&brief), Ok("repair-source"));
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#![cfg(unix)]
2+
//! 原项目文档契约的实际 Clippy 检查;不安装工具,不作为独立精度或生产资格。
3+
use serde_json::{Value, json};
4+
use sha2::{Digest, Sha256};
5+
use std::{fs, path::PathBuf, process::Command};
6+
7+
struct Project(PathBuf);
8+
impl Project {
9+
fn new(index: usize, source: &str) -> Self {
10+
let root = std::env::temp_dir()
11+
.canonicalize()
12+
.unwrap()
13+
.join(format!("cg-clippy-doc-{}-{index}", std::process::id()));
14+
fs::create_dir_all(root.join("src")).unwrap();
15+
fs::write(
16+
root.join("Cargo.toml"),
17+
"[package]\nname='documentation-sample'\nversion='0.1.0'\nedition='2021'\n",
18+
)
19+
.unwrap();
20+
fs::write(
21+
root.join("Cargo.lock"),
22+
"version = 4\n[[package]]\nname=\"documentation-sample\"\nversion=\"0.1.0\"\n",
23+
)
24+
.unwrap();
25+
fs::write(root.join("src/lib.rs"), source).unwrap();
26+
let init = Self(root);
27+
init.invoke(&["init", "--apply", "--format=json"]);
28+
init
29+
}
30+
fn invoke(&self, args: &[&str]) -> Value {
31+
let output = Command::new(env!("CARGO_BIN_EXE_codeguard"))
32+
.args(args)
33+
.arg(&self.0)
34+
.env_remove("CODEGUARD_TIMEOUT")
35+
.output()
36+
.unwrap();
37+
assert_eq!(
38+
output.status.code(),
39+
Some(3),
40+
"{}",
41+
String::from_utf8_lossy(&output.stderr)
42+
);
43+
serde_json::from_slice(&output.stdout).unwrap()
44+
}
45+
}
46+
impl Drop for Project {
47+
fn drop(&mut self) {
48+
let _ = fs::remove_dir_all(&self.0);
49+
}
50+
}
51+
52+
#[test]
53+
#[ignore = "requires explicitly selected installed Cargo with Clippy; no installation"]
54+
fn native_documentation_contracts_reuse_tasks_and_preserve_semantic_limits() {
55+
let tool = std::env::var("CODEGUARD_TEST_CARGO").expect("select existing Cargo");
56+
let version = Command::new(&tool)
57+
.args(["clippy", "--version"])
58+
.output()
59+
.unwrap();
60+
assert!(version.status.success());
61+
let version = String::from_utf8(version.stdout).unwrap();
62+
let cli_sha256 = format!(
63+
"{:x}",
64+
Sha256::digest(fs::read(env!("CARGO_BIN_EXE_codeguard")).unwrap())
65+
);
66+
let mut evidence = Vec::new();
67+
for (index, (rule, section, body, detail)) in [
68+
(
69+
"missing_errors_doc",
70+
"Errors",
71+
"pub fn fail() -> Result<(), &'static str> { Err(\"unavailable\") }",
72+
"不可用时返回 unavailable 错误。",
73+
),
74+
(
75+
"missing_panics_doc",
76+
"Panics",
77+
"pub fn fail() { panic!(\"unavailable\"); }",
78+
"每次调用都会因 unavailable 而 panic。",
79+
),
80+
(
81+
"missing_safety_doc",
82+
"Safety",
83+
"pub unsafe fn fail() {}",
84+
"本接口没有额外调用前置条件。",
85+
),
86+
]
87+
.into_iter()
88+
.enumerate()
89+
{
90+
let source = |doc: &str, suppression: &str| {
91+
format!(
92+
"#![warn(clippy::{rule})]\n{}/// 演示原生契约检查。\n{}{body}\n",
93+
if suppression.is_empty() {
94+
String::new()
95+
} else {
96+
format!("{suppression}\n")
97+
},
98+
if doc.is_empty() {
99+
String::new()
100+
} else {
101+
format!("{doc}\n")
102+
}
103+
)
104+
};
105+
let p = Project::new(index, &source("", ""));
106+
let lint = || p.invoke(&["lint", "rust", "--cargo-tool", &tool, "--format=json"]);
107+
let first = lint();
108+
let native_rule = format!("clippy::{rule}");
109+
assert_eq!(
110+
first["native_report"]["local_scan_complete"], true,
111+
"{first}"
112+
);
113+
let finding = first["native_report"]["findings"]
114+
.as_array()
115+
.unwrap()
116+
.iter()
117+
.find(|f| f["rule_id"] == native_rule)
118+
.expect("native documentation finding");
119+
let id = finding["finding_id"].as_str().unwrap();
120+
let brief = &first["next"]["repair_brief"];
121+
assert_eq!(brief["task_id"], id, "{first}");
122+
assert!(brief["step"].as_str().unwrap().contains(section), "{brief}");
123+
let repeat = lint();
124+
assert_eq!(repeat["next"]["repair_brief"]["task_id"], id);
125+
let verify = || p.invoke(&["task", "verify", id, "--cargo-tool", &tool, "--format=json"]);
126+
let present = verify();
127+
assert_eq!(present["observation"], "still_present", "{present}");
128+
fs::write(
129+
p.0.join("src/lib.rs"),
130+
source("", &format!("#[allow(clippy::{rule})]")),
131+
)
132+
.unwrap();
133+
let suppressed = verify();
134+
assert_eq!(
135+
suppressed["observation"], "suppression_requires_review",
136+
"{suppressed}"
137+
);
138+
fs::write(
139+
p.0.join("src/lib.rs"),
140+
source(&format!("/// # {section}"), ""),
141+
)
142+
.unwrap();
143+
let bare = lint();
144+
assert_eq!(bare["native_report"]["local_scan_complete"], true, "{bare}");
145+
assert!(
146+
bare["native_report"]["findings"]
147+
.as_array()
148+
.unwrap()
149+
.iter()
150+
.all(|f| f["rule_id"] != native_rule),
151+
"{bare}"
152+
);
153+
assert_eq!(bare["delivery_decision"], "not_evaluated");
154+
fs::write(
155+
p.0.join("src/lib.rs"),
156+
source(&format!("/// # {section}\n/// {detail}"), ""),
157+
)
158+
.unwrap();
159+
let repaired = verify();
160+
assert_eq!(
161+
repaired["observation"], "candidate_absent_unverified_policy",
162+
"{repaired}"
163+
);
164+
let fact: Value = serde_json::from_slice(
165+
&fs::read(p.0.join(format!(".codeguard/findings/{id}/finding.json"))).unwrap(),
166+
)
167+
.unwrap();
168+
assert_eq!(fact["state"], "open");
169+
evidence.push(json!({"rule":native_rule,"first":first,"repeat":repeat,"present":present,"suppressed":suppressed,"bare_heading":bare,"repaired":repaired,"state":fact["state"]}));
170+
}
171+
let p = Project::new(
172+
3,
173+
"/// 返回固定错误。\npub fn fail() -> Result<(), &'static str> { Err(\"unavailable\") }\n",
174+
);
175+
let unselected = p.invoke(&["lint", "rust", "--cargo-tool", &tool, "--format=json"]);
176+
assert_eq!(
177+
unselected["native_report"]["local_scan_complete"], true,
178+
"{unselected}"
179+
);
180+
assert!(
181+
unselected["native_report"]["findings"]
182+
.as_array()
183+
.unwrap()
184+
.iter()
185+
.all(|f| f["rule_id"] != "clippy::missing_errors_doc")
186+
);
187+
if let Ok(path) = std::env::var("CODEGUARD_CLIPPY_DOC_EVIDENCE") {
188+
fs::write(path, serde_json::to_vec_pretty(&json!({"qualification":"not_granted","cli_sha256":cli_sha256,"clippy_version":version.trim(),"cases":evidence,"unselected_pedantic":unselected})).unwrap()).unwrap();
189+
}
190+
}

‎docs/Codeguard-Architecture.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,3 +1504,6 @@ Explicit Gradle CVE now syncs sanitized stable preparation tasks in initialized
15041504

15051505

15061506
The standalone C/C++ Clang entry now distinguishes literal/comment hashes and raw strings from preprocessing directives, including alternate tokens and line splices. Leading non-ASCII recovery remains conservatively unresolved. This bounded verification does not qualify project syntax, documentation, development conventions, or CVE coverage. See [acceptance limits](../tests/acceptance/clang-preprocessor-context.md).
1507+
1508+
1509+
Native Clippy documentation findings now receive specific Errors/Panics/Safety repair guidance through stable tasks and original-tool suppression rechecks. The installed Clippy accepts bare headings: zero diagnostics do not qualify detailed documentation and facts remain open. Ordinary checks do not enable pedantic implicitly. `comments rust` remains a local Rustdoc probe; complete documentation contracts are not unified or qualified. See [acceptance and gaps](../tests/acceptance/clippy-documentation-contract.md).

‎docs/Codeguard-Architecture.zh_CN.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,3 +1504,6 @@ Java Gradle 漏洞检查新增显式原任务入口 `codeguard cve java`;JSON
15041504

15051505

15061506
C/C++ 独立 Clang 入口已修复字符串、注释和原始字符串中的井号误判,并补预处理替代记号/续行防护;行首非 ASCII 恢复仍保守未解析。仅局部验收,完整项目语法、文档规范、开发规范、CVE 四核心生产门槛保持开放。见 [验收边界](../tests/acceptance/clang-preprocessor-context.md)。
1507+
1508+
1509+
Rust 的详细文档任务现对原生 Clippy `missing_errors_doc`、`missing_panics_doc`、`missing_safety_doc` 给出具体 Errors/Panics/Safety 修复指引,复用稳定任务及原工具抑制对照。已有 Clippy 实测接受空章节标题,因此零诊断不代表详细说明合格,事实继续 open;不会隐式启用 pedantic。`comments rust` 仍为 Rustdoc 局部探针,尚未统一完整文档契约。参见 [局部验收与缺口](../tests/acceptance/clippy-documentation-contract.md)。

‎docs/Codeguard-Technical-Design.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,3 +1977,6 @@ Explicit Gradle CVE now syncs sanitized stable preparation tasks in initialized
19771977

19781978

19791979
The standalone C/C++ Clang entry now distinguishes literal/comment hashes and raw strings from preprocessing directives, including alternate tokens and line splices. Leading non-ASCII recovery remains conservatively unresolved. This bounded verification does not qualify project syntax, documentation, development conventions, or CVE coverage. See [acceptance limits](../tests/acceptance/clang-preprocessor-context.md).
1980+
1981+
1982+
Native Clippy documentation findings now receive specific Errors/Panics/Safety repair guidance through stable tasks and original-tool suppression rechecks. The installed Clippy accepts bare headings: zero diagnostics do not qualify detailed documentation and facts remain open. Ordinary checks do not enable pedantic implicitly. `comments rust` remains a local Rustdoc probe; complete documentation contracts are not unified or qualified. See [acceptance and gaps](../tests/acceptance/clippy-documentation-contract.md).

‎docs/Codeguard-Technical-Design.zh_CN.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,3 +1978,6 @@ Java Gradle 漏洞检查新增显式原任务入口 `codeguard cve java`;JSON
19781978

19791979

19801980
C/C++ 独立 Clang 入口已修复字符串、注释和原始字符串中的井号误判,并补预处理替代记号/续行防护;行首非 ASCII 恢复仍保守未解析。仅局部验收,完整项目语法、文档规范、开发规范、CVE 四核心生产门槛保持开放。见 [验收边界](../tests/acceptance/clang-preprocessor-context.md)。
1981+
1982+
1983+
Rust 的详细文档任务现对原生 Clippy `missing_errors_doc`、`missing_panics_doc`、`missing_safety_doc` 给出具体 Errors/Panics/Safety 修复指引,复用稳定任务及原工具抑制对照。已有 Clippy 实测接受空章节标题,因此零诊断不代表详细说明合格,事实继续 open;不会隐式启用 pedantic。`comments rust` 仍为 Rustdoc 局部探针,尚未统一完整文档契约。参见 [局部验收与缺口](../tests/acceptance/clippy-documentation-contract.md)。

‎openspec/changes/introduce-rust-codeguard-cli/specs/native-tool-adapters/spec.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ Rustdoc逐问题反馈 MUST 提供问题证据、规则依据、允许修改的
120120
- **THEN** 复用原生Clippy局部观察、共享预算和输入核对,仅执行lint而不执行rustdoc、build或CVE;初始化工作区自动保存报告、同步稳定任务并反馈下一步,零诊断不能关闭任务或签发完整通过
121121
- **AND** 显式入口无效或原生执行失败不得换用其它工具或WASM掩盖原生失败
122122

123+
#### Scenario: Native Clippy documentation contracts guide the original repair task
124+
- **WHEN** 原项目启用的 Clippy 检出 missing_errors_doc、missing_panics_doc 或 missing_safety_doc
125+
- **THEN** 保留原生规则和同一稳定任务,分别要求说明实际错误及条件、panic 条件或 unsafe 调用前置条件;不得改变 API 行为或关闭规则迎合检查,使用原工具 task verify 复检
126+
- **AND** 不为普通检查强制启用 pedantic 规则;抑制对照仍保持任务开放,空章节标题的原生零诊断不代表详细说明合格或完整文档验收
127+
123128
#### Scenario: Standalone Rust lint has no native Cargo
124129
- **WHEN** 未显式选择Cargo且绝对PATH没有可用Cargo
125130
- **THEN** WASM构建对有界Rust源码提供候选初检并同步确认任务;发现候选或范围/运行未完成时必须准备原生工具,完整有界范围零候选时推荐准备;不安装工具、不把候选认定为源码违规;无WASM构建明确初检不可用

‎openspec/changes/introduce-rust-codeguard-cli/tasks.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,3 +2093,5 @@ next/task show在已核验当前原生零诊断、原grammar引用及同一源
20932093
CI37503859591终态MSRV通过、gate源码检出失败。确认语料9e4adb1远端可达,审计dec5f9d只在插件本地main;本地固定Git21项审计及两个变异/缺仓库目标通过、旧注册表审计回放通过;插件Node6通过/2实际条件跳过、技能vendor离线/在线通过。插件main有3个远端未包含提交,候选branch不是其祖先;主分支推送会改变默认Hook,已请求授权但尚未执行,不强推/合并/更换来源或发市场。待决动作与证据见tests/acceptance/ci-source-reachability.md。
20942094

20952095
逐语言四核心、32grammar完整原生版本/方言与独立语料、可信闭环、平台/性能/宿主/发行仍未完成;父任务保持66完成/288待完成,正式grammar资格0/32。受保护Erlang草稿未修改、执行或提交。
2096+
2097+
2026-10-07 Rust Clippy 文档契约局部进展:延续7.1/15.3/15.6,原 Errors 指引测试先RED后补齐三个精确原规则指引,真实已有Clippy验证重复身份、仍存在、allow抑制及修复后未受信消失。空章节标题被本机原生接受,不能证明详细说明;未选pedantic不自动开启。源码事实仍open,comments rust仍独立Rustdoc探针;详细内容/全部构建组合/统一类别/独立精度/可信闭环仍未完成,不勾选父任务、不改变0/32资格。见tests/acceptance/clippy-documentation-contract.md。

0 commit comments

Comments
 (0)