Skip to content

Commit 194eb95

Browse files
committed
并行推进八条切片:补反例、修取消契约、固化适用性
按文件所有权切分八条独立切片并行推进,全部 RED→GREEN,全量回归 146 通过。 Java 文档(6.3/15.3):补生成代码/文件预算/重载三类零覆盖反例;JDK 工作台加 128MiB 总预算与重复路径拒绝,修正与 Maven 工作台、同步侧之间的防线不对称。 Java 规范(6.2/15.4):files[] 增 configuration_reason,把 configured/missing/ invalid/unknown 与底层原因、下一步动作逐文件透出。规则实际加载的直接原生证据 仍缺(隔离 Maven 仓库已不在磁盘),不勾选。 Java 漏洞(6.8/15.5):修复真实产品缺陷——OWASP Maven 插件 requiresOnline 与旧 探针的 -o 冲突,真实插件此前从未可执行,所有验收都是模拟工具。首次让真实 dependency-check-maven 10.0.4 经 CLI 全链路运行,库不匹配与空库均如实 incomplete。 Node(7.3):ts/tsx/mts/cts 按包根冻结 tsconfig 字节身份,链接/目录/特殊文件在任何 原生执行前以 eslint_tsconfig_untrusted 拒绝。 Rust/Zig/Go:修请求取消契约——Zig 曾把取消改写成版本不符、非 UTF-8 版本输出冒充 工具缺失、取消后仍跑 WASM;Go 取消仍退出 3。取消/超时语义现正确保持。 Kotlin/Swift/Ruby(8.7/8.10):适用性档案与规则配置发现纯 Rust 固化,状态机区分 configured/missing/invalid/unknown,未运行任何外部工具。接线需改 schema 共享文件。 Shell/Erlang/C++(7.4):C/C++ lint 入口纳入头文件(.h/.hpp/.hh/.hxx/.H/.h++), .cpp 下 .h 保持歧义拒绝;zsh 与 external-sources 隔离反例补齐。真实 clang 21 / ShellCheck 0.11 / OTP 28 均实际运行通过。 生产验收审计:产出 tests/acceptance/production-acceptance-audit-2026-10-07.md。 核实 66/288 属实且无虚报完成度(23 契约回填 + 18 真实完成 + 25 局部切片); 228 能力单元全 blocked、grammar 0/32 四源一致。另发现 tasks.md 13.4.4 提交哈希 损坏、计划 JSON 三条 source_hashes 滞后,待修。 所有切片均未授予生产资格,父任务保持未勾选。工具条件测试一律记未运行。
1 parent 2b68317 commit 194eb95

39 files changed

Lines changed: 4595 additions & 24 deletions

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

Lines changed: 669 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 467 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 622 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,26 @@ pub(crate) fn observe(args: &SyntaxLintArguments, deadline: Instant) -> Value {
4949
}) {
5050
return report;
5151
}
52-
if !((args.language == "c" && path.extension().is_some_and(|s| s == "c"))
52+
// 头文件进入同语言显式观察;`.h` 在 cpp 语言下保持 C/C++ 歧义拒绝,不猜语言。
53+
if !((args.language == "c" && path.extension().is_some_and(|s| s == "c" || s == "h"))
5354
|| (args.language == "cpp"
54-
&& path
55-
.extension()
56-
.and_then(|s| s.to_str())
57-
.is_some_and(|s| matches!(s, "cpp" | "cc" | "cxx" | "C" | "CPP" | "cp" | "c++"))))
55+
&& path.extension().and_then(|s| s.to_str()).is_some_and(|s| {
56+
matches!(
57+
s,
58+
"cpp"
59+
| "cc"
60+
| "cxx"
61+
| "C"
62+
| "CPP"
63+
| "cp"
64+
| "c++"
65+
| "hpp"
66+
| "hh"
67+
| "hxx"
68+
| "H"
69+
| "h++"
70+
)
71+
})))
5872
{
5973
return report;
6074
}

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

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ fn observe_captured(
249249
}) {
250250
return feedback("eslint_source_kind_not_supported");
251251
}
252+
// TS 方言按包根绑定 tsconfig.json 输入身份;缺失不阻塞,非普通文件不可信。
253+
// 该输入只用于冻结本轮观察,不证明项目 parser 实际读取或适用该文件。
254+
let tsconfig = match typescript_project_config(cwd, &source) {
255+
Ok(value) => value.filter(|path| path != config && path != entry && path != node),
256+
Err(reason) => return feedback(reason),
257+
};
252258
// 临时区仅承载本轮原生报告;工作台另存脱敏观察,不声称保留完整原生证据。
253259
let id = format!(
254260
"eslint-{}-{}",
@@ -261,12 +267,16 @@ fn observe_captured(
261267
return feedback("eslint_private_workspace_unavailable");
262268
};
263269
let mut hashes = BTreeMap::new();
264-
for (path, limit) in [
270+
let mut inputs = vec![
265271
(node, 128 * 1024 * 1024),
266272
(entry, 16 * 1024 * 1024),
267273
(config, 1024 * 1024),
268274
(&source, 16 * 1024 * 1024),
269-
] {
275+
];
276+
if let Some(tsconfig) = &tsconfig {
277+
inputs.push((tsconfig, 1024 * 1024));
278+
}
279+
for (path, limit) in inputs {
270280
if Instant::now() >= deadline {
271281
return feedback("request_deadline_exceeded");
272282
}
@@ -333,6 +343,103 @@ pub(crate) fn feedback(reason: &str) -> Value {
333343
"eslint_config_untrusted"=>"项目本地 ESLint 已发现,但配置是链接、特殊文件或不可读;恢复原配置后原生复检",
334344
"eslint_scope_requires_explicit_file"=>"当前入口需显式单文件;项目目录与完整源集调度尚未接通,不将目录标为已检查",
335345
"request_cancelled"|"request_deadline_exceeded"=>"重新安排原工具检查预算;中断不要求修改源码",
346+
"eslint_parser_or_configuration_diagnostic"=>"原生 ESLint 报告含解析级致命诊断;先核对项目 parser 是否覆盖该文件方言(如 typescript-eslint 与 tsconfig),复现同一文件的解析错误后再修复源码或恢复适用 parser,并用同一入口与配置复检;不能据此关闭其它文件的检查",
347+
"eslint_unattributed_diagnostic"=>"报告存在无法归属规则的诊断,常见于文件被原配置 ignores/files 排除;核对原配置覆盖与本轮范围后用同一入口复检;被排除文件不能记为已检查",
348+
"eslint_version_mismatch"=>"本地 ESLint 包声明版本与原生入口实际输出不一致;核对 node_modules 内包身份与版本后用受控 Node 复检,不修改源码",
349+
"eslint_report_invalid"|"eslint_report_read_failed"|"eslint_execution_evidence_failed"=>"本轮原生报告缺失或不可解析;先核对原配置能否加载(配置无效时原生进程可能不产出报告),重跑同一命令并保留证据,不把无报告当作干净或源码违规",
350+
"eslint_tsconfig_untrusted"=>"TypeScript 方言检查需绑定包内 tsconfig.json,当前它是链接或特殊文件;恢复普通文件后用同一入口和配置复检,不修改源码",
336351
_=>"核对原配置、parser/插件与本轮规则;按原生发现修复后用同一入口和配置复检,抑制及未知覆盖需核查,任务不能自行关闭",
337352
}})
338353
}
354+
/// TS 方言源码在包根的 tsconfig.json 输入;缺失返回 None 不阻塞,
355+
/// 链接/特殊文件/不可判定返回不可信原因,观察保持未完成。
356+
fn typescript_project_config(
357+
cwd: &Path,
358+
source: &Path,
359+
) -> Result<Option<std::path::PathBuf>, &'static str> {
360+
if !source
361+
.extension()
362+
.and_then(|extension| extension.to_str())
363+
.is_some_and(|extension| matches!(extension, "ts" | "tsx" | "mts" | "cts"))
364+
{
365+
return Ok(None);
366+
}
367+
let path = cwd.join("tsconfig.json");
368+
match std::fs::symlink_metadata(&path) {
369+
Ok(metadata) if metadata.is_file() => Ok(Some(path)),
370+
Ok(_) => Err("eslint_tsconfig_untrusted"),
371+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
372+
Err(_) => Err("eslint_tsconfig_untrusted"),
373+
}
374+
}
375+
376+
#[cfg(test)]
377+
mod tests {
378+
use super::typescript_project_config;
379+
use std::{fs, os::unix::fs::symlink, path::PathBuf};
380+
381+
#[test]
382+
fn tsconfig_binding_routes_only_typescript_dialects_and_regular_files() {
383+
let root = std::env::temp_dir()
384+
.canonicalize()
385+
.unwrap()
386+
.join(format!("cg-tsconfig-route-{}", std::process::id()));
387+
fs::create_dir(&root).unwrap();
388+
let (js, mjs, ts, tsx, mts, cts) = (
389+
root.join("app.js"),
390+
root.join("app.mjs"),
391+
root.join("app.ts"),
392+
root.join("app.tsx"),
393+
root.join("app.mts"),
394+
root.join("app.cts"),
395+
);
396+
for path in [&js, &mjs, &ts, &tsx, &mts, &cts] {
397+
fs::write(path, "x").unwrap();
398+
}
399+
// 无 tsconfig:JS/TS 方言都不阻塞。
400+
for path in [&js, &mjs, &ts, &tsx] {
401+
assert_eq!(typescript_project_config(&root, path), Ok(None));
402+
}
403+
fs::write(root.join("tsconfig.json"), "{}").unwrap();
404+
// JS 方言不绑定;TS 方言按包根绑定。
405+
assert_eq!(typescript_project_config(&root, &js), Ok(None));
406+
assert_eq!(typescript_project_config(&root, &mjs), Ok(None));
407+
let expected = Ok(Some(root.join("tsconfig.json")));
408+
assert_eq!(typescript_project_config(&root, &ts), expected);
409+
assert_eq!(typescript_project_config(&root, &tsx), expected);
410+
assert_eq!(typescript_project_config(&root, &mts), expected);
411+
assert_eq!(typescript_project_config(&root, &cts), expected);
412+
// 链接/目录不可信:不执行原生也不宣称绑定成功。
413+
fs::remove_file(root.join("tsconfig.json")).unwrap();
414+
fs::write(root.join("tsconfig.real.json"), "{}").unwrap();
415+
symlink(root.join("tsconfig.real.json"), root.join("tsconfig.json")).unwrap();
416+
assert_eq!(
417+
typescript_project_config(&root, &ts),
418+
Err("eslint_tsconfig_untrusted")
419+
);
420+
fs::remove_file(root.join("tsconfig.json")).unwrap();
421+
fs::create_dir(root.join("tsconfig.json")).unwrap();
422+
assert_eq!(
423+
typescript_project_config(&root, &ts),
424+
Err("eslint_tsconfig_untrusted")
425+
);
426+
fs::remove_dir_all(&root).unwrap();
427+
}
428+
429+
#[test]
430+
fn tsconfig_binding_allows_foreign_package_root_without_input() {
431+
let root = std::env::temp_dir()
432+
.canonicalize()
433+
.unwrap()
434+
.join(format!("cg-tsconfig-foreign-{}", std::process::id()));
435+
fs::create_dir(&root).unwrap();
436+
let source = root.join("app.ts");
437+
fs::write(&source, "x").unwrap();
438+
// 包根无 tsconfig 时 TS 方言检查保持可执行,不虚构输入。
439+
assert_eq!(
440+
typescript_project_config(&PathBuf::from("/definitely/not/here"), &source),
441+
Ok(None)
442+
);
443+
fs::remove_dir_all(&root).unwrap();
444+
}
445+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ pub(crate) fn valid_reason(reason: &str) -> bool {
196196
| "eslint_version_evidence_failed"
197197
| "eslint_version_execution_incomplete"
198198
| "eslint_version_mismatch"
199+
| "eslint_tsconfig_untrusted"
199200
| "eslint_execution_evidence_failed"
200201
| "eslint_report_read_failed"
201202
| "eslint_native_execution_incomplete"
@@ -355,6 +356,9 @@ pub(crate) fn guidance(root: &Path, fact: &Value) -> Value {
355356
Some("eslint_config_selection_unresolved" | "eslint_config_untrusted") => {
356357
"项目本地 ESLint 已发现;核对原项目实际选中的 flat config、文件类型与来源,恢复配置后原生复检,不修改无依据源码"
357358
}
359+
Some("eslint_tsconfig_untrusted") => {
360+
"TypeScript 方言检查需绑定包内 tsconfig.json,当前它是链接或特殊文件;恢复普通文件后用同一入口和配置复检,不修改无依据源码"
361+
}
358362
Some("eslint_local_entry_untrusted" | "eslint_local_dependency_path_untrusted") => {
359363
"项目本地 ESLint 路径不可信;核对链接、文件类型和来源后原生复检,不重复安装或修改无依据源码"
360364
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ pub fn run_eslint_probe(req: &EslintProbeRequest, cancelled: &AtomicBool) -> Esl
4141
(&req.command.config, 1024 * 1024),
4242
];
4343
inputs.extend(req.command.sources.iter().map(|p| (p, 16 * 1024 * 1024)));
44+
// 与请求构造方镜像:TS 方言且包根存在 tsconfig.json 时冻结为输入身份。
45+
// 任何存在形态都纳入核对,中途换成链接或新建都能被识别为输入变化而非静默放行。
46+
let mut tsconfig_input: Option<PathBuf> = None;
47+
if req.command.sources.iter().any(|p| {
48+
p.extension()
49+
.and_then(|extension| extension.to_str())
50+
.is_some_and(|extension| matches!(extension, "ts" | "tsx" | "mts" | "cts"))
51+
}) {
52+
let tsconfig = req.cwd.join("tsconfig.json");
53+
if std::fs::symlink_metadata(&tsconfig).is_ok()
54+
&& !inputs.iter().any(|(path, _)| **path == tsconfig)
55+
{
56+
tsconfig_input = Some(tsconfig);
57+
}
58+
}
59+
if let Some(tsconfig) = &tsconfig_input {
60+
inputs.push((tsconfig, 1024 * 1024));
61+
}
4462
if req.expected_sha256.len()
4563
!= inputs
4664
.iter()

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ pub fn run(args: &[String]) -> ExitCode {
6262
let deadline = started + Duration::from_millis(arguments.timeout_ms);
6363
let root = match arguments.root.canonicalize() {
6464
Ok(path) if path.is_dir() => path,
65-
_ => return emit(empty_feedback(), arguments.json),
65+
_ => {
66+
let mut feedback = empty_feedback();
67+
mark_request_cancellation(&mut feedback);
68+
return emit(feedback, arguments.json);
69+
}
6670
};
6771
let cancelled = AtomicBool::new(false);
6872
let selection = crate::go_tool_selection::GoToolSelection::discover(arguments.tool);
@@ -77,9 +81,21 @@ pub fn run(args: &[String]) -> ExitCode {
7781
arguments.json,
7882
);
7983
}
84+
// 持久化身份保持可导入的 incomplete/3;公开反馈按取消契约标 130。
85+
mark_request_cancellation(&mut feedback);
8086
emit(feedback, arguments.json)
8187
}
8288

89+
/// 请求取消只改公开反馈的退出语义;不撤销已观察的原生结果或发现。
90+
pub(crate) fn mark_request_cancellation(feedback: &mut Value) {
91+
if feedback["reason"] == "request_cancelled"
92+
|| codeguard_runtime::sigint_cancellation_requested()
93+
{
94+
feedback["command_status"] = json!("cancelled");
95+
feedback["exit_code"] = json!(130);
96+
}
97+
}
98+
8399
/// 与 `check all` 共用同一受控 Go 观察,不赋予策略或门禁权威。
84100
pub(crate) fn observe_for_check(
85101
root: &Path,
@@ -910,9 +926,13 @@ fn parse_go_version(stdout: &[u8]) -> Option<&str> {
910926
}
911927

912928
fn emit(feedback: Value, json_format: bool) -> ExitCode {
929+
let exit = feedback["exit_code"].as_u64().unwrap_or(3) as u8;
913930
if json_format {
914931
println!("{feedback}");
915932
} else {
933+
if feedback["command_status"] == "cancelled" {
934+
println!("Go 检查已取消(退出 130);保留已观察结果,不签发任何通过结论");
935+
}
916936
println!(
917937
"Go vet:{}",
918938
feedback["native_status"].as_str().unwrap_or("incomplete")
@@ -941,5 +961,5 @@ fn emit(feedback: Value, json_format: bool) -> ExitCode {
941961
feedback["reason"].as_str().unwrap_or("unknown")
942962
);
943963
}
944-
ExitCode::from(3)
964+
ExitCode::from(exit)
945965
}

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ pub(crate) fn run(
1010
deadline: Instant,
1111
json_output: bool,
1212
) -> ExitCode {
13+
// 取消后不得继续启动 WASM 候选初检;保留取消语义并按契约返回 130。
14+
if native["reason"] == "request_cancelled" || codeguard_runtime::sigint_cancellation_requested()
15+
{
16+
let report = json!({"schema_version":"0.7.0","report_type":"go_lint_fallback_feedback","operation":"lint","language":"go",
17+
"command_status":"cancelled","exit_code":130,"authority":"local_unverified","coverage_proven":false,"delivery_decision":"not_evaluated",
18+
"tool_selection":selection,"native_report":native,
19+
"syntax_candidates":{"status":"not_run","reason":"request_cancelled","execution_phase":"after_native","authority":"candidate_unqualified","delivery_decision":"incomplete",
20+
"source_file_count":0,"skipped_count":0,"unrouted_count":0,"native_preferred_count":0,"observations":[],
21+
"next_action":"请求已取消;不缓存也不签发 clean,需要时重新执行原检查"},
22+
"syntax_tasks":{"status":"not_attempted"},
23+
"preliminary_result":"incomplete","native_tool_requirement":"required",
24+
"next_actions":["request_cancelled_rerun_after_review"]});
25+
if json_output {
26+
println!("{report}");
27+
} else {
28+
println!("Go 检查已取消(退出 130);候选初检未运行,不签发任何通过结论");
29+
}
30+
return ExitCode::from(130);
31+
}
1332
let syntax = observe(root, deadline);
1433
let tasks = crate::syntax_confirmation::persist(root, &syntax, deadline);
1534
let result = preliminary_result(&syntax);
@@ -18,11 +37,14 @@ pub(crate) fn run(
1837
} else {
1938
"required"
2039
};
21-
let report = json!({"schema_version":"0.7.0","report_type":"go_lint_fallback_feedback","operation":"lint","language":"go",
40+
let mut report = json!({"schema_version":"0.7.0","report_type":"go_lint_fallback_feedback","operation":"lint","language":"go",
2241
"command_status":"incomplete","exit_code":3,"authority":"local_unverified","coverage_proven":false,"delivery_decision":"not_evaluated",
2342
"tool_selection":selection,"native_report":native,"syntax_candidates":syntax,"syntax_tasks":tasks,
2443
"preliminary_result":result,"native_tool_requirement":requirement,
2544
"next_actions":if requirement == "required" {json!(["prepare_project_native_go_then_confirm_candidates","preserve_source_until_native_confirmation"])}else {json!(["recommend_project_native_go_for_complete_lint","retain_unfinished_project_obligations"])}});
45+
// 候选初检期间的取消同样保留取消语义;已取得的候选观察不撤回也不升级为通过。
46+
crate::go_lint_command::mark_request_cancellation(&mut report);
47+
let exit = report["exit_code"].as_u64().unwrap_or(3) as u8;
2648
if json_output {
2749
println!("{report}");
2850
} else {
@@ -57,7 +79,7 @@ pub(crate) fn run(
5779
report["syntax_tasks"]["status"]
5880
);
5981
}
60-
ExitCode::from(3)
82+
ExitCode::from(exit)
6183
}
6284

6385
#[cfg(feature = "wasm-precheck")]

0 commit comments

Comments
 (0)