Skip to content

Commit ea04c91

Browse files
committed
接入公开文档命令的同次原生占位反馈及协议回归
1 parent 8a6b3b1 commit ea04c91

18 files changed

Lines changed: 4412 additions & 343 deletions

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

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub fn run(args: &[String]) -> ExitCode {
1818
}
1919
};
2020
let deadline = Instant::now() + Duration::from_millis(request.timeout_ms);
21-
let report = match observe(&request, deadline, &AtomicBool::new(false), true) {
21+
let report = match observe_policy(&request, deadline, &AtomicBool::new(false), true, true) {
2222
Ok(report) => report,
2323
Err(reason) => {
2424
eprintln!("{reason}");
@@ -59,6 +59,17 @@ pub fn run(args: &[String]) -> ExitCode {
5959
row["structure_status"]
6060
);
6161
}
62+
for row in report["documentation_placeholders"]["observation"]["positions"]
63+
.as_array()
64+
.into_iter()
65+
.flatten()
66+
.take(8)
67+
{
68+
println!(
69+
"自有占位规则:{}:{}:{};组件 {}",
70+
report["path"], row["line"], row["column_byte"], row["component"]
71+
);
72+
}
6273
if !report["workbench"].is_null() {
6374
println!(
6475
"工作台 {};任务 {}",
@@ -103,6 +114,16 @@ pub(crate) fn observe(
103114
deadline: Instant,
104115
cancellation: &AtomicBool,
105116
persist: bool,
117+
) -> Result<Value, String> {
118+
observe_policy(request, deadline, cancellation, persist, false)
119+
}
120+
121+
fn observe_policy(
122+
request: &crate::syntax_lint_arguments::SyntaxLintArguments,
123+
deadline: Instant,
124+
cancellation: &AtomicBool,
125+
persist: bool,
126+
placeholders: bool,
106127
) -> Result<Value, String> {
107128
let workspace = match crate::c_family_comments_workbench::resolve_root(
108129
&request.source,
@@ -134,6 +155,8 @@ pub(crate) fn observe(
134155
let mut native = json!({"status":"incomplete","reason":"clang_source_scope_unavailable","version":null,"tool_sha256":null,"diagnostics":[]});
135156
let mut source_sha256 = Value::Null;
136157
let mut structure = json!({"status":"incomplete","reason":"clang_structure_unavailable","observation":null,"native_raw_diagnostic_count":null});
158+
let mut placeholder =
159+
json!({"status":"incomplete","reason":"clang_placeholder_unavailable","observation":null});
137160
let source_observation = crate::plain_syntax_source::read_plain_source(&request.source);
138161
if let Ok(source) = &source_observation {
139162
let extension = path.extension().and_then(|extension| extension.to_str());
@@ -146,20 +169,23 @@ pub(crate) fn observe(
146169
};
147170
if applicable {
148171
source_sha256 = json!(format!("{:x}", Sha256::digest(source)));
149-
(native, structure) = crate::clang_syntax_probe::observe_documentation_with_structure(
150-
request.clang_tool.as_deref().expect("工具上下文已校验"),
151-
&request.language,
152-
request.standard.as_deref().expect("标准上下文已校验"),
153-
source,
154-
deadline,
155-
cancellation,
156-
);
172+
(native, structure, placeholder) =
173+
crate::clang_syntax_probe::observe_documentation_with_placeholders(
174+
request.clang_tool.as_deref().expect("工具上下文已校验"),
175+
&request.language,
176+
request.standard.as_deref().expect("标准上下文已校验"),
177+
source,
178+
deadline,
179+
cancellation,
180+
placeholders,
181+
);
157182
if !crate::plain_syntax_source::read_plain_source(&request.source)
158183
.is_ok_and(|current| current == *source)
159184
{
160185
native["status"] = json!("incomplete");
161186
native["reason"] = json!("clang_source_changed");
162187
native["diagnostics"] = json!([]);
188+
placeholder = json!({"status":"incomplete","reason":"clang_source_changed","observation":null});
163189
structure = json!({"status":"incomplete","reason":"clang_source_changed","observation":null,"native_raw_diagnostic_count":null});
164190
}
165191
}
@@ -176,6 +202,7 @@ pub(crate) fn observe(
176202
"clang_execution_incomplete"
177203
});
178204
native["diagnostics"] = json!([]);
205+
placeholder = json!({"status":"incomplete","reason":if cancelled {"request_cancelled"}else{"clang_execution_incomplete"},"observation":null});
179206
structure = json!({"status":"incomplete","reason":if cancelled {"request_cancelled"}else{"clang_execution_incomplete"},"observation":null,"native_raw_diagnostic_count":null});
180207
}
181208
let mut documentation_findings = Vec::new();
@@ -241,5 +268,11 @@ pub(crate) fn observe(
241268
report["structural_task_workflow_status"] = json!("not_integrated");
242269
}
243270
report["next_actions"].as_array_mut().expect("固定反馈动作").push(json!("读取原生AST结构观察中的缺失文档/用途/参数/返回组件并依据真实API补充说明;已初始化工作区可读取结构任务的当前定位并运行原工具task verify;受控尝试记录已接入,可信关闭仍待接线,不把非空说明当准确性或关闭证据。"));
271+
if placeholders {
272+
report["schema_version"] = json!("0.10.0");
273+
report["documentation_placeholders"] = placeholder;
274+
report["placeholder_task_workflow_status"] = json!("not_integrated");
275+
report["next_actions"].as_array_mut().expect("固定反馈动作").push(json!("按占位观察的位置和组件补充真实用途、参数或返回说明,再以本报告原工具与标准复扫;占位规则稳定任务与专用复检尚未接入,不以结构任务消失关闭占位问题。"));
276+
}
244277
Ok(report)
245278
}

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

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,11 @@ fn observe_profile(
119119
env: BTreeMap::new(),
120120
stdin,
121121
deadline,
122-
output_limit_bytes: if matches!(profile, NativeClangProfile::DocumentationStructure)
123-
{
122+
output_limit_bytes: if matches!(
123+
profile,
124+
NativeClangProfile::DocumentationStructure
125+
| NativeClangProfile::DocumentationPlaceholders
126+
) {
124127
8 * 1024 * 1024
125128
} else {
126129
64 * 1024
@@ -164,7 +167,10 @@ fn observe_profile(
164167
let mut args: Vec<OsString> = flags.into_iter().map(OsString::from).collect();
165168
args.insert(1, OsString::from(format!("-std={standard}")));
166169
args.splice(7..7, profile.warning_flags().iter().map(OsString::from));
167-
if matches!(profile, NativeClangProfile::DocumentationStructure) {
170+
if matches!(
171+
profile,
172+
NativeClangProfile::DocumentationStructure | NativeClangProfile::DocumentationPlaceholders
173+
) {
168174
args.splice(
169175
1..1,
170176
[OsString::from("-Xclang"), OsString::from("-ast-dump=json")],
@@ -183,7 +189,11 @@ fn observe_profile(
183189
report["reason"] = json!("clang_execution_incomplete");
184190
return report;
185191
}
186-
if !matches!(profile, NativeClangProfile::DocumentationStructure) && !output.stdout.is_empty() {
192+
if !matches!(
193+
profile,
194+
NativeClangProfile::DocumentationStructure | NativeClangProfile::DocumentationPlaceholders
195+
) && !output.stdout.is_empty()
196+
{
187197
report["reason"] = json!("clang_report_invalid");
188198
return report;
189199
}
@@ -193,7 +203,11 @@ fn observe_profile(
193203
output.termination == Termination::Exited(0),
194204
) {
195205
Ok(mut rows) => {
196-
if matches!(profile, NativeClangProfile::DocumentationStructure) {
206+
if matches!(
207+
profile,
208+
NativeClangProfile::DocumentationStructure
209+
| NativeClangProfile::DocumentationPlaceholders
210+
) {
197211
report["structure_raw_diagnostic_count"] = json!(rows.len());
198212
// AST输出可触发Clang重新读取注释;按脱敏规则/行列/等级归并并保留原条数。
199213
let mut seen = BTreeSet::new();
@@ -213,18 +227,33 @@ fn observe_profile(
213227
}
214228
Err(reason) => report["reason"] = json!(reason),
215229
}
216-
if matches!(profile, NativeClangProfile::DocumentationStructure)
217-
&& matches!(
218-
report["status"].as_str(),
219-
Some("completed" | "diagnostics_observed")
220-
)
221-
&& output.termination == Termination::Exited(0)
230+
if matches!(
231+
profile,
232+
NativeClangProfile::DocumentationStructure | NativeClangProfile::DocumentationPlaceholders
233+
) && matches!(
234+
report["status"].as_str(),
235+
Some("completed" | "diagnostics_observed")
236+
) && output.termination == Termination::Exited(0)
222237
&& !report["diagnostics"]
223238
.as_array()
224239
.into_iter()
225240
.flatten()
226241
.any(|row| row["level"] == "error")
227242
{
243+
if matches!(profile, NativeClangProfile::DocumentationPlaceholders) {
244+
report["placeholder_observation"] =
245+
match codeguard_adapters::parse_clang_documentation_placeholders(
246+
&output.stdout,
247+
source,
248+
) {
249+
Ok(observation) => {
250+
json!({"status":"observed","reason":"clang_placeholder_observed","observation":observation})
251+
}
252+
Err(_) => {
253+
json!({"status":"incomplete","reason":"clang_placeholder_report_invalid","observation":null})
254+
}
255+
};
256+
}
228257
report["structure_observation"] = match codeguard_adapters::parse_clang_documentation_ast(
229258
&output.stdout,
230259
source,
@@ -250,19 +279,40 @@ pub(crate) fn observe_documentation_with_structure(
250279
deadline: Instant,
251280
cancelled: &AtomicBool,
252281
) -> (Value, Value) {
282+
let (native, structure, _) = observe_documentation_with_placeholders(
283+
tool, language, standard, source, deadline, cancelled, false,
284+
);
285+
(native, structure)
286+
}
287+
288+
/// 同次原生扫描的独立占位策略观察;enable为false时不执行新增解析,不改变历史消费协议。
289+
pub(crate) fn observe_documentation_with_placeholders(
290+
tool: &Path,
291+
language: &str,
292+
standard: &str,
293+
source: &[u8],
294+
deadline: Instant,
295+
cancelled: &AtomicBool,
296+
enable: bool,
297+
) -> (Value, Value, Value) {
253298
let mut native = observe_profile(
254299
tool,
255300
language,
256301
standard,
257302
source,
258303
deadline,
259304
cancelled,
260-
NativeClangProfile::DocumentationStructure,
305+
if enable {
306+
NativeClangProfile::DocumentationPlaceholders
307+
} else {
308+
NativeClangProfile::DocumentationStructure
309+
},
261310
);
262311
let mut structure=native.as_object_mut().and_then(|n|n.remove("structure_observation")).unwrap_or(json!({"status":"incomplete","reason":"clang_structure_unavailable","observation":null}));
263312
structure["native_raw_diagnostic_count"] = native
264313
.as_object_mut()
265314
.and_then(|n| n.remove("structure_raw_diagnostic_count"))
266315
.unwrap_or(Value::Null);
267-
(native, structure)
316+
let placeholder = native.as_object_mut().and_then(|n| n.remove("placeholder_observation")).unwrap_or(json!({"status":"incomplete","reason":"clang_placeholder_unavailable","observation":null}));
317+
(native, structure, placeholder)
268318
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ pub(crate) enum NativeClangProfile {
99
Documentation,
1010
/// 同次原生扫描输出警告和函数文档AST,结构事实不冒充原警告或完整政策。
1111
DocumentationStructure,
12+
/// 公开单文件文档反馈同次AST的独立占位策略,不修改历史结构协议。
13+
DocumentationPlaceholders,
1214
}
1315
impl NativeClangProfile {
1416
/// 返回固定原生警告参数;无运行输入,返回静态字面argv列表。
1517
pub(crate) fn warning_flags(self) -> &'static [&'static str] {
1618
match self {
1719
Self::Syntax => &["-Wall", "-Wextra", "-Wpedantic"],
18-
Self::Documentation | Self::DocumentationStructure => {
19-
&["-Wdocumentation", "-Wdocumentation-pedantic"]
20-
}
20+
Self::Documentation
21+
| Self::DocumentationStructure
22+
| Self::DocumentationPlaceholders => &["-Wdocumentation", "-Wdocumentation-pedantic"],
2123
}
2224
}
2325
}

‎crates/codeguard-cli/tests/c_family_comments_structure_cli.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn public_comments_exposes_absent_empty_and_valid_structural_descriptions() {
5454
assert_eq!(out.status.code(), Some(3));
5555
let report: Value = serde_json::from_slice(&out.stdout).unwrap();
5656
assert_eq!(report["documentation_structure"]["status"], "observed");
57-
assert_eq!(report["schema_version"], "0.5.0");
57+
assert_eq!(report["schema_version"], "0.10.0");
5858
assert_eq!(
5959
report["documentation_structure"]["observation"]["functions"][0]["missing_components"],
6060
json!(expected)
@@ -96,7 +96,7 @@ fn public_comments_exposes_absent_empty_and_valid_structural_descriptions() {
9696
.unwrap();
9797
assert_eq!(out.status.code(), Some(3));
9898
let report: Value = serde_json::from_slice(&out.stdout).unwrap();
99-
assert_eq!(report["schema_version"], "0.9.0");
99+
assert_eq!(report["schema_version"], "0.10.0");
100100
assert_eq!(report["workspace_binding"], "bound");
101101
assert_eq!(report["structural_task_workflow_status"], "partial");
102102
if missing_tool {

‎crates/codeguard-cli/tests/c_family_comments_workbench.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ fn linked_source_cannot_redirect_observation_storage_into_another_project() {
211211
.unwrap();
212212
assert_eq!(out.status.code(), Some(3));
213213
let r: Value = serde_json::from_slice(&out.stdout).unwrap();
214-
assert_eq!(r["schema_version"], "0.5.0");
214+
assert_eq!(r["schema_version"], "0.10.0");
215215
assert_eq!(r["workspace_binding"], "not_bound");
216216
assert_eq!(r["documentation_findings"], json!([]));
217217
assert_eq!(
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use serde_json::Value;
2+
use std::{fs, process::Command};
3+
4+
#[test]
5+
#[ignore = "requires explicit existing Clang via CODEGUARD_CLANG_BIN"]
6+
fn public_comments_exposes_same_scan_placeholder_policy_and_withdraws_on_failure() {
7+
let tool = std::env::var("CODEGUARD_CLANG_BIN").unwrap();
8+
let root = std::env::temp_dir().join(format!(
9+
"cg-placeholder-cli-{}-{}",
10+
std::process::id(),
11+
std::time::SystemTime::now()
12+
.duration_since(std::time::UNIX_EPOCH)
13+
.unwrap()
14+
.as_nanos()
15+
));
16+
fs::create_dir(&root).unwrap();
17+
let root = root.canonicalize().unwrap();
18+
struct Cleanup(std::path::PathBuf);
19+
impl Drop for Cleanup {
20+
fn drop(&mut self) {
21+
let _ = fs::remove_dir_all(&self.0);
22+
}
23+
}
24+
let _cleanup = Cleanup(root.clone());
25+
let source = root.join("api.c");
26+
fs::write(
27+
&source,
28+
"/// TODO.\n/// @param x TBD\n/// @return FIXME!\nint f(int x);\n",
29+
)
30+
.unwrap();
31+
let scan = |selected: &str| {
32+
let out = Command::new(env!("CARGO_BIN_EXE_codeguard"))
33+
.args(["comments", "c"])
34+
.arg(&source)
35+
.args([
36+
"--clang-tool",
37+
selected,
38+
"--standard",
39+
"c11",
40+
"--format=json",
41+
])
42+
.output()
43+
.unwrap();
44+
assert_eq!(out.status.code(), Some(3));
45+
serde_json::from_slice::<Value>(&out.stdout).unwrap()
46+
};
47+
let first = scan(&tool);
48+
assert_eq!(first["schema_version"], "0.10.0");
49+
assert_eq!(first["documentation_placeholders"]["status"], "observed");
50+
assert_eq!(
51+
first["documentation_placeholders"]["observation"]["positions"]
52+
.as_array()
53+
.unwrap()
54+
.len(),
55+
3
56+
);
57+
assert_eq!(
58+
first["documentation_placeholders"]["observation"]["authority"],
59+
"codeguard_structural_policy"
60+
);
61+
assert_eq!(first["placeholder_task_workflow_status"], "not_integrated");
62+
assert!(!root.join(".codeguard").exists());
63+
fs::write(&source,"/// Explain TODO queue processing.\n/// @param x The input value.\n/// @return The input unchanged.\nint f(int x);\n").unwrap();
64+
let repaired = scan(&tool);
65+
assert_eq!(
66+
repaired["documentation_placeholders"]["observation"]["positions"],
67+
serde_json::json!([])
68+
);
69+
assert_eq!(repaired["detailed_contract_qualification"], "not_granted");
70+
let missing = scan(root.join("missing-clang").to_str().unwrap());
71+
assert_eq!(
72+
missing["documentation_placeholders"]["status"],
73+
"incomplete"
74+
);
75+
assert!(missing["documentation_placeholders"]["observation"].is_null());
76+
if let Ok(destination) = std::env::var("CODEGUARD_PLACEHOLDER_CLI_EVIDENCE") {
77+
assert!(std::path::Path::new(&destination).is_absolute());
78+
fs::write(
79+
destination,
80+
serde_json::to_vec_pretty(&serde_json::json!([first, repaired, missing])).unwrap(),
81+
)
82+
.unwrap();
83+
}
84+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,3 +1282,13 @@ The adapter SHALL expose a separately versioned, source-associated placeholder o
12821282
- **THEN** the observation SHALL retain the precise function position and component without copying comment text
12831283
- **WHEN** TODO appears within an explanatory sentence or a void return is not applicable
12841284
- **THEN** this rule SHALL not invent a placeholder or required return finding
1285+
1286+
### Requirement: Public standalone comments SHALL expose same-scan placeholder observations without false task coverage
1287+
1288+
The standalone comments entry SHALL use the original frozen stdin Clang AST for a separately versioned placeholder observation. It SHALL NOT launch another compiler scan. Source instability, tool failure, cancellation or expired deadline SHALL withdraw that observation. Feedback SHALL distinguish this Codeguard policy from native warnings and retain exact function/component positions and original-tool re-scan guidance. Historical shared check/hook and stored structural protocols SHALL remain unchanged until their own integration is accepted. Until stable placeholder tasks and original-task verification are implemented, feedback SHALL explicitly report placeholder_task_workflow_status=not_integrated and SHALL NOT equate absence of structural tasks with resolution of a placeholder issue.
1289+
1290+
#### Scenario: The native compiler emits no warning for a placeholder description
1291+
- **WHEN** public comments reads a supported full description consisting of an explicit placeholder
1292+
- **THEN** separately versioned feedback SHALL expose the policy positions without inventing a native diagnostic or claiming detailed qualification
1293+
- **WHEN** the original tool is unavailable or the source changes during scanning
1294+
- **THEN** placeholder observation SHALL be incomplete with no usable positions

0 commit comments

Comments
 (0)