From 6b0f46deb5d76316c72c480f01646b16c923593e Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:02:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5=20Python=20WASM=20=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E5=80=99=E9=80=89=E5=85=9C=E5=BA=95=E5=B9=B6=E4=BF=9D?= =?UTF-8?q?=E7=95=99=E5=8E=9F=E7=94=9F=E4=BC=98=E5=85=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- README.zh-CN.md | 6 +- .../codeguard-cli/src/eslint_preparation.rs | 2 +- .../src/eslint_syntax_evidence.rs | 5 +- crates/codeguard-cli/src/eslint_workbench.rs | 1 + crates/codeguard-cli/src/lib.rs | 2 + .../codeguard-cli/src/python_lint_command.rs | 79 +- .../src/python_syntax_precheck.rs | 156 ++++ .../src/syntax_worker_command.rs | 1 + crates/codeguard-cli/tests/lint_python_cli.rs | 4 + .../tests/python_syntax_fallback_candidate.rs | 153 ++++ docs/Codeguard-Architecture.md | 4 +- docs/Codeguard-Architecture.zh_CN.md | 4 +- docs/Codeguard-Command-Reference.md | 2 +- docs/Codeguard-Command-Reference.zh_CN.md | 2 +- docs/Codeguard-Technical-Design.md | 4 +- docs/Codeguard-Technical-Design.zh_CN.md | 4 +- docs/Codeguard-Trust-and-Distribution.md | 2 +- .../Codeguard-Trust-and-Distribution.zh_CN.md | 2 +- .../introduce-rust-codeguard-cli/tasks.md | 8 +- .../python-lint-feedback-v0.14.schema.json | 860 ++++++++++++++++++ .../python-syntax-fallback-candidate.md | 39 + 22 files changed, 1320 insertions(+), 26 deletions(-) create mode 100644 crates/codeguard-cli/src/python_syntax_precheck.rs create mode 100644 crates/codeguard-cli/tests/python_syntax_fallback_candidate.rs create mode 100644 schemas/python-lint-feedback-v0.14.schema.json create mode 100644 tests/acceptance/python-syntax-fallback-candidate.md diff --git a/README.md b/README.md index 81cc7a65..5de1c7a1 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,9 @@ cargo build --locked -p codeguard-cli Version output includes `cli_version`, `target`, and protocol information. Discovery and init preview return observations and unknowns. Dry-run creates neither `AGENTS.md` nor a project data directory. Add Cargo `--offline` only when dependencies are cached. -The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with pinned Java, TypeScript, and TSX grammar candidates. It currently reaches only single-file `lint java` and `lint typescript` requests without explicit native context; `.tsx` uses its own grammar. It is absent from the published npm `0.1.2` package. No `no_std`, completed project-wide WASM fallback, zero-unsafe, or fastest-runtime claim is made. Runtime OS calls require safety review; a comprehensive security audit is not claimed. +The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with pinned Java, Python, TypeScript, and TSX grammar candidates. Java and TypeScript/TSX have narrow single-file fallback paths; `lint python` can provide bounded suspected syntax positions when Ruff is unavailable or no project Ruff configuration is declared. Native Ruff results take priority. It is absent from the published npm `0.1.2` package. No `no_std`, completed project-wide WASM fallback, zero-unsafe, or fastest-runtime claim is made. Runtime OS calls require safety review; a comprehensive security audit is not claimed. -The current source-built CLI's `codeguard grammar status --format=json` reports the coverage gap without loading grammars or running lint: the pinned CodeGraph source has 30 vendored WASM files plus two distinct grammars resolved through its dependency; CodeGuard has four unqualified asset candidates (Java, Python, TypeScript, TSX) and zero released syntax capabilities. Python is pinned and Rust-loadable but has no CLI fallback path. The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB source asset also exceeds the current Rust loader's 8 MiB limit. +The current source-built CLI's `codeguard grammar status --format=json` reports the coverage gap without loading grammars or running lint: the pinned CodeGraph source has 30 vendored WASM files plus two distinct grammars resolved through its dependency; CodeGuard has four unqualified asset candidates (Java, Python, TypeScript, TSX) and zero released syntax capabilities. Python has a source-build-only, unqualified CLI fallback path; its report remains incomplete and cannot approve delivery. The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB source asset also exceeds the current Rust loader's 8 MiB limit. ### npm installation and one-off use @@ -181,7 +181,7 @@ Generic `dependencies`, generic `security`, arbitrary category/language combinat **The published `0.1.2` does not yet implement bundled WASM fallback or automatic conversation delivery.** The existing commands remain the unified entry points; native adapter-specific prerequisites still apply today: -An opt-in source build with `--features codeguard-cli/wasm-precheck` has narrow TypeScript and Java single-file candidate paths. For TypeScript, a visible local ESLint 10 entry and one flat config take priority: when an executable Node is resolved from `PATH`, the CLI runs its existing bounded native probe; without Node it reports the setup gap. Only when the project-local ESLint package is not observed can the bundled TypeScript/TSX candidate provide suspected syntax positions; an untrusted entry, package identity or config returns a specific setup blocker. The Java candidate likewise remains incomplete; no bundled grammar has an accepted language/dialect range. Native observations and prechecks never approve delivery. Default and published binaries keep their previous native-context behavior. [TypeScript acceptance](tests/acceptance/typescript-syntax-fallback-candidate.md) · [native-first acceptance](tests/acceptance/native-first-eslint-candidate.md) · [Java acceptance](tests/acceptance/java-syntax-fallback-candidate.md). +An opt-in source build with `--features codeguard-cli/wasm-precheck` has narrow TypeScript and Java single-file candidate paths. For TypeScript, a visible local ESLint 10 entry and one flat config take priority: when an executable Node is resolved from `PATH`, the CLI runs its existing bounded native probe; without Node it reports the setup gap. Only when the project-local ESLint package is not observed can the bundled TypeScript/TSX candidate provide suspected syntax positions; an untrusted entry, package identity or config returns a specific setup blocker. The Java candidate likewise remains incomplete. Python `lint` first preserves Ruff results, then can attach suspected positions for missing Ruff or undeclared project Ruff configuration; a broken Ruff configuration stays a setup blocker. No bundled grammar has an accepted language/dialect range. Native observations and prechecks never approve delivery. Default and published binaries keep their previous native-context behavior. [TypeScript acceptance](tests/acceptance/typescript-syntax-fallback-candidate.md) · [native-first acceptance](tests/acceptance/native-first-eslint-candidate.md) · [Java acceptance](tests/acceptance/java-syntax-fallback-candidate.md). With an initialized `--workspace`, that TypeScript/TSX fallback now returns a real, stable native-confirmation task ID after sync; repeated WASM scans never close it. A versioned local report now preserves bounded suspected positions with source and grammar identities; capability-matched native closure still needs implementation. [Workbench acceptance](tests/acceptance/typescript-syntax-confirmation-task.md). diff --git a/README.zh-CN.md b/README.zh-CN.md index d19d5c5c..feffcdd2 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -94,9 +94,9 @@ cargo build --locked -p codeguard-cli 版本输出包含 `cli_version`、`target` 和协议信息。发现与 init 预览返回观察及未知项。Dry-run 不创建 `AGENTS.md` 或项目数据目录。仅在依赖已缓存时为 Cargo 添加 `--offline`。 -工作区要求 `std`。可选 `wasm-precheck` 构建特性已用固定的 Java、TypeScript、TSX grammar 候选资产与有界 Rust worker,为缺少显式原生上下文的 `lint java`、`lint typescript` 单文件请求提供疑似语法观察;`.tsx` 使用独立 grammar。公开 npm `0.1.2` 不含该路径。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。 +工作区要求 `std`。可选 `wasm-precheck` 构建特性已用固定的 Java、Python、TypeScript、TSX grammar 候选资产与有界 Rust worker。Java 和 TypeScript/TSX 有局部单文件兜底;`lint python` 在 Ruff 不可用或项目未声明 Ruff 配置时可报告有界疑似语法位置,原生 Ruff 结果优先。公开 npm `0.1.2` 不含该路径。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。 -当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有 Java、Python、TypeScript、TSX 四份未验收资产候选,已发行语法能力为零。Python 已固定并通过 Rust 加载试验,但尚未接入 CLI 兜底。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 来源资产约 16.4 MB,还超过当前 Rust 加载器的 8 MiB 限额。 +当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有 Java、Python、TypeScript、TSX 四份未验收资产候选,已发行语法能力为零。Python 已有仅限源码可选构建的 CLI 候选兜底,结果保持不完整,不能批准交付。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 来源资产约 16.4 MB,还超过当前 Rust 加载器的 8 MiB 限额。 ### npm 安装与一次性调用 @@ -180,7 +180,7 @@ Python 编辑快反馈可执行 `codeguard lint python . --file src/changed.py - **公开 `0.1.2` 尚未实现内置 WASM 兜底或自动对话交付。** 保留已有命令作为统一入口;当前仍须满足各原生适配器的参数和前置条件: -可选源码构建已有 Java/TypeScript 单文件候选路径。TypeScript 项目若有可见的本地 ESLint 10 入口和唯一 flat config,会先尝试从 `PATH` 解析 Node 并运行既有有界原生探针;缺 Node 时报告准备缺口;仅本地 ESLint 包未观察到时才提供 TypeScript/TSX 疑似语法初检。入口、包身份或配置不可信时保留具体环境阻塞。Java 候选仍须原生确认,所有内置 grammar 的版本/方言尚未验收,原生局部观察和初检都不能批准交付。默认及公开二进制沿用既有原生上下文路径,见 [Java 局部验收](tests/acceptance/java-syntax-fallback-candidate.md)、[TypeScript 局部验收](tests/acceptance/typescript-syntax-fallback-candidate.md)与[原生优先局部验收](tests/acceptance/native-first-eslint-candidate.md)。 +可选源码构建已有 Java/TypeScript 单文件候选路径。TypeScript 项目若有可见的本地 ESLint 10 入口和唯一 flat config,会先尝试从 `PATH` 解析 Node 并运行既有有界原生探针;缺 Node 时报告准备缺口;仅本地 ESLint 包未观察到时才提供 TypeScript/TSX 疑似语法初检。入口、包身份或配置不可信时保留具体环境阻塞。Java 候选仍须原生确认。Python `lint` 先保留 Ruff 结果,在 Ruff 缺失或项目未声明配置时补充疑似位置;损坏的 Ruff 配置仍是环境阻塞。所有内置 grammar 的版本/方言尚未验收,原生局部观察和初检都不能批准交付。默认及公开二进制沿用既有原生上下文路径,见 [Java 局部验收](tests/acceptance/java-syntax-fallback-candidate.md)、[TypeScript 局部验收](tests/acceptance/typescript-syntax-fallback-candidate.md)与[原生优先局部验收](tests/acceptance/native-first-eslint-candidate.md)。 已初始化工作区使用 `--workspace` 时,TypeScript/TSX 候选回退在同步后返回真实且稳定的原生确认任务 ID;反复 WASM 扫描不会自行关闭任务。版本化本地报告已按源码与 grammar 身份保存有界疑似位置;能力匹配的原生关闭仍待实现,见[局部验收](tests/acceptance/typescript-syntax-confirmation-task.md)。 diff --git a/crates/codeguard-cli/src/eslint_preparation.rs b/crates/codeguard-cli/src/eslint_preparation.rs index e3d41ac8..829a2931 100644 --- a/crates/codeguard-cli/src/eslint_preparation.rs +++ b/crates/codeguard-cli/src/eslint_preparation.rs @@ -66,7 +66,7 @@ pub(crate) fn prepare( "affected_paths":[relative],"source_sha256":format!("{:x}",Sha256::digest(bytes))}), ) } -#[cfg(unix)] +#[cfg(all(unix, feature = "wasm-precheck"))] pub(crate) fn prepare_with_syntax( root: &Path, args: &EslintLintArguments, diff --git a/crates/codeguard-cli/src/eslint_syntax_evidence.rs b/crates/codeguard-cli/src/eslint_syntax_evidence.rs index b94e651b..17a69ada 100644 --- a/crates/codeguard-cli/src/eslint_syntax_evidence.rs +++ b/crates/codeguard-cli/src/eslint_syntax_evidence.rs @@ -1,11 +1,14 @@ //! ESLint 原生确认任务中的脱敏 WASM 疑似位置;只具待核实证据权威。 use codeguard_adapters::bundled_grammar_candidates; -use serde_json::{Value, json}; +use serde_json::Value; +#[cfg(feature = "wasm-precheck")] +use serde_json::json; use sha2::{Digest, Sha256}; /// 从当轮候选反馈提取有界坐标,不保存源码片段或语法节点文本。 /// 参数为初检反馈、工作区相对路径和当前源码字节;返回绑定固定 grammar 的疑似证据。 +#[cfg(feature = "wasm-precheck")] pub(crate) fn project(precheck: &Value, scope: &str, source: &[u8]) -> Result { let observations = precheck["observations"] .as_array() diff --git a/crates/codeguard-cli/src/eslint_workbench.rs b/crates/codeguard-cli/src/eslint_workbench.rs index b43f588b..de39b031 100644 --- a/crates/codeguard-cli/src/eslint_workbench.rs +++ b/crates/codeguard-cli/src/eslint_workbench.rs @@ -28,6 +28,7 @@ pub(crate) fn connect_preparation(root: &Path, args: &EslintLintArguments, reaso Err(reason) => json!({"status":reason}), } } +#[cfg(feature = "wasm-precheck")] pub(crate) fn connect_syntax_preparation( root: &Path, args: &EslintLintArguments, diff --git a/crates/codeguard-cli/src/lib.rs b/crates/codeguard-cli/src/lib.rs index 8b216fdc..d1356042 100644 --- a/crates/codeguard-cli/src/lib.rs +++ b/crates/codeguard-cli/src/lib.rs @@ -148,6 +148,8 @@ pub(crate) mod python_cve_task_recheck; pub mod python_lint_command; #[cfg(unix)] pub mod python_lint_scan; +#[cfg(all(unix, feature = "wasm-precheck"))] +mod python_syntax_precheck; pub mod quality_policy_candidate; #[cfg(unix)] mod report_export; diff --git a/crates/codeguard-cli/src/python_lint_command.rs b/crates/codeguard-cli/src/python_lint_command.rs index 988df87d..ccfb3e56 100644 --- a/crates/codeguard-cli/src/python_lint_command.rs +++ b/crates/codeguard-cli/src/python_lint_command.rs @@ -105,10 +105,14 @@ pub fn run(args: &[String]) -> ExitCode { feedback["scan_scope"] = Value::String("discovered_python".into()); feedback["requested_paths"] = Value::Null; } - let request_cancelled = codeguard_runtime::sigint_cancellation_requested() + let cancellation_observed = codeguard_runtime::sigint_cancellation_requested() || feedback["incomplete_reasons"] .as_array() .is_some_and(|reasons| reasons.iter().any(|reason| reason == "request_cancelled")); + #[cfg(feature = "wasm-precheck")] + let mut request_cancelled = cancellation_observed; + #[cfg(not(feature = "wasm-precheck"))] + let request_cancelled = cancellation_observed; if request_cancelled { feedback["command_status"] = Value::String("cancelled".into()); feedback["exit_code"] = Value::from(130); @@ -118,6 +122,51 @@ pub fn run(args: &[String]) -> ExitCode { } } } + #[cfg(feature = "wasm-precheck")] + if !request_cancelled { + if let Some(precheck) = crate::python_syntax_precheck::observe(&root, &feedback, deadline) { + feedback["schema_version"] = Value::String("0.14.0".into()); + feedback["scope"] = Value::String("local_native_and_candidate_syntax_scan".into()); + feedback["execution_budget"]["enforcement"] = + Value::String("native_and_bounded_syntax_worker".into()); + let native_reasons: std::collections::BTreeSet<&str> = feedback["files"] + .as_array() + .into_iter() + .flatten() + .filter_map(|file| file["reason"].as_str()) + .collect(); + let native_reason = if native_reasons.len() == 1 { + native_reasons + .iter() + .next() + .copied() + .unwrap_or("native_context_unavailable") + } else { + "multiple_native_incomplete_reasons" + }; + feedback["native"] = serde_json::json!({"status":"incomplete","reason":native_reason}); + feedback["setup"] = serde_json::json!({"requirement":"required","reason":"native_confirmation_needed","task_id":null}); + feedback["next_action"] = Value::String( + if precheck["observations"].as_array().is_some_and(|rows| !rows.is_empty()) { + "核对 Python 疑似语法位置,确认项目要求的原生检查器及配置,再以适用的 Python 原生语法能力复检;勿凭候选初检修改源码或关闭任务" + } else { + "候选 Python grammar 版本尚未验收;确认项目要求的原生检查器及配置后复检,不把零恢复节点当作通过" + }.into(), + ); + feedback["syntax_precheck"] = precheck; + } + } + #[cfg(feature = "wasm-precheck")] + if !request_cancelled && codeguard_runtime::sigint_cancellation_requested() { + request_cancelled = true; + feedback["command_status"] = Value::String("cancelled".into()); + feedback["exit_code"] = Value::from(130); + if let Some(reasons) = feedback["incomplete_reasons"].as_array_mut() { + if !reasons.iter().any(|reason| reason == "request_cancelled") { + reasons.push(Value::String("request_cancelled".into())); + } + } + } if parsed.json { println!("{feedback}"); } else { @@ -632,7 +681,7 @@ fn create_scratch(run_id: &str) -> std::io::Result { } fn print_human(feedback: &Value) { - println!("CodeGuard Python lint:局部原生检查;完整质量门禁尚未评估"); + println!("CodeGuard Python lint:局部检查;完整质量门禁尚未评估"); println!( "本轮范围:{};目标:{}", feedback["scan_scope"].as_str().unwrap_or("unknown"), @@ -732,6 +781,32 @@ fn print_human(feedback: &Value) { } } } + if feedback["syntax_precheck"].is_object() { + let precheck = &feedback["syntax_precheck"]; + println!( + "内置 Python 语法候选初检:已检查 {} / {},疑似位置 {},状态 incomplete;候选涉及文件的原生 Ruff 未运行。", + precheck["checked_files"], + precheck["selected_files"], + precheck["observations"].as_array().map_or(0, Vec::len) + ); + for row in precheck["observations"] + .as_array() + .into_iter() + .flatten() + .take(8) + { + println!( + "疑似语法 {}:{}:{} {};须由原生工具确认。", + row["path"], row["start_line"], row["start_column"], row["kind"] + ); + } + println!( + "下一步:{}", + feedback["next_action"] + .as_str() + .unwrap_or("复查原生检查条件") + ); + } println!("本命令尚无批准策略与完整义务,交付判定:not_evaluated"); } diff --git a/crates/codeguard-cli/src/python_syntax_precheck.rs b/crates/codeguard-cli/src/python_syntax_precheck.rs new file mode 100644 index 00000000..6f19158f --- /dev/null +++ b/crates/codeguard-cli/src/python_syntax_precheck.rs @@ -0,0 +1,156 @@ +//! Ruff 缺失或项目未声明 Ruff 时的 Python WASM 语法候选观察;不改变原生报告或交付门禁。 + +use crate::syntax_worker_runner::run_syntax_worker_candidate; +use crate::typescript_syntax_precheck::map_observations; +use codeguard_adapters::bundled_grammar_candidates; +use codeguard_runtime::read_bounded_regular_file; +use serde_json::{Value, json}; +use sha2::{Digest, Sha256}; +use std::path::{Component, Path}; +use std::sync::atomic::AtomicBool; +use std::time::Instant; + +const MAX_FILES: usize = 8; +const MAX_OBSERVATIONS: usize = 128; + +/// 仅为本轮缺少可运行 Ruff 上下文的文件补充有界疑似位置。 +/// 参数是规范化项目根、原生反馈和整轮截止时间;返回无原生或交付权威的候选报告。 +pub(crate) fn observe(root: &Path, feedback: &Value, deadline: Instant) -> Option { + let candidates: Vec<&Value> = feedback["files"] + .as_array()? + .iter() + .filter(|file| { + file["run_status"] == "incomplete" + && file["reason"].as_str().is_some_and(|reason| { + matches!( + reason, + "ruff_tool_not_found" + | "project_ruff_config_not_found" + | "ruff_section_not_declared" + | "ruff_lint_intent_not_explicit" + | "ruff_config_not_resolved" + ) + }) + && file["findings"].as_array().is_some_and(Vec::is_empty) + }) + .collect(); + if candidates.is_empty() { + return None; + } + let asset = bundled_grammar_candidates().ok().and_then(|manifest| { + manifest + .assets + .into_iter() + .find(|asset| asset.language == "python") + }); + let executable = std::env::current_exe().ok(); + let mut observations = Vec::new(); + let mut checked = Vec::new(); + let mut unavailable = Vec::new(); + let mut truncated = false; + for (index, file) in candidates.iter().enumerate() { + if index >= MAX_FILES { + truncated = true; + if unavailable.len() < MAX_FILES { + unavailable.push(json!({"path":file["path"],"reason":"file_budget_exceeded"})); + } + continue; + } + let Some(path) = file["path"].as_str().filter(|path| safe_python_path(path)) else { + unavailable.push(json!({"path":file["path"],"reason":"source_path_invalid"})); + continue; + }; + if Instant::now() >= deadline || codeguard_runtime::sigint_cancellation_requested() { + unavailable.push(json!({"path":path,"reason":"request_deadline_or_cancelled"})); + continue; + } + let source_path = root.join(path); + if !std::fs::symlink_metadata(&source_path).is_ok_and(|metadata| metadata.is_file()) + || source_path.canonicalize().ok().as_deref() != Some(source_path.as_path()) + { + unavailable.push(json!({"path":path,"reason":"source_unavailable"})); + continue; + } + let Ok(source) = read_bounded_regular_file(&source_path, 1024 * 1024) else { + unavailable.push(json!({"path":path,"reason":"source_unavailable_or_over_budget"})); + continue; + }; + let source_sha = format!("{:x}", Sha256::digest(&source)); + if file["source_sha256"] + .as_str() + .is_some_and(|expected| expected != source_sha) + { + unavailable.push(json!({"path":path,"reason":"source_changed"})); + continue; + } + let (Some(asset), Some(executable)) = (&asset, &executable) else { + unavailable.push(json!({"path":path,"reason":"grammar_or_worker_unavailable"})); + continue; + }; + let Ok(result) = run_syntax_worker_candidate( + executable, + "python", + path, + &source, + deadline, + &AtomicBool::new(false), + ) else { + unavailable.push(json!({"path":path,"reason":"worker_incomplete"})); + continue; + }; + if result.grammar_sha256 != asset.sha256 { + unavailable.push(json!({"path":path,"reason":"grammar_identity_changed"})); + continue; + } + let Ok(mapped) = map_observations(&source, &result.recoveries) else { + unavailable.push(json!({"path":path,"reason":"position_mapping_incomplete"})); + continue; + }; + checked.push(json!({"path":path,"source_sha256":source_sha,"grammar_sha256":asset.sha256})); + truncated |= result.precheck.truncated_files > 0; + for mut row in mapped { + if observations.len() >= MAX_OBSERVATIONS { + truncated = true; + break; + } + row["path"] = json!(path); + row["source_sha256"] = json!(source_sha); + observations.push(row); + } + } + let reason = if !unavailable.is_empty() { + "partial_unavailable" + } else if truncated { + "recovery_or_file_budget_truncated" + } else { + "grammar_version_unqualified" + }; + Some(json!({ + "backend":"bundled_tree_sitter_wasm_candidate", + "language":"python", + "status":"incomplete", + "reason":reason, + "grammar_sha256":asset.as_ref().map(|asset| asset.sha256.as_str()), + "grammar_qualified":false, + "selected_files":candidates.len(), + "checked_files":checked.len(), + "checked":checked, + "unavailable":unavailable, + "truncated":truncated, + "observations":observations, + })) +} + +fn safe_python_path(value: &str) -> bool { + let path = Path::new(value); + !value.is_empty() + && value.len() <= 512 + && !value.contains('\\') + && !value.chars().any(char::is_control) + && path + .components() + .all(|part| matches!(part, Component::Normal(_))) + && path + .extension() + .is_some_and(|ext| matches!(ext.to_str(), Some("py" | "pyw"))) +} diff --git a/crates/codeguard-cli/src/syntax_worker_command.rs b/crates/codeguard-cli/src/syntax_worker_command.rs index 3c684c0e..24d803bb 100644 --- a/crates/codeguard-cli/src/syntax_worker_command.rs +++ b/crates/codeguard-cli/src/syntax_worker_command.rs @@ -62,6 +62,7 @@ fn observe(language: &str, source: &[u8]) -> Result include_bytes!("../../../grammars/java/parser.wasm").as_slice(), + "python" => include_bytes!("../../../grammars/python/parser.wasm").as_slice(), "typescript" => include_bytes!("../../../grammars/typescript/parser.wasm").as_slice(), "tsx" => include_bytes!("../../../grammars/tsx/parser.wasm").as_slice(), _ => return Err("不支持的 grammar 语种".into()), diff --git a/crates/codeguard-cli/tests/lint_python_cli.rs b/crates/codeguard-cli/tests/lint_python_cli.rs index 74975053..f86daee8 100644 --- a/crates/codeguard-cli/tests/lint_python_cli.rs +++ b/crates/codeguard-cli/tests/lint_python_cli.rs @@ -173,6 +173,7 @@ fn selected_file_runs_native_ruff_without_importing_partial_backlog() { assert_eq!(report["files"][0]["path"], "changed.py"); assert_eq!(report["files"][0]["run_status"], "findings"); assert_eq!(report["files"][0]["findings"][0]["rule_id"], "F401"); + assert!(report.get("syntax_precheck").is_none()); assert_eq!(report["backlog_status"], "not_synced_scoped"); assert_eq!(fs::read_dir(reports_dir).unwrap().count(), before_reports); assert_eq!(report["delivery_decision"], "not_evaluated"); @@ -391,6 +392,9 @@ fn initialized_lint_report_binds_the_existing_workspace_identity() { serde_json::from_slice(&fs::read(project.0.join(".codeguard/workspace.json")).unwrap()) .unwrap(); let (_, report) = run(&project, &["--ruff-tool", "/nonexistent/ruff"]); + #[cfg(feature = "wasm-precheck")] + assert_eq!(report["schema_version"], "0.14.0"); + #[cfg(not(feature = "wasm-precheck"))] assert_eq!(report["schema_version"], "0.13.0"); assert_eq!(report["execution_budget"]["timeout_ms"], 1_800_000); assert_eq!(report["execution_budget"]["source"], "builtin_default"); diff --git a/crates/codeguard-cli/tests/python_syntax_fallback_candidate.rs b/crates/codeguard-cli/tests/python_syntax_fallback_candidate.rs new file mode 100644 index 00000000..2f07ed18 --- /dev/null +++ b/crates/codeguard-cli/tests/python_syntax_fallback_candidate.rs @@ -0,0 +1,153 @@ +#![cfg(all(unix, feature = "wasm-precheck"))] + +use serde_json::Value; +use std::fs; +use std::path::PathBuf; +use std::process::Command; +use std::sync::atomic::{AtomicU64, Ordering}; + +static NEXT: AtomicU64 = AtomicU64::new(0); + +struct Project(PathBuf); + +impl Project { + fn new() -> Self { + let id = NEXT.fetch_add(1, Ordering::Relaxed); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-python-syntax-{}-{id}", + std::process::id() + )); + fs::create_dir(&root).unwrap(); + Self(root) + } + + fn lint(&self, extra: &[&str]) -> Value { + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["lint", "python", self.0.to_str().unwrap(), "--format=json"]) + .args(extra) + .env("PATH", "") + .output() + .unwrap(); + assert_eq!(output.status.code(), Some(3)); + assert!( + output.stderr.is_empty(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + serde_json::from_slice(&output.stdout).unwrap() + } +} + +impl Drop for Project { + fn drop(&mut self) { + fs::remove_dir_all(&self.0).unwrap(); + } +} + +#[test] +fn missing_configured_ruff_uses_bounded_python_wasm_without_native_approval() { + let project = Project::new(); + fs::write(project.0.join("ruff.toml"), "[lint]\nselect = ['F401']\n").unwrap(); + fs::write(project.0.join("broken.py"), "def broken(\n").unwrap(); + let report = project.lint(&["--file", "broken.py"]); + assert_eq!(report["schema_version"], "0.14.0"); + assert_eq!(report["command_status"], "incomplete"); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!(report["files"][0]["run_status"], "incomplete"); + assert_eq!(report["files"][0]["reason"], "ruff_tool_not_found"); + assert!( + report["files"][0]["findings"] + .as_array() + .unwrap() + .is_empty() + ); + assert_eq!( + report["syntax_precheck"]["backend"], + "bundled_tree_sitter_wasm_candidate" + ); + assert_eq!(report["syntax_precheck"]["status"], "incomplete"); + assert_eq!(report["syntax_precheck"]["selected_files"], 1); + assert_eq!(report["syntax_precheck"]["checked_files"], 1, "{report}"); + assert_eq!(report["syntax_precheck"]["grammar_qualified"], false); + assert_eq!( + report["syntax_precheck"]["observations"][0]["path"], + "broken.py" + ); + assert_eq!( + report["syntax_precheck"]["observations"][0]["classification"], + "suspected" + ); + assert_eq!(report["native"]["status"], "incomplete"); + assert_eq!(report["setup"]["requirement"], "required"); + assert!(report["setup"]["task_id"].is_null()); +} + +#[test] +fn valid_python_does_not_turn_unqualified_grammar_into_clean() { + let project = Project::new(); + fs::write(project.0.join("ruff.toml"), "[lint]\nselect = ['F401']\n").unwrap(); + fs::write( + project.0.join("good.py"), + "def greet(name):\n return name\n", + ) + .unwrap(); + let report = project.lint(&["--file", "good.py"]); + assert_eq!(report["schema_version"], "0.14.0"); + assert_eq!(report["syntax_precheck"]["checked_files"], 1, "{report}"); + assert_eq!(report["syntax_precheck"]["status"], "incomplete"); + assert!( + report["syntax_precheck"]["observations"] + .as_array() + .unwrap() + .is_empty() + ); + assert_eq!(report["delivery_decision"], "not_evaluated"); +} + +#[test] +fn undeclared_ruff_still_provides_bounded_syntax_observation_without_claiming_ruff_missing() { + let project = Project::new(); + fs::write(project.0.join("broken.py"), "def broken(\n").unwrap(); + let report = project.lint(&["--file", "broken.py"]); + assert_eq!(report["schema_version"], "0.14.0"); + assert_eq!( + report["files"][0]["reason"], + "project_ruff_config_not_found" + ); + assert_eq!(report["native"]["reason"], "project_ruff_config_not_found"); + assert_eq!(report["syntax_precheck"]["checked_files"], 1, "{report}"); + assert_eq!( + report["syntax_precheck"]["observations"][0]["classification"], + "suspected" + ); + assert_eq!(report["delivery_decision"], "not_evaluated"); +} + +#[test] +fn invalid_ruff_config_does_not_get_laundered_by_wasm_precheck() { + let project = Project::new(); + fs::write(project.0.join("ruff.toml"), "[lint\n").unwrap(); + fs::write(project.0.join("broken.py"), "def broken(\n").unwrap(); + let report = project.lint(&["--file", "broken.py"]); + assert_eq!(report["schema_version"], "0.13.0"); + assert_eq!(report["files"][0]["reason"], "ruff_toml_invalid"); + assert!(report.get("syntax_precheck").is_none()); + assert_eq!(report["delivery_decision"], "not_evaluated"); +} + +#[test] +fn oversized_sibling_remains_visible_as_unavailable_without_erasing_other_suspicions() { + let project = Project::new(); + fs::write(project.0.join("ruff.toml"), "[lint]\nselect = ['F401']\n").unwrap(); + fs::write(project.0.join("broken.py"), "def broken(\n").unwrap(); + fs::write(project.0.join("huge.py"), vec![b'x'; 1024 * 1024 + 1]).unwrap(); + let report = project.lint(&["--file", "broken.py", "--file", "huge.py"]); + let precheck = &report["syntax_precheck"]; + assert_eq!(precheck["selected_files"], 2); + assert_eq!(precheck["checked_files"], 1); + assert_eq!(precheck["reason"], "partial_unavailable"); + assert_eq!(precheck["observations"][0]["path"], "broken.py"); + assert_eq!(precheck["unavailable"][0]["path"], "huge.py"); + assert_eq!(report["command_status"], "incomplete"); + assert_eq!(report["delivery_decision"], "not_evaluated"); +} diff --git a/docs/Codeguard-Architecture.md b/docs/Codeguard-Architecture.md index ab945cd0..65299efd 100644 --- a/docs/Codeguard-Architecture.md +++ b/docs/Codeguard-Architecture.md @@ -28,7 +28,7 @@ The existing [OpenSpec change](../openspec/changes/introduce-rust-codeguard-cli/ | [Core aggregation](../crates/codeguard-core/src/aggregate.rs), [delivery gate](../crates/codeguard-core/src/delivery_gate.rs) | Pure result semantics, not trusted public gate availability | | [Acceptance records](../tests/acceptance) | Scoped observations, test method, and residual gaps | -WASM requirements and 18 implementation tasks are recorded in the existing change. An opt-in Rust worker and narrow Java/TypeScript/TSX single-file feedback paths now exist; Python is an additional pinned, Rust-loadable asset candidate without CLI fallback. Project-wide native-first routing, qualified grammar ranges, capability-matched task closure, and host integrations remain incomplete; design examples are not current command output. +WASM requirements and 18 implementation tasks are recorded in the existing change. An opt-in Rust worker and narrow Java/TypeScript/TSX single-file feedback paths and a Python Ruff-unavailable candidate path now exist. All four grammars remain unqualified. Project-wide native-first routing, qualified grammar ranges, capability-matched task closure, and host integrations remain incomplete; design examples are not current command output. ## 2. Architectural drivers @@ -241,7 +241,7 @@ A completed native violation never selects fallback to obtain a clean result. A Reuse pinned CodeGraph grammar WASM artifacts and applicable upstream licenses, source references, patches and corpus cases. Do not copy CodeGraph's graph extraction or source-blanking heuristics into a syntax verdict. CodeGraph's `src/extraction/grammars.ts` loads language artifacts through `web-tree-sitter`; successful loading there does not prove compatibility with Codeguard's Rust runtime. The local source review on 2026-09-28 found modified grammar artifacts and about 65 MB of grammar files, so a live directory is not a release input. -The read-only `codeguard grammar status` command now projects a [pinned CodeGraph coverage inventory](../grammars/codegraph-coverage.json): 30 vendored WASM files and two dependency-provided grammars, with Java, Python, TypeScript and TSX as CodeGuard asset candidates and none qualified for release. Python has no CLI fallback route. This inventory is deliberately separate from the pinned [candidate asset manifest](../grammars/manifest.json). In particular, the COBOL source file exceeds the current loader's 8 MiB input limit. Inventory rows never trigger loading or count as supported parser capability. +The read-only `codeguard grammar status` command now projects a [pinned CodeGraph coverage inventory](../grammars/codegraph-coverage.json): 30 vendored WASM files and two dependency-provided grammars, with Java, Python, TypeScript and TSX as CodeGuard asset candidates and none qualified for release. Python has a narrow source-build-only fallback for unavailable or undeclared Ruff, with native results taking priority and no delivery approval. This inventory is deliberately separate from the pinned [candidate asset manifest](../grammars/manifest.json). In particular, the COBOL source file exceeds the current loader's 8 MiB input limit. Inventory rows never trigger loading or count as supported parser capability. ```text grammars/ # proposed release assets, not project state diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index 6f87a636..553672d8 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -28,7 +28,7 @@ | [核心聚合](../crates/codeguard-core/src/aggregate.rs)、[交付门禁](../crates/codeguard-core/src/delivery_gate.rs) | 纯结果语义,不代表公开可信门禁已可用 | | [验收记录](../tests/acceptance) | 限定范围的观察、测试方法和剩余缺口 | -WASM 的规范与 18 项实施任务已纳入既有 change;可选 Rust worker 与 Java/TypeScript/TSX 局部单文件反馈路径已存在。Python 新增为固定且通过 Rust 加载试验的资产候选,尚未接入 CLI 兜底。项目级原生优先调度、grammar 版本范围验收、任务的能力匹配关闭及宿主接线仍未完成;设计示例不是当前命令输出。 +WASM 的规范与 18 项实施任务已纳入既有 change;可选 Rust worker 与 Java/TypeScript/TSX 局部单文件反馈路径及 Python Ruff 不可用时的候选路径已存在;四份 grammar 都尚未验收。项目级原生优先调度、grammar 版本范围验收、任务的能力匹配关闭及宿主接线仍未完成;设计示例不是当前命令输出。 ## 2. 架构驱动 @@ -241,7 +241,7 @@ flowchart TD 复用固定版本的 CodeGraph grammar WASM,以及适用的上游许可证、源码引用、补丁和语料。不能把 CodeGraph 的图提取或源码遮盖启发式直接用于语法判定。CodeGraph 的 `src/extraction/grammars.ts` 通过 `web-tree-sitter` 加载语言资产,在那里可加载不证明兼容 Codeguard 的 Rust 运行时。发行时必须固定不可变的来源与资产清单,不能把正在修改的本地目录直接当作发行输入。 -只读 `codeguard grammar status` 现投影[固定来源覆盖库存](../grammars/codegraph-coverage.json):CodeGraph 随仓 30 份 WASM,依赖提供另两种独立 grammar;CodeGuard 有 Java、Python、TypeScript、TSX 四份资产候选,尚无已验收发行能力。Python 只通过固定字节与 Rust 加载试验,未接入 lint。覆盖库存与[候选资产清单](../grammars/manifest.json)分开,不能凭库存行自动加载或宣称支持。尤其 COBOL 来源资产超过当前加载器 8 MiB 限额。 +只读 `codeguard grammar status` 现投影[固定来源覆盖库存](../grammars/codegraph-coverage.json):CodeGraph 随仓 30 份 WASM,依赖提供另两种独立 grammar;CodeGuard 有 Java、Python、TypeScript、TSX 四份资产候选,尚无已验收发行能力。Python 已有源码可选构建中的局部 `lint` 兜底,仅覆盖 Ruff 不可用或项目未声明配置的情形;原生结果优先且不能批准交付。覆盖库存与[候选资产清单](../grammars/manifest.json)分开,不能凭库存行自动加载或宣称支持。尤其 COBOL 来源资产超过当前加载器 8 MiB 限额。 ```text grammars/ # 规划中的发行资产,不是项目状态 diff --git a/docs/Codeguard-Command-Reference.md b/docs/Codeguard-Command-Reference.md index 82fc1a05..c92b7c8e 100644 --- a/docs/Codeguard-Command-Reference.md +++ b/docs/Codeguard-Command-Reference.md @@ -16,7 +16,7 @@ With `--workspace` pointing to an initialized, canonical workspace, the TypeScri `codeguard hook plan [--format=json]` is a current, read-only host-event bridge. It reads a bounded [1.0 request](../schemas/hook-trigger-request.schema.json) from stdin and returns a [1.1 candidate tier](../schemas/hook-trigger-plan-v1.1.schema.json) with exit 3, `execution=not_run` and `delivery_decision=not_evaluated`. A confirmed edit may request fast file checks for at most eight distinct paths, 512 bytes per path and 2 KiB combined; an over-budget event instead requests scope resolution with `fast_scope_budget_exceeded`, without silently dropping files. This is a signal to select a bounded batch check, not to resubmit the same list forever. It does not invoke a checker, take a Git snapshot or block any host. The earlier [1.0 response schema](../schemas/hook-trigger-plan.schema.json) remains available for historical readers. See the [CLI acceptance record](../tests/acceptance/hook-plan-cli-candidate.md). -A separate Python execution slice now accepts `codeguard lint python . --file src/changed.py --format json`. Repeatable `--file` values share the eight-file, 512-byte per-path and 2-KiB combined budget. Only precisely selected, discovered Python files are scanned; an undiscovered target remains incomplete. The CLI conversation response is `python_lint_feedback` 0.13 with `scan_scope=selected_files` and `requested_paths`; it is not imported as a full workbench scan and keeps `delivery_decision=not_evaluated`. The previous 0.12 schema remains for historical results; the nested Python result in `check all` has not been upgraded. `hook plan` does not yet invoke this command automatically; other languages, hosts and bounded batch execution remain unwired. +A separate Python execution slice now accepts `codeguard lint python . --file src/changed.py --format json`. Repeatable `--file` values share the eight-file, 512-byte per-path and 2-KiB combined budget. Only precisely selected, discovered Python files are scanned; an undiscovered target remains incomplete. The default CLI conversation response is `python_lint_feedback` 0.13 with `scan_scope=selected_files` and `requested_paths`. An opt-in source build may return 0.14 with bounded Python WASM suspicion when Ruff is unavailable or undeclared; native feedback and `delivery_decision=not_evaluated` remain. The selected-file response is not imported as a full workbench scan. The previous 0.12 schema remains for historical results; the nested Python result in `check all` has not been upgraded. `hook plan` does not yet invoke this command automatically; other languages, hosts and bounded batch execution remain unwired. The current partial execution entry is `codeguard hook execute PATH --timeout DURATION --format=json [--ruff-tool ABS_PATH] [--git-tool ABS_PATH]`, reading the same 1.0.0 `hook_trigger_request` from stdin. The explicit timeout is capped at 120 seconds. Session start only discovers; Stop reads at most 64 findings and 64 reports with an 8 MiB report-byte budget and returns `hook_next_guidance` without running a checker. An oversized backlog returns `not_run/guidance_scope_exceeded`; use `codeguard next PATH --format=json` explicitly. An empty task list recommends a fresh full check, never a pass. `repair_ready` first bounds one task history to 128 events and 1 MiB, then verifies an existing stable task through the same `task verify` implementation in a bounded child process; a missing task, oversized history or failed child remains incomplete. Only task/checker identity, observation, saved-event state and a bounded reason are projected back, while the full native report stays in the existing local verification path. Explicit tool options supported by `task verify` may be passed to `hook execute`; they are rejected on unrelated events. A confirmed Python-only edit runs local Ruff. With an explicit Git binary, `pre_commit` reads the current staged index, including an alternate `GIT_INDEX_FILE`; the Git observation itself is bounded by the lesser of the supplied timeout and 15 seconds. Its `hook_git_index_summary` reports index identity, total violation count, at most 32 paths of at most 512 bytes each, truncation and object status while `source_check=not_run`; it cannot prove full quality coverage. Missing Git or failed index observation is `not_run`. Failed or uncertain writes, mixed languages, push and CI also remain `not_run`. The outer `hook_execution_feedback` is now 0.5; historical 0.1–0.4 schemas are preserved. `prompt_submitted` returns constant `read_only_intent_guidance` with no source check or delivery verdict. `host_blocking_verified=false` and `delivery_decision=not_evaluated` remain fixed even if the host claims blocking. No plugin Hook invokes this entry automatically yet; it is not a strict gate. diff --git a/docs/Codeguard-Command-Reference.zh_CN.md b/docs/Codeguard-Command-Reference.zh_CN.md index 22ab0234..b703a0df 100644 --- a/docs/Codeguard-Command-Reference.zh_CN.md +++ b/docs/Codeguard-Command-Reference.zh_CN.md @@ -419,7 +419,7 @@ gate 与 check 使用同一个义务/适配器/报告内核,差异是权威内 当前已有局部只读入口 `codeguard hook plan [--format=json]`,从 stdin 读取最多 64 KiB 的 [`hook_trigger_request` 1.0.0](../schemas/hook-trigger-request.schema.json),返回 [`hook_trigger_plan` 1.1.0](../schemas/hook-trigger-plan-v1.1.schema.json) 与退出码 3。确认成功的编辑最多选择 8 个不同路径、单路径 512 字节及总计 2 KiB 作逐文件快检;超预算返回 `fast_scope_budget_exceeded` 并要求重新确定有界批量范围,不截断后假称已检查,也不能反复提交同一超预算列表。旧 [1.0 响应 schema](../schemas/hook-trigger-plan.schema.json)保留供历史消费者识别。`execution=not_run`、`delivery_decision=not_evaluated` 固定,尚未调用检查器、取得 Git 快照或阻断任一宿主。它不是下述完整宿主接入的验收证明,见[局部验收](../tests/acceptance/hook-plan-cli-candidate.md)。 -已提供独立的 Python 执行切片:`codeguard lint python . --file src/changed.py --format json`。`--file` 可重复,但遵守同一 8 文件/512 字节单路径/2 KiB 总路径预算;只扫描被发现且精确选中的 Python 文件,未发现目标保留未完成原因。CLI 对话报告为 `python_lint_feedback` 0.13,返回 `scan_scope=selected_files` 和 `requested_paths`,不把该局部反馈保存并同步为完整工作台扫描,`delivery_decision` 仍为 `not_evaluated`。原 0.12 报告 schema 保留供历史结果识别;`check all` 的嵌套 Python 结果未因此升级。目前 `hook plan` 尚未自动调用它;其它语言、宿主入口和批量范围执行仍待接线。 +已提供独立的 Python 执行切片:`codeguard lint python . --file src/changed.py --format json`。`--file` 可重复,但遵守同一 8 文件/512 字节单路径/2 KiB 总路径预算;只扫描被发现且精确选中的 Python 文件,未发现目标保留未完成原因。默认 CLI 对话报告为 `python_lint_feedback` 0.13,返回 `scan_scope=selected_files` 和 `requested_paths`。源码可选构建在 Ruff 不可用或未声明时可返回 0.14 的有界 Python WASM 疑似位置,保留原生反馈和 `delivery_decision=not_evaluated`;局部反馈不作为完整工作台扫描导入。原 0.12 报告 schema 保留供历史结果识别;`check all` 的嵌套 Python 结果未因此升级。目前 `hook plan` 尚未自动调用它;其它语言、宿主入口和批量范围执行仍待接线。 现有局部执行入口 `codeguard hook execute PATH --timeout DURATION --format=json [--ruff-tool ABS_PATH] [--git-tool ABS_PATH]` 从 stdin 接收同一 `hook_trigger_request` 1.0.0。超时必须显式提供且不超过 120s。启动事件只读发现;Stop 只读最多 64 个 finding 和 64 份报告,报告字节总预算为 8 MiB,返回不调用检查器的 `hook_next_guidance`。记录超预算返回 `not_run/guidance_scope_exceeded`,需要显式执行 `codeguard next PATH --format=json`;无任务时要求新鲜完整检查,不能视作通过。`repair_ready` 先将单任务历史限制在 128 项和 1 MiB,再按稳定任务 ID 复用现有 `task verify`,通过有界子进程按原检查器复检;任务缺失、历史超限或子进程失败保留未完成。对话仅返回任务/检查器身份、观察结果、事件是否保存及有界原因,原生完整报告仍在既有本地复检路径中。`task verify` 的显式工具参数可传给 `hook execute`,其它事件收到这些参数会拒绝。确认成功且目标全为 Python 的编辑事件执行局部 Ruff。显式给出 Git 二进制后,`pre_commit` 读取本轮真实暂存 index(含替代 `GIT_INDEX_FILE`),Git 观察本身的截止时间取传入预算与 15s 较小值;`hook_git_index_summary` 给出 index 身份、违规总数、最多 32 个且每个至多 512 字节的路径、截断状态与对象状态,同时 `source_check=not_run`,不证明完整质量覆盖。Git 缺失或 index 观察失败仍为 `not_run`。失败/未知写入、混合语言、推送和 CI 仍保持 `not_run`。外层 `hook_execution_feedback` 升为 0.5,历史 0.1–0.4 schema 保留;`prompt_submitted` 仅返回固定的 `read_only_intent_guidance`,不运行源码检查、不签发交付结论;即使宿主声称可阻断,也固定 `host_blocking_verified=false`、`delivery_decision=not_evaluated`。该命令还没有被插件 Hook 自动调用,不能代替严格门禁。 diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index b6915a90..719691ba 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -14,7 +14,7 @@ The existing [OpenSpec change](../openspec/changes/introduce-rust-codeguard-cli/ Current source is the authority for callable behavior. Target contracts are labelled as such. The product priority is configuration discovery → native results → useful repair guidance. Internal consistency checks support this experience; they are not a demand for users to manually construct execution certificates. -WASM requirements and 18 implementation tasks are recorded in the existing change. Candidate Rust worker and narrow Java/TypeScript/TSX single-file reports now exist behind an opt-in feature; Python is pinned and Rust-loadable but has no CLI fallback. Project-wide native-first routing, qualified grammar ranges, and host integrations remain incomplete. Design examples are not current command output. +WASM requirements and 18 implementation tasks are recorded in the existing change. Candidate Rust worker and narrow Java/TypeScript/TSX single-file reports and a Python Ruff-unavailable candidate report now exist behind an opt-in feature; all grammars remain unqualified. Project-wide native-first routing, qualified grammar ranges, and host integrations remain incomplete. Design examples are not current command output. ## 2. Technology choices and trade-offs @@ -191,7 +191,7 @@ The opt-in CLI build first checks for a local ESLint 10 package and a single ord Import only pinned WASM bytes with upstream license, commit/patch provenance, SHA-256, ABI, tested runtime and language/dialect ranges, corpus reference and known gaps. Verify each copied CodeGraph artifact in the selected Rust Tree-sitter runtime; do not copy TypeScript extraction logic or assume CodeGraph support equals syntax acceptance. A release manifest belongs to distribution assets, not writable `.codeguard/` project state. -Use `codeguard grammar status --format=json` to query the separate source coverage inventory. It reports 32 distinct CodeGraph grammar assets, four CodeGuard asset candidates, and zero released capabilities without running a parser. Python is Rust-loadable but has no CLI lint fallback. The two dependency-provided assets lack pinned bytes, and COBOL exceeds the current 8 MiB loader limit; these remain explicit gaps before any per-language qualification. +Use `codeguard grammar status --format=json` to query the separate source coverage inventory. It reports 32 distinct CodeGraph grammar assets, four CodeGuard asset candidates, and zero released capabilities without running a parser. Python has a narrow source-build-only `lint` fallback for unavailable or undeclared Ruff, never a clean or delivery result. The two dependency-provided assets lack pinned bytes, and COBOL exceeds the current 8 MiB loader limit; these remain explicit gaps before any per-language qualification. Use a parent-controlled parser worker with a total deadline, per-file input bound, memory/process limit and capped diagnostics. Lazy-load selected grammars, parse without general network/filesystem imports, and terminate a stuck worker without losing native results from other modules. Host-specific enforcement and Rust MSRV compatibility must be tested; numeric production budgets remain measurement-driven rather than invented promises. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 0df01dec..f1e4f729 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -14,7 +14,7 @@ 可调用行为以当前源码为准,目标契约明确标注。产品优先级是配置发现 → 原生结果 → 有用的修复指引。内部一致性校验为这个体验服务,不要求用户手工构造执行证明。 -WASM 的规范与 18 项实施任务已纳入既有 change;可选特性下的 Rust worker 与 Java/TypeScript/TSX 局部单文件候选报告已存在,Python 固定资产通过 Rust 加载试验但尚无 CLI 兜底。项目级原生优先调度、grammar 版本范围验收与宿主接线仍未完成。设计示例不是当前命令输出。 +WASM 的规范与 18 项实施任务已纳入既有 change;可选特性下的 Rust worker 与 Java/TypeScript/TSX 局部单文件候选报告及 Python Ruff 不可用时的候选报告已存在;所有 grammar 仍未验收。项目级原生优先调度、grammar 版本范围验收与宿主接线仍未完成。设计示例不是当前命令输出。 ## 2. 技术选型与权衡 @@ -191,7 +191,7 @@ flowchart LR 只引入固定 WASM 字节及上游许可证、提交/补丁来源、SHA-256、ABI、已验证运行时和语言/方言范围、语料引用与已知缺口。逐个验证 CodeGraph 资产与选定 Rust Tree-sitter 运行时兼容,不能复制 TypeScript 提取逻辑或把 CodeGraph 支持等同语法验收。发行清单属于分发资产,不属于可写的 `.codeguard/` 项目状态。 -可用 `codeguard grammar status --format=json` 查询独立的来源覆盖库存。它只读报告 CodeGraph 32 种独立 grammar、CodeGuard 四份资产候选和零项已发行能力,不运行解析器;Python 候选未接入 lint。依赖提供的两份资产尚未固定字节,COBOL 超过现有加载器的 8 MiB 限额;每语言验收前仍须解决这些缺口。 +可用 `codeguard grammar status --format=json` 查询独立的来源覆盖库存。它只读报告 CodeGraph 32 种独立 grammar、CodeGuard 四份资产候选和零项已发行能力,不运行解析器;Python 已有源码可选构建中的局部 `lint` 兜底,结果不能视为通过。依赖提供的两份资产尚未固定字节,COBOL 超过现有加载器的 8 MiB 限额;每语言验收前仍须解决这些缺口。 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 diff --git a/docs/Codeguard-Trust-and-Distribution.md b/docs/Codeguard-Trust-and-Distribution.md index b4e4df47..80b1d04e 100644 --- a/docs/Codeguard-Trust-and-Distribution.md +++ b/docs/Codeguard-Trust-and-Distribution.md @@ -48,7 +48,7 @@ Network publication verifies signature and exact site permission before transfer The recorded npm `@partme.ai/codeguard@0.1.0` publication wraps an Apple Silicon macOS Rust binary. Node forwards argv/cwd/environment/streams/exit and is not another checker implementation. Current packaging has no automatic grammar download or install hook. Public installation verification is scoped in [npm acceptance](../tests/acceptance/npm-public-candidate.md); immutable source/tag and multi-platform qualification remain separate. -The repository now includes a [candidate asset manifest](../grammars/manifest.json), fixed-commit Java, Python, TypeScript and TSX WASM bytes with their CodeGraph and upstream MIT licenses. The [Rust verifier](../crates/codeguard-adapters/src/grammar_asset_manifest.rs) checks provenance fields and exact bytes. Rust offline loading and basic parsing tests pass for these four candidates; language versions, dialects, full corpora, complete CLI routing and a real MSRV build remain unvalidated. They therefore remain `candidate_unvalidated`. See [scoped acceptance](../tests/acceptance/grammar-asset-candidates.md), [Python asset acceptance](../tests/acceptance/python-grammar-asset-candidate.md) and [TSX correction](../tests/acceptance/tsx-grammar-candidate.md). The optional source build has narrow Java/TypeScript/TSX single-file fallback; Python has no CLI route. Public npm still does not package or select these assets. +The repository now includes a [candidate asset manifest](../grammars/manifest.json), fixed-commit Java, Python, TypeScript and TSX WASM bytes with their CodeGraph and upstream MIT licenses. The [Rust verifier](../crates/codeguard-adapters/src/grammar_asset_manifest.rs) checks provenance fields and exact bytes. Rust offline loading and basic parsing tests pass for these four candidates; language versions, dialects, full corpora, complete CLI routing and a real MSRV build remain unvalidated. They therefore remain `candidate_unvalidated`. See [scoped acceptance](../tests/acceptance/grammar-asset-candidates.md), [Python asset acceptance](../tests/acceptance/python-grammar-asset-candidate.md) and [TSX correction](../tests/acceptance/tsx-grammar-candidate.md). The optional source build has narrow Java/TypeScript/TSX fallback and a Python Ruff-unavailable/undeclared candidate path; none is qualified for release. Public npm still does not package or select these assets. CodeGraph loads grammar WASM with JavaScript `web-tree-sitter`, which was also used for the initial ABI observation. CodeGuard's Rust [WasmGrammar](../crates/codeguard-runtime/src/wasm_grammar.rs) loads the **same pinned bytes** through Tree-sitter `WasmStore` and parses Java, Python, TypeScript and TSX in tests. The grammar assets are shared; the JavaScript loader is not. Node only forwards the npm command. Rust loading proves limited compatibility of this path, not syntax accuracy or completion of every OpenSpec 14.2 criterion. diff --git a/docs/Codeguard-Trust-and-Distribution.zh_CN.md b/docs/Codeguard-Trust-and-Distribution.zh_CN.md index 7743a74f..9c66b115 100644 --- a/docs/Codeguard-Trust-and-Distribution.zh_CN.md +++ b/docs/Codeguard-Trust-and-Distribution.zh_CN.md @@ -71,7 +71,7 @@ Git 联合入口现进一步核对每跳已签名基线的原生祖先关系( npm 已发布 macOS arm64 0.1.0;Node 只转发到打包的 Rust 二进制,无检测或解析逻辑,无安装生命周期脚本。默认本地 private 打包与显式 public 模式分开。当前缺不可变源码/tag 绑定,多平台支持不能由打包器分支或交叉编译推断。使用方式归 [README](../README.zh-CN.md#npm-安装与一次性调用)。 -目前已有[候选资产清单](../grammars/manifest.json)及固定提交的 Java、Python、TypeScript、TSX WASM,保留 CodeGraph 和上游 MIT 许可证;[Rust 校验器](../crates/codeguard-adapters/src/grammar_asset_manifest.rs)核对来源字段和原始字节。四份资产的 Rust 离线加载及基本解析测试已通过;语言版本、方言、完整语料、完整调度和 MSRV 实编仍未验收,所以资产仍标记为 `candidate_unvalidated`。详见[局部验收](../tests/acceptance/grammar-asset-candidates.md)、[Python 资产验收](../tests/acceptance/python-grammar-asset-candidate.md)和 [TSX 纠错验收](../tests/acceptance/tsx-grammar-candidate.md)。可选源码构建仅有 Java/TypeScript/TSX 的局部单文件兜底,Python 尚无 CLI 路由;公开 npm 包仍不含这些字节或检查路径。 +目前已有[候选资产清单](../grammars/manifest.json)及固定提交的 Java、Python、TypeScript、TSX WASM,保留 CodeGraph 和上游 MIT 许可证;[Rust 校验器](../crates/codeguard-adapters/src/grammar_asset_manifest.rs)核对来源字段和原始字节。四份资产的 Rust 离线加载及基本解析测试已通过;语言版本、方言、完整语料、完整调度和 MSRV 实编仍未验收,所以资产仍标记为 `candidate_unvalidated`。详见[局部验收](../tests/acceptance/grammar-asset-candidates.md)、[Python 资产验收](../tests/acceptance/python-grammar-asset-candidate.md)和 [TSX 纠错验收](../tests/acceptance/tsx-grammar-candidate.md)。可选源码构建已有 Java/TypeScript/TSX 的局部单文件兜底及 Python Ruff 不可用/未声明配置时的候选路径;公开 npm 包仍不含这些字节或检查路径。 CodeGraph 使用 JavaScript `web-tree-sitter` 加载 grammar WASM,初始 ABI 观察也沿用该加载器。CodeGuard 的 Rust [WasmGrammar](../crates/codeguard-runtime/src/wasm_grammar.rs) 已使用 Tree-sitter `WasmStore` 加载**同一固定字节**并在测试中解析 Java、Python、TypeScript、TSX。这里复用的是 grammar 资产,不是 JavaScript 加载器;Node 仅负责 npm 命令转发。Rust 加载成功只证明该加载路径的有限兼容性,不证明语法判定准确,也不代表 OpenSpec 14.2 的全部验收完成。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 4cc3803b..186c77cf 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1028,15 +1028,15 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 状态:逐项实施中,未达到整体验收。统一规格 [syntax-precheck](specs/syntax-precheck/spec.md)。依赖已有 CLI/原生调度/工作台切片,不等待整个 S13;本阶段验收进入 S12、S13。各任务须先失败样本后实现与回归;不改写原生规则。责任为模块维护职责,非已分配人员。 -- [ ] 14.1 资产清单与来源。责任:adapters / 发布。规格:SP04。依赖:无。验收:固定 CodeGraph 来源提交、grammar 版本、许可证、SHA-256、ABI、语言/方言;缺来源、错散列和不兼容样本拒绝加载。进行中:已从固定 CodeGraph 提交复制 Java/Python/TypeScript/TSX WASM 与 MIT 许可证,清单和 Rust 校验拒绝来源/字节/声明 ABI 漂移;四份资产已由 Rust 离线加载并实测 ABI,Python 尚无 lint 路由;见[Python 资产局部验收](../../../tests/acceptance/python-grammar-asset-candidate.md);[局部证据](../../../tests/acceptance/grammar-asset-candidates.md)。新增与批准资产清单分离的[完整来源覆盖库存](../../../grammars/codegraph-coverage.json)及只读 `grammar status`:固定提交中的 30 份随仓 WASM 已逐字节核对,另两种依赖资产标为未固定,COBOL 的 16.4 MB 超出当前 8 MiB 加载限额;[覆盖验收](../../../tests/acceptance/codegraph-grammar-coverage.md)。其余 grammar 的上游许可证/版本、Rust 加载,以及所有候选的语言版本/方言、语料及可发行验收仍缺,不勾选。 +- [ ] 14.1 资产清单与来源。责任:adapters / 发布。规格:SP04。依赖:无。验收:固定 CodeGraph 来源提交、grammar 版本、许可证、SHA-256、ABI、语言/方言;缺来源、错散列和不兼容样本拒绝加载。进行中:已从固定 CodeGraph 提交复制 Java/Python/TypeScript/TSX WASM 与 MIT 许可证,清单和 Rust 校验拒绝来源/字节/声明 ABI 漂移;四份资产已由 Rust 离线加载并实测 ABI,Python 尚无 lint 路由;见[Python 资产局部验收](../../../tests/acceptance/python-grammar-asset-candidate.md);[局部证据](../../../tests/acceptance/grammar-asset-candidates.md)。新增与批准资产清单分离的[完整来源覆盖库存](../../../grammars/codegraph-coverage.json)及只读 `grammar status`:固定提交中的 30 份随仓 WASM 已逐字节核对,另两种依赖资产标为未固定,COBOL 的 16.4 MB 超出当前 8 MiB 加载限额;[覆盖验收](../../../tests/acceptance/codegraph-grammar-coverage.md)。其余 grammar 的上游许可证/版本、Rust 加载,以及所有候选的语言版本/方言、语料及可发行验收仍缺,不勾选。 Python 资产已接入可选源码构建的 `lint python` 局部候选路径,但版本/方言和发行验收仍缺;见[Python 兜底局部验收](../../../tests/acceptance/python-syntax-fallback-candidate.md)。 - [ ] 14.2 Rust WASM 加载器。责任:runtime。规格:SP04,SP05。依赖:14.1。验收:支持固定资产离线加载;不依赖 Node 解析器、CodeGraph 或用户安装 tree-sitter-cli;通过有效/损坏/错 ABI 样本及 MSRV。进行中:可选 `wasm-precheck` 特性已通过 Java/Python/TypeScript/TSX Rust `WasmStore` 加载与反例测试,并由私有工作进程从 stdin 处理真实源码、父进程核对身份与恢复锚点,见[局部验收](../../../tests/acceptance/syntax-worker-candidate.md);本机无 Rust 1.85,MSRV 实编及正式 `lint/check` 接线尚缺。 - [ ] 14.3 进程资源隔离。责任:runtime。规格:SP05。依赖:14.2。验收:验证超时、内存、输出、取消与崩溃;损坏 grammar 不阻塞其他模块、不遗留进程。进行中:Unix 候选 worker 复用受控进程组内核,输入 1 MiB、输出 64 KiB,父进程有共同截止时间和取消;崩溃后代清理、输出洪泛、运行中取消及随后正常解析均增加真实子进程反例。Linux 2 GiB `RLIMIT_AS`、3 GiB 分配负例与 worker 故障隔离在 [Ubuntu CI](https://github.com/full-stack-plugins/codeguard/actions/runs/36478461118) 真实通过;Wasmtime 虚拟预留缩小。macOS 本机硬限制实测不支持,候选仍不具内存隔离权威;见[局部验收](../../../tests/acceptance/syntax-worker-candidate.md)。其它目标平台、损坏 grammar 隔离及正式调度仍缺,不勾选。 - [ ] 14.4 语言覆盖与源码映射。责任:adapters。规格:SP06。依赖:14.2。验收:识别 ERROR/MISSING,合并恢复节点;测试 Unicode、换行、嵌入语言、新语法和未知版本,不将不支持当源码违规。进行中:Rust 已提取两类原始节点,adapters 核对 UTF-8/CRLF 字节锚点并转换 Unicode 标量列;结构祖先归组保留并列错误,预算截断保持不完整;[局部证据](../../../tests/acceptance/syntax-recovery-candidate.md)。级联归并语料、版本/嵌入语言及原生对照仍缺。 - [ ] 14.5 原生工具准备探测。责任:adapters。规格:SP01,SP03。依赖:现有配置发现。验收:识别项目本地工具、wrapper、版本和无效配置;PATH 缺失不等于未安装,不对坏配置重复建议安装。进行中:ESLint 本地包/入口及 Maven Wrapper 脚本/发行配置已有只读候选分类,`detect` 与 `check` 已输出固定路径观察的候选;`node_modules` 普通源码遍历被排除,链接和损坏配置保留阻塞;发现协议升级为 0.4.0,聚合协议升级为 0.31.0 并保留旧 schema;[局部证据](../../../tests/acceptance/native-tool-readiness-candidates.md)。单文件 TypeScript 本地候选已接通受控原生版本探针,见 [原生优先局部验收](../../../tests/acceptance/native-first-eslint-candidate.md);单文件 TypeScript 现进一步区分本地包确实未观察到与配置歧义、链接入口、包身份损坏等环境阻塞;阻塞不被误当成工具缺失而触发 WASM,见同一[原生优先局部验收](../../../tests/acceptance/native-first-eslint-candidate.md)。显式工具自动选择、其它语言及多模块原生优先调度、更多构建器和真实多平台验收尚缺,不勾选。 -- [ ] 14.6 逐模块原生优先调度。责任:CLI/core。规格:SP01。依赖:14.4,14.5。验收:原生可用先执行;原生违规不得被 WASM 洗白;多根分别选择并保留运行失败与兄弟结果。进行中:可选 CLI 特性在 `lint typescript`、`lint java` 显式单文件且完全缺少原生上下文时附加候选语法观察,部分原生上下文、符号链接、其它扩展名及 Javadoc/Checkstyle 选择保留原路径,见 [TypeScript 局部验收](../../../tests/acceptance/typescript-syntax-fallback-candidate.md)、[Java 局部验收](../../../tests/acceptance/java-syntax-fallback-candidate.md)。新增 `.tsx` 独立 grammar 的有效/无效 JSX 反例与候选报告方言绑定,见 [TSX 纠错验收](../../../tests/acceptance/tsx-grammar-candidate.md)。本地 ESLint 10 候选与单一 flat config 可见且 PATH 提供普通 Node 时,单文件 TypeScript 路径现在自动调用既有原生探针并保留其局部发现或版本失败;Node 不可解析或本地入口/配置/包身份不可信时保持具体准备反馈,不启动 WASM;本地包确实未观察到时仍可初检,见 [本地原生优先局部验收](../../../tests/acceptance/native-first-eslint-candidate.md)。项目原脚本参数、多模块调度、真实 ESLint 10 与混合结果保留仍缺,不勾选。 -- [ ] 14.7 初检协议与 schema。责任:core/CLI。规格:SP02,SP11。依赖:14.4。验收:固定版本和状态枚举、输入/资产身份、范围及缺口;空文件集、部分解析和未知版本不能 clean;未知 major 拒绝。进行中:core 已有状态聚合及[局部证据](../../../tests/acceptance/syntax-precheck-status-candidate.md);adapters 候选报告 1.0.0 的封闭 schema 与严格读者拒绝重复键、未知版本、身份漂移与伪造 clean,见[候选报告证据](../../../tests/acceptance/syntax-precheck-report-candidate.md);可选 CLI `lint typescript` 与 `lint java` 单文件入口分别产生 TypeScript 0.3.0、TSX 0.4.0、Java 0.1.0 局部候选反馈及位置化疑似观察,见 [TypeScript](../../../tests/acceptance/typescript-syntax-fallback-candidate.md) 与 [Java](../../../tests/acceptance/java-syntax-fallback-candidate.md) 验收。TypeScript/TSX 疑似位置现可进入已初始化工作台的版本化任务报告;正式全范围/嵌入区域/排除原因、Java 与多模块任务引用、所有消费者未知 major 拒绝与发布兼容仍缺,不勾选。 +- [ ] 14.6 逐模块原生优先调度。责任:CLI/core。规格:SP01。依赖:14.4,14.5。验收:原生可用先执行;原生违规不得被 WASM 洗白;多根分别选择并保留运行失败与兄弟结果。进行中:可选 CLI 特性在 `lint typescript`、`lint java` 显式单文件且完全缺少原生上下文时附加候选语法观察,部分原生上下文、符号链接、其它扩展名及 Javadoc/Checkstyle 选择保留原路径,见 [TypeScript 局部验收](../../../tests/acceptance/typescript-syntax-fallback-candidate.md)、[Java 局部验收](../../../tests/acceptance/java-syntax-fallback-candidate.md)。新增 `.tsx` 独立 grammar 的有效/无效 JSX 反例与候选报告方言绑定,见 [TSX 纠错验收](../../../tests/acceptance/tsx-grammar-candidate.md)。本地 ESLint 10 候选与单一 flat config 可见且 PATH 提供普通 Node 时,单文件 TypeScript 路径现在自动调用既有原生探针并保留其局部发现或版本失败;Node 不可解析或本地入口/配置/包身份不可信时保持具体准备反馈,不启动 WASM;本地包确实未观察到时仍可初检,见 [本地原生优先局部验收](../../../tests/acceptance/native-first-eslint-candidate.md)。项目原脚本参数、多模块调度、真实 ESLint 10 与混合结果保留仍缺,不勾选。 Python `lint` 现先保留 Ruff 原生结果;工具缺失或项目未声明 Ruff 时,对有界文件附加 WASM 疑似位置。无效配置与已有原生诊断不被 WASM 洗白;见[Python 兜底局部验收](../../../tests/acceptance/python-syntax-fallback-candidate.md)。 +- [ ] 14.7 初检协议与 schema。责任:core/CLI。规格:SP02,SP11。依赖:14.4。验收:固定版本和状态枚举、输入/资产身份、范围及缺口;空文件集、部分解析和未知版本不能 clean;未知 major 拒绝。进行中:core 已有状态聚合及[局部证据](../../../tests/acceptance/syntax-precheck-status-candidate.md);adapters 候选报告 1.0.0 的封闭 schema 与严格读者拒绝重复键、未知版本、身份漂移与伪造 clean,见[候选报告证据](../../../tests/acceptance/syntax-precheck-report-candidate.md);可选 CLI `lint typescript` 与 `lint java` 单文件入口分别产生 TypeScript 0.3.0、TSX 0.4.0、Java 0.1.0 局部候选反馈及位置化疑似观察,见 [TypeScript](../../../tests/acceptance/typescript-syntax-fallback-candidate.md) 与 [Java](../../../tests/acceptance/java-syntax-fallback-candidate.md) 验收。TypeScript/TSX 疑似位置现可进入已初始化工作台的版本化任务报告;正式全范围/嵌入区域/排除原因、Java 与多模块任务引用、所有消费者未知 major 拒绝与发布兼容仍缺,不勾选。 新增 Python 0.14.0 局部反馈 schema,明确原生状态、候选范围、源码和 grammar SHA-256、缺口与疑似位置;零观察仍 incomplete,见[Python 兜底局部验收](../../../tests/acceptance/python-syntax-fallback-candidate.md)。 - [ ] 14.8 安装建议与必须确认策略。责任:core。规格:SP03。依赖:14.6,14.7。验收:可选原生缺失且完整 clean 仅推荐;疑似或已有必需义务要求恢复原生确认;无适配器给具体决策。进行中:core 已有纯决策函数,区分缺工具、坏配置、执行失败和无适配器;仅非空、完整、已验收且无恢复节点的 clean 才允许可选建议,伪造 clean 状态不能降级。见[局部验收](../../../tests/acceptance/syntax-setup-guidance-candidate.md)。真实工具准备来源、CLI/工作台任务投影及宿主反馈尚未接线,不勾选。 -- [ ] 14.9 四类可读与 JSON 报告。责任:CLI。规格:SP07,SP11。依赖:14.7,14.8。验收:覆盖原生成功、缺原生且 clean、疑似语法、初检不可用;结果范围/行动/交付语义一致;不伪造任务 ID。进行中:启用可选 WASM 特性的 TypeScript 与 Java 单文件在无显式原生上下文时提供 JSON/human 候选疑似观察;无恢复节点仍为 incomplete。未初始化时任务引用为空;TypeScript/TSX 在已初始化工作区同步成功后用 0.5.0 报告返回真实稳定任务 ID,见[工作台局部验收](../../../tests/acceptance/typescript-syntax-confirmation-task.md)。Java 超大输入保留 worker 未完成,见 [TypeScript](../../../tests/acceptance/typescript-syntax-fallback-candidate.md) 与 [Java](../../../tests/acceptance/java-syntax-fallback-candidate.md) 局部验收。正式四类报告、多模块范围与宿主渲染仍缺,不勾选。 +- [ ] 14.9 四类可读与 JSON 报告。责任:CLI。规格:SP07,SP11。依赖:14.7,14.8。验收:覆盖原生成功、缺原生且 clean、疑似语法、初检不可用;结果范围/行动/交付语义一致;不伪造任务 ID。进行中:启用可选 WASM 特性的 TypeScript 与 Java 单文件在无显式原生上下文时提供 JSON/human 候选疑似观察;无恢复节点仍为 incomplete。未初始化时任务引用为空;TypeScript/TSX 在已初始化工作区同步成功后用 0.5.0 报告返回真实稳定任务 ID,见[工作台局部验收](../../../tests/acceptance/typescript-syntax-confirmation-task.md)。Java 超大输入保留 worker 未完成,见 [TypeScript](../../../tests/acceptance/typescript-syntax-fallback-candidate.md) 与 [Java](../../../tests/acceptance/java-syntax-fallback-candidate.md) 局部验收。正式四类报告、多模块范围与宿主渲染仍缺,不勾选。 Python 0.14.0 JSON/human 局部反馈现区分 Ruff 缺失、配置未声明、WASM 疑似及不可用范围,始终返回 incomplete、退出码 3 且任务 ID 不伪造;见[局部验收](../../../tests/acceptance/python-syntax-fallback-candidate.md)。 - [ ] 14.10 稳定任务与下一步。责任:core/CLI。规格:SP08。依赖:14.8,14.9及现有工作台。验收:重复扫描更新同一准备/确认任务,失败尝试有记录,无进展给具体诊断;推荐项不持续占用 next。进行中:单文件 TypeScript 已能把“本地 ESLint 可见但当前 PATH 无 Node”记为 `eslint_node_runtime_unresolved`,初始化工作区连续两轮仅更新同一环境任务,`next` 要求受控 Node 原生复检而非重复安装 ESLint 或修改源码;见[原生优先局部验收](../../../tests/acceptance/native-first-eslint-candidate.md)。配置歧义、链接入口和损坏包清单连续出现时亦更新同一 ESLint 环境任务、变更具体诊断而不生成源码 finding;见同一[局部验收](../../../tests/acceptance/native-first-eslint-candidate.md)。TypeScript/TSX 候选 WASM 初检现在将已初始化工作区的源码范围接到一张稳定的 ESLint 原生确认阻塞任务;重复扫描和后续零恢复节点不关闭,见[工作台局部验收](../../../tests/acceptance/typescript-syntax-confirmation-task.md)。TypeScript/TSX 有界疑似位置已用源码/grammar 身份保存到 0.2.0 准备报告并由任务报告摘要引用,导入拒绝伪造 grammar 和越界坐标;失败尝试/无进展预算、推荐项非阻断投影、Java 与多模块任务尚缺,不勾选。 - [ ] 14.11 能力匹配复检。责任:core/adapters。规格:SP08。依赖:14.10。验收:同模块/输入/方言的原生语法能力确认才能关闭;仅安装、后续 WASM clean 或格式检查不能关闭;复发重开。 - [ ] 14.12 精确误报纠错。责任:core。规格:SP09。依赖:14.11及既有白名单提案。验收:绑定 grammar/规则/范围/版本和可信审批,变更失效;原生反证保留历史并进语料,不撤销原生义务。 diff --git a/schemas/python-lint-feedback-v0.14.schema.json b/schemas/python-lint-feedback-v0.14.schema.json new file mode 100644 index 00000000..083b639e --- /dev/null +++ b/schemas/python-lint-feedback-v0.14.schema.json @@ -0,0 +1,860 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:codeguard:schema:python-lint-feedback:0.14.0", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "scope", + "scan_scope", + "requested_paths", + "operation", + "language", + "run_id", + "workspace_binding", + "workspace_id", + "checker_configurations", + "files", + "incomplete_reasons", + "local_scan_complete", + "command_status", + "exit_code", + "tool_approval", + "native_tool_version", + "rulepack_approval", + "adapter_sha256", + "backlog_status", + "backlog_sync", + "repair_brief_status", + "repair_brief_reason", + "next", + "delivery_decision", + "execution_budget", + "native", + "setup", + "next_action", + "syntax_precheck" + ], + "properties": { + "schema_version": { + "const": "0.14.0" + }, + "execution_budget": { + "type": "object", + "additionalProperties": false, + "required": [ + "timeout_ms", + "source", + "enforcement" + ], + "properties": { + "timeout_ms": { + "type": "integer", + "minimum": 1, + "maximum": 86400000 + }, + "source": { + "enum": [ + "cli", + "registered_environment", + "project_default", + "builtin_default" + ] + }, + "enforcement": { + "const": "native_and_bounded_syntax_worker" + } + } + }, + "report_type": { + "const": "python_lint_feedback" + }, + "scope": { + "const": "local_native_and_candidate_syntax_scan" + }, + "scan_scope": { + "enum": [ + "discovered_python", + "selected_files" + ] + }, + "requested_paths": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "array", + "minItems": 1, + "maxItems": 8, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!/)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\u0000-\\u001f]+$" + } + } + ] + }, + "operation": { + "const": "lint" + }, + "language": { + "const": "python" + }, + "run_id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{1,120}$" + }, + "workspace_binding": { + "enum": [ + "bound", + "uninitialized", + "legacy_unbound", + "invalid" + ] + }, + "workspace_id": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^ws-[0-9a-f]{32}$" + } + ] + }, + "checker_configurations": { + "type": "array", + "items": { + "type": "object" + } + }, + "files": { + "type": "array", + "items": { + "type": "object", + "required": [ + "run_status", + "tool_sha256", + "config_sha256", + "rule_settings", + "suppression_audit" + ], + "properties": { + "run_status": { + "enum": [ + "passed", + "findings", + "suppressed", + "incomplete" + ] + }, + "tool_sha256": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/sha256" + } + ] + }, + "config_sha256": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/sha256" + } + ] + }, + "rule_settings": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/rule_settings" + } + ] + }, + "suppression_audit": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/suppression_audit" + } + ] + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "codeguard_rule_id", + "rulepack_sha256", + "rulepack_status" + ], + "properties": { + "codeguard_rule_id": { + "type": [ + "string", + "null" + ] + }, + "rulepack_sha256": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/sha256" + } + ] + }, + "rulepack_status": { + "enum": [ + "candidate_unapproved", + "unverified_incomplete", + "rulepack_unavailable", + "unmapped_or_unvalidated_version" + ] + } + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "run_status": { + "const": "incomplete" + } + } + }, + "then": { + "properties": { + "tool_sha256": { + "type": "null" + }, + "config_sha256": { + "type": "null" + }, + "rule_settings": { + "type": "null" + }, + "suppression_audit": { + "type": "null" + } + } + }, + "else": { + "properties": { + "tool_sha256": { + "$ref": "#/$defs/sha256" + }, + "config_sha256": { + "$ref": "#/$defs/sha256" + }, + "rule_settings": { + "$ref": "#/$defs/rule_settings" + }, + "suppression_audit": { + "$ref": "#/$defs/suppression_audit" + } + } + } + } + ] + } + }, + "incomplete_reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "local_scan_complete": { + "type": "boolean" + }, + "command_status": { + "enum": [ + "incomplete", + "cancelled" + ] + }, + "exit_code": { + "enum": [ + 3, + 130 + ] + }, + "tool_approval": { + "const": "unverified" + }, + "native_tool_version": { + "type": [ + "string", + "null" + ] + }, + "rulepack_approval": { + "const": "unverified" + }, + "adapter_sha256": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/sha256" + } + ] + }, + "backlog_status": { + "enum": [ + "synced_partial", + "backlog_update_failed", + "not_initialized", + "not_synced_scoped" + ] + }, + "backlog_sync": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/backlog_sync" + } + ] + }, + "repair_brief_status": { + "enum": [ + "available", + "unavailable" + ] + }, + "repair_brief_reason": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "minLength": 1 + } + ] + }, + "next": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/next" + } + ] + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "native": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "reason" + ], + "properties": { + "status": { + "const": "incomplete" + }, + "reason": { + "enum": [ + "ruff_tool_not_found", + "project_ruff_config_not_found", + "ruff_section_not_declared", + "ruff_lint_intent_not_explicit", + "ruff_config_not_resolved", + "multiple_native_incomplete_reasons" + ] + } + } + }, + "setup": { + "type": "object", + "additionalProperties": false, + "required": [ + "requirement", + "reason", + "task_id" + ], + "properties": { + "requirement": { + "const": "required" + }, + "reason": { + "const": "native_confirmation_needed" + }, + "task_id": { + "type": "null" + } + } + }, + "next_action": { + "type": "string", + "minLength": 1 + }, + "syntax_precheck": { + "type": "object", + "additionalProperties": false, + "required": [ + "backend", + "language", + "status", + "reason", + "grammar_sha256", + "grammar_qualified", + "selected_files", + "checked_files", + "checked", + "unavailable", + "truncated", + "observations" + ], + "properties": { + "backend": { + "const": "bundled_tree_sitter_wasm_candidate" + }, + "language": { + "const": "python" + }, + "status": { + "const": "incomplete" + }, + "reason": { + "enum": [ + "partial_unavailable", + "recovery_or_file_budget_truncated", + "grammar_version_unqualified" + ] + }, + "grammar_sha256": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/sha256" + } + ] + }, + "grammar_qualified": { + "const": false + }, + "selected_files": { + "type": "integer", + "minimum": 1 + }, + "checked_files": { + "type": "integer", + "minimum": 0, + "maximum": 8 + }, + "truncated": { + "type": "boolean" + }, + "checked": { + "type": "array", + "maxItems": 8, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "source_sha256", + "grammar_sha256" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "source_sha256": { + "$ref": "#/$defs/sha256" + }, + "grammar_sha256": { + "$ref": "#/$defs/sha256" + } + } + } + }, + "unavailable": { + "type": "array", + "maxItems": 8, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "reason" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "reason": { + "enum": [ + "source_path_invalid", + "file_budget_exceeded", + "request_deadline_or_cancelled", + "source_unavailable", + "source_unavailable_or_over_budget", + "source_changed", + "grammar_or_worker_unavailable", + "worker_incomplete", + "grammar_identity_changed", + "position_mapping_incomplete" + ] + } + } + } + }, + "observations": { + "type": "array", + "maxItems": 128, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "classification", + "kind", + "syntax_kind", + "start_line", + "start_column", + "end_line", + "end_column", + "group_id", + "path", + "source_sha256" + ], + "properties": { + "classification": { + "const": "suspected" + }, + "kind": { + "enum": [ + "ERROR", + "MISSING" + ] + }, + "syntax_kind": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "start_line": { + "type": "integer", + "minimum": 1 + }, + "start_column": { + "type": "integer", + "minimum": 1 + }, + "end_line": { + "type": "integer", + "minimum": 1 + }, + "end_column": { + "type": "integer", + "minimum": 1 + }, + "group_id": { + "type": "integer", + "minimum": 1 + }, + "path": { + "type": "string", + "minLength": 1 + }, + "source_sha256": { + "$ref": "#/$defs/sha256" + } + } + } + } + } + } + }, + "$defs": { + "sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "rule_settings": { + "type": "object", + "additionalProperties": false, + "required": [ + "settings_sha256", + "globally_enabled_mapped_rules", + "per_file_ignores_present", + "coverage_proven" + ], + "properties": { + "settings_sha256": { + "$ref": "#/$defs/sha256" + }, + "globally_enabled_mapped_rules": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "E501", + "F401" + ] + } + }, + "per_file_ignores_present": { + "type": "boolean" + }, + "coverage_proven": { + "const": false + } + } + }, + "suppression_audit": { + "type": "object", + "additionalProperties": false, + "required": [ + "audit_sha256", + "suppressed_diagnostic_count", + "suppressed_rule_ids", + "scope" + ], + "properties": { + "audit_sha256": { + "$ref": "#/$defs/sha256" + }, + "suppressed_diagnostic_count": { + "type": "integer", + "minimum": 0 + }, + "suppressed_rule_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[A-Z0-9]{1,12}$" + } + }, + "scope": { + "const": "source_comments_only" + } + } + }, + "backlog_sync": { + "type": "object", + "additionalProperties": false, + "required": [ + "new_findings", + "new_blockers", + "imported_reports", + "historical_findings", + "failed_reports" + ], + "properties": { + "new_findings": { + "type": "integer", + "minimum": 0 + }, + "new_blockers": { + "type": "integer", + "minimum": 0 + }, + "imported_reports": { + "type": "integer", + "minimum": 0 + }, + "historical_findings": { + "type": "integer", + "minimum": 0 + }, + "failed_reports": { + "type": "integer", + "minimum": 0 + } + } + }, + "next": { + "type": "object", + "required": [ + "schema_version", + "report_type", + "disposition", + "repair_brief", + "authority", + "delivery_decision" + ], + "properties": { + "schema_version": { + "const": "0.1.0" + }, + "report_type": { + "const": "repair_brief_preview" + }, + "disposition": { + "enum": [ + "actionable", + "waiting", + "needs_decision", + "verification_required", + "no_applicable_work" + ] + }, + "repair_brief": { + "type": [ + "object", + "null" + ] + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "scan_scope": { + "const": "selected_files" + } + }, + "required": [ + "scan_scope" + ] + }, + "then": { + "properties": { + "requested_paths": { + "type": "array" + }, + "backlog_status": { + "const": "not_synced_scoped" + }, + "backlog_sync": { + "type": "null" + }, + "repair_brief_status": { + "const": "unavailable" + }, + "repair_brief_reason": { + "const": "not_synced_scoped" + }, + "next": { + "type": "null" + } + } + }, + "else": { + "properties": { + "requested_paths": { + "type": "null" + }, + "backlog_status": { + "enum": [ + "synced_partial", + "backlog_update_failed", + "not_initialized" + ] + } + } + } + }, + { + "if": { + "properties": { + "command_status": { + "const": "cancelled" + } + }, + "required": [ + "command_status" + ] + }, + "then": { + "properties": { + "exit_code": { + "const": 130 + } + } + }, + "else": { + "properties": { + "exit_code": { + "const": 3 + } + } + } + }, + { + "if": { + "properties": { + "repair_brief_status": { + "const": "available" + } + }, + "required": [ + "repair_brief_status" + ] + }, + "then": { + "properties": { + "next": { + "$ref": "#/$defs/next" + }, + "repair_brief_reason": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "repair_brief_status": { + "const": "unavailable" + } + }, + "required": [ + "repair_brief_status" + ] + }, + "then": { + "properties": { + "next": { + "type": "null" + }, + "repair_brief_reason": { + "type": "string", + "minLength": 1 + } + } + } + } + ] +} diff --git a/tests/acceptance/python-syntax-fallback-candidate.md b/tests/acceptance/python-syntax-fallback-candidate.md new file mode 100644 index 00000000..c918b16e --- /dev/null +++ b/tests/acceptance/python-syntax-fallback-candidate.md @@ -0,0 +1,39 @@ +# Python WASM 语法候选兜底:局部验收 + +对应 OpenSpec `introduce-rust-codeguard-cli` 的 14.1、14.6、14.7、14.9 局部进展。此记录只适用于源码以 `wasm-precheck` 特性构建的 CLI;公开 npm `0.1.2` 未包含此能力。 + +`lint python` 先执行原有 Ruff 路径。只有选中文件的原生反馈为 `incomplete`、无原生 finding,且原因是 Ruff 工具缺失或项目未明确声明 Ruff 配置时,才由固定 Python grammar 和有界 Rust worker 补充疑似语法观察。无效 Ruff 配置保留原生阻塞;Ruff 已执行时不会用 WASM 覆盖诊断。最多处理八个文件、每文件 1 MiB、总计 128 个疑似位置,并沿用命令截止时间。超范围文件明确列在 `unavailable`。 + +JSON 使用 [Python 反馈 0.14.0](../../schemas/python-lint-feedback-v0.14.schema.json)。示意字段如下,`native.reason` 保留实际原因,不统一伪称工具缺失: + +```json +{ + "schema_version": "0.14.0", + "command_status": "incomplete", + "native": { "status": "incomplete", "reason": "project_ruff_config_not_found" }, + "setup": { "requirement": "required", "reason": "native_confirmation_needed", "task_id": null }, + "syntax_precheck": { + "backend": "bundled_tree_sitter_wasm_candidate", + "status": "incomplete", + "reason": "grammar_version_unqualified", + "grammar_qualified": false, + "selected_files": 1, + "checked_files": 1, + "observations": [{ "path": "broken.py", "classification": "suspected" }] + }, + "delivery_decision": "not_evaluated", + "exit_code": 3 +} +``` + +此片段展示关键字段,完整报告另含源码/grammar SHA-256、位置、配置和同步状态。候选有位置不等于已证实源码违规;零位置也不等于 clean。`task_id: null` 表示尚未接通稳定 Python 原生确认任务,不能冒充已创建的任务。 + +验证命令: + +```bash +CARGO_NET_OFFLINE=true cargo test --locked -p codeguard-cli --features wasm-precheck --test python_syntax_fallback_candidate +``` + +五个端到端反例覆盖配置的 Ruff 缺失、项目未声明 Ruff、有效语法、无效 Ruff 配置和超大兄弟文件;均保持退出码 3、`delivery_decision=not_evaluated`。真实 Ruff 优先路径另由 `lint_python_cli` 的忽略式本机用例核对。报告经 0.14.0 JSON Schema 校验。 + +仍未完成:Python grammar 的版本/方言及语料验收、项目级多模块调度、稳定原生确认任务、能力匹配的复检关闭、公开发行和真实宿主对话渲染。因此相关 OpenSpec 总任务保持未勾选。