diff --git a/README.md b/README.md index edc69b97..d8840292 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ Generic `dependencies`, generic `security`, arbitrary category/language combinat 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). +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. The individual suspected positions and a capability-matched native closure still need implementation. [Workbench acceptance](tests/acceptance/typescript-syntax-confirmation-task.md). + ```bash codeguard lint java . codeguard lint typescript . diff --git a/README.zh-CN.md b/README.zh-CN.md index 472d0352..dfbdd6ba 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -182,6 +182,8 @@ Python 编辑快反馈可执行 `codeguard lint python . --file src/changed.py - 可选源码构建已有 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)。 +已初始化工作区使用 `--workspace` 时,TypeScript/TSX 候选回退在同步后返回真实且稳定的原生确认任务 ID;反复 WASM 扫描不会自行关闭任务。逐个疑似位置的持久关联和能力匹配的原生关闭仍待实现,见[局部验收](tests/acceptance/typescript-syntax-confirmation-task.md)。 + ```bash codeguard lint java . codeguard lint typescript . diff --git a/crates/codeguard-cli/src/eslint_lint_command.rs b/crates/codeguard-cli/src/eslint_lint_command.rs index 5eb93981..566afa7a 100644 --- a/crates/codeguard-cli/src/eslint_lint_command.rs +++ b/crates/codeguard-cli/src/eslint_lint_command.rs @@ -61,7 +61,11 @@ pub fn run(args: &[String]) -> ExitCode { ) } } - Ok(None) => crate::typescript_syntax_precheck::observe(&args, deadline), + Ok(None) => { + let mut report = crate::typescript_syntax_precheck::observe(&args, deadline); + connect_syntax_confirmation(&args, &mut report); + report + } Err(reason) => observe_with_preparation_reason(&args, deadline, Some(reason)), }; if args.json { @@ -87,6 +91,34 @@ pub fn run(args: &[String]) -> ExitCode { 3 }) } +#[cfg(feature = "wasm-precheck")] +fn connect_syntax_confirmation(args: &EslintLintArguments, report: &mut Value) { + let Some(workspace) = &args.workspace else { + return; + }; + report["schema_version"] = json!("0.5.0"); + let reason = if report["syntax_precheck"]["checked_files"] == 1 + && report["syntax_precheck"]["source_sha256"].is_string() + && report["syntax_precheck"]["grammar_sha256"].is_string() + { + "eslint_syntax_confirmation_needed" + } else { + "eslint_syntax_precheck_unavailable" + }; + report["workbench"] = match workspace.canonicalize() { + Ok(root) if root == *workspace => { + crate::eslint_workbench::connect_preparation(&root, args, reason) + } + _ => json!({"status":"workspace_invalid"}), + }; + report["workbench_status"] = report["workbench"]["status"].clone(); + if let Some(task_id) = report["workbench"]["task_id"].as_str() { + report["setup"]["task_id"] = json!(task_id); + if let Some(step) = report["workbench"]["next"]["repair_brief"]["step"].as_str() { + report["next_action"] = json!(step); + } + } +} pub(crate) fn observe_with_preparation(args: &EslintLintArguments, deadline: Instant) -> Value { observe_with_preparation_reason(args, deadline, None) } diff --git a/crates/codeguard-cli/src/eslint_preparation.rs b/crates/codeguard-cli/src/eslint_preparation.rs index 953bb122..c8445aba 100644 --- a/crates/codeguard-cli/src/eslint_preparation.rs +++ b/crates/codeguard-cli/src/eslint_preparation.rs @@ -142,6 +142,8 @@ pub(crate) fn valid_reason(reason: &str) -> bool { matches!( reason, "eslint_execution_context_missing" + | "eslint_syntax_confirmation_needed" + | "eslint_syntax_precheck_unavailable" | "eslint_node_runtime_unresolved" | "eslint_project_manifest_untrusted" | "eslint_local_dependency_path_untrusted" @@ -302,6 +304,12 @@ pub(crate) fn guidance(root: &Path, fact: &Value) -> Value { return fallback; } let step = match report["diagnostic_reason"].as_str() { + Some("eslint_syntax_confirmation_needed") => { + "当前源码只有未验收 WASM 语法初检;核对本轮疑似位置,恢复项目适用的原生 TypeScript/ESLint 语法检查并复检同一输入。初检正常、安装完成或任务勾选均不能关闭确认任务" + } + Some("eslint_syntax_precheck_unavailable") => { + "本轮内置语法初检未完成;核对 CLI 的具体未完成原因,恢复项目适用的原生 TypeScript/ESLint 语法检查并复检同一输入,不按未完成报告修改源码或关闭任务" + } Some("eslint_node_runtime_unresolved") => { "项目本地 ESLint 候选与原配置已发现;核对并提供受控 Node 路径后执行原生复检,不重复安装 ESLint 或修改无依据源码" } diff --git a/crates/codeguard-cli/src/eslint_workbench.rs b/crates/codeguard-cli/src/eslint_workbench.rs index aa2c8e57..32d72e9e 100644 --- a/crates/codeguard-cli/src/eslint_workbench.rs +++ b/crates/codeguard-cli/src/eslint_workbench.rs @@ -24,7 +24,27 @@ pub(crate) fn connect( } pub(crate) fn connect_preparation(root: &Path, args: &EslintLintArguments, reason: &str) -> Value { match crate::eslint_preparation::prepare(root, args, reason) { - Ok(report) => persist(root, report, "node.eslint.preparation"), + Ok(report) => { + let id = report["blocker_id"].as_str().unwrap_or("").to_owned(); + let mut result = persist(root, report, "node.eslint.preparation"); + if matches!( + reason, + "eslint_syntax_confirmation_needed" | "eslint_syntax_precheck_unavailable" + ) && result["status"] == "synced_partial" + { + if let Ok(brief) = crate::next_command::read_task_brief(root, &id) { + result["task_id"] = json!(id); + result["next"] = json!({ + "schema_version":"0.1.0","report_type":"repair_brief_preview", + "operation":"next","command_status":"complete","exit_code":0, + "disposition":brief["disposition"],"reason":"current_source_confirmation_task", + "repair_brief":brief,"next_actions":[], + "authority":"local_unverified","delivery_decision":"not_evaluated" + }); + } + } + result + } Err(reason) => json!({"status":reason}), } } diff --git a/crates/codeguard-cli/tests/typescript_syntax_fallback_candidate.rs b/crates/codeguard-cli/tests/typescript_syntax_fallback_candidate.rs index 863f46f7..08fb3b2d 100644 --- a/crates/codeguard-cli/tests/typescript_syntax_fallback_candidate.rs +++ b/crates/codeguard-cli/tests/typescript_syntax_fallback_candidate.rs @@ -72,6 +72,173 @@ fn missing_native_context_yields_scoped_suspected_observation_and_incomplete_gat assert!(!project.0.join(".codeguard").exists()); } +#[test] +fn initialized_wasm_precheck_keeps_one_native_confirmation_task() { + let project = Project::new(); + let root = project.0.canonicalize().unwrap(); + let source = root.join("bad.ts"); + fs::write(&source, "const x: number = ;\n").unwrap(); + let init = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args([ + "init", + root.to_str().unwrap(), + "--apply", + "--format", + "json", + ]) + .output() + .unwrap(); + assert_eq!(init.status.code(), Some(3)); + let lint = || { + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args([ + "lint", + "typescript", + source.to_str().unwrap(), + "--workspace", + root.to_str().unwrap(), + "--format", + "json", + ]) + .output() + .unwrap(); + assert_eq!(output.status.code(), Some(3)); + serde_json::from_slice::(&output.stdout).unwrap() + }; + let first = lint(); + assert_eq!(first["schema_version"], "0.5.0"); + let schema: Value = serde_json::from_str(include_str!( + "../../../schemas/eslint-local-feedback-v0.5.schema.json" + )) + .unwrap(); + assert_eq!(schema["properties"]["schema_version"]["const"], "0.5.0"); + assert_eq!(schema["additionalProperties"], false); + let actual: std::collections::BTreeSet<_> = first.as_object().unwrap().keys().collect(); + let required: std::collections::BTreeSet<_> = schema["required"] + .as_array() + .unwrap() + .iter() + .map(|value| value.as_str().unwrap()) + .collect(); + assert_eq!( + actual + .iter() + .map(|value| value.as_str()) + .collect::>(), + required + ); + assert!( + !first["syntax_precheck"]["observations"] + .as_array() + .unwrap() + .is_empty() + ); + assert_eq!(first["workbench_status"], "synced_partial"); + assert_eq!(first["workbench"]["new_blockers"], 1); + assert!( + first["next_action"] + .as_str() + .unwrap() + .contains("WASM 语法初检") + ); + let task_id = first["setup"]["task_id"].as_str().unwrap(); + assert!( + root.join(".codeguard/tasks") + .join(format!("{task_id}.md")) + .is_file() + ); + let fact: Value = serde_json::from_slice( + &fs::read( + root.join(".codeguard/findings") + .join(task_id) + .join("finding.json"), + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(fact["kind"], "blocker"); + assert_eq!(fact["checker_id"], "node.eslint.preparation"); + assert_eq!(fact["state"], "open"); + let second = lint(); + assert_eq!(second["setup"]["task_id"], task_id); + assert_eq!(second["workbench"]["new_blockers"], 0); + assert_eq!(second["delivery_decision"], "not_evaluated"); + fs::write(&source, "const x: number = 1;\n").unwrap(); + let changed = lint(); + assert_eq!(changed["setup"]["task_id"], task_id); + assert_eq!(changed["workbench"]["new_blockers"], 0); + assert_eq!(changed["delivery_decision"], "not_evaluated"); + + let tsx = root.join("Component.tsx"); + fs::write(&tsx, "const App = () =>
;\n").unwrap(); + let tsx_output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args([ + "lint", + "typescript", + tsx.to_str().unwrap(), + "--workspace", + root.to_str().unwrap(), + "--format", + "json", + ]) + .output() + .unwrap(); + assert_eq!(tsx_output.status.code(), Some(3)); + let tsx_report: Value = serde_json::from_slice(&tsx_output.stdout).unwrap(); + assert_eq!(tsx_report["schema_version"], "0.5.0"); + assert_eq!(tsx_report["syntax_precheck"]["language"], "tsx"); + assert_eq!(tsx_report["workbench"]["new_blockers"], 1); + assert_ne!(tsx_report["setup"]["task_id"], task_id); + assert_eq!( + tsx_report["workbench"]["next"]["repair_brief"]["task_id"], + tsx_report["setup"]["task_id"] + ); +} + +#[test] +fn initialized_workspace_keeps_precheck_failure_as_native_setup_task() { + let project = Project::new(); + let root = project.0.canonicalize().unwrap(); + let source = root.join("large.ts"); + fs::write(&source, vec![b'a'; 1024 * 1024 + 1]).unwrap(); + let init = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args([ + "init", + root.to_str().unwrap(), + "--apply", + "--format", + "json", + ]) + .output() + .unwrap(); + assert_eq!(init.status.code(), Some(3)); + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args([ + "lint", + "typescript", + source.to_str().unwrap(), + "--workspace", + root.to_str().unwrap(), + "--format", + "json", + ]) + .output() + .unwrap(); + assert_eq!(output.status.code(), Some(3)); + let report: Value = serde_json::from_slice(&output.stdout).unwrap(); + assert_eq!(report["schema_version"], "0.5.0"); + assert_eq!(report["syntax_precheck"]["reason"], "source_unavailable"); + assert_eq!(report["workbench_status"], "synced_partial"); + assert!(report["setup"]["task_id"].as_str().is_some()); + assert!(report["findings"].as_array().unwrap().is_empty()); + assert!( + report["next_action"] + .as_str() + .unwrap() + .contains("初检未完成") + ); +} + #[test] fn no_recovery_remains_incomplete_and_javascript_does_not_use_typescript_grammar() { let project = Project::new(); diff --git a/docs/Codeguard-Command-Reference.md b/docs/Codeguard-Command-Reference.md index c252fba6..752a220a 100644 --- a/docs/Codeguard-Command-Reference.md +++ b/docs/Codeguard-Command-Reference.md @@ -12,6 +12,8 @@ Current slices include static discovery/init, selected native checks, partial ag The opt-in `codeguard-cli/wasm-precheck` build adds narrow `lint typescript ` and `lint java ` paths when no explicit native execution context is provided. TypeScript first recognizes an ordinary project-local ESLint 10 entry and one flat config, then uses an executable Node from `PATH` to run the existing bounded native probe; when Node is unresolved, it reports setup instead of starting WASM. Only when no project-local ESLint package is observed does it return [TypeScript feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) or TSX 0.4.0 candidate feedback. Untrusted local paths, package identity or config selection remain setup blockers. Java returns [Java feedback 0.1.0](../schemas/java-syntax-precheck-feedback-v0.1.schema.json). These paths retain exit 3 and never turn an unvalidated grammar or local native observation into delivery approval. Default/published binaries retain the existing native-context behavior. See [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). +With `--workspace` pointing to an initialized, canonical workspace, the TypeScript/TSX fallback emits [feedback 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json). It saves one source-scoped ESLint native-confirmation blocker and returns its real `setup.task_id` only after successful sync. Repeated scans and a later WASM result with no recovery nodes keep that task open; the individual suspected positions still need durable linkage and capability-matched native verification. See the [workbench acceptance record](../tests/acceptance/typescript-syntax-confirmation-task.md). + `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. diff --git a/docs/Codeguard-Command-Reference.zh_CN.md b/docs/Codeguard-Command-Reference.zh_CN.md index 890eb77a..d1846444 100644 --- a/docs/Codeguard-Command-Reference.zh_CN.md +++ b/docs/Codeguard-Command-Reference.zh_CN.md @@ -22,6 +22,8 @@ [已验收实现切片](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md)跟踪当前局部能力;[tasks](../openspec/changes/introduce-rust-codeguard-cli/tasks.md)独占完成状态。WASM 的完整统一入口仍为 S14 待办。可选 `codeguard-cli/wasm-precheck` 构建处理 `lint typescript <显式单文件>` 时,若观察到本地 ESLint 10 入口与唯一 flat config,会优先用 `PATH` 中可执行 Node 调用既有有界原生探针;缺 Node 则返回准备缺口。仅未观察到本地 ESLint 包时才输出 [TypeScript 0.3.0 候选反馈](../schemas/eslint-local-feedback-v0.3.schema.json)或 TSX 0.4.0。本地入口、包身份或配置不可信时保留具体环境阻塞,不启动 WASM。`lint java <显式单文件>` 仍输出 [Java 0.1.0 候选反馈](../schemas/java-syntax-precheck-feedback-v0.1.schema.json)。这些局部路径均不批准交付并保持退出 3;部分显式参数、Javadoc/Checkstyle 和符号链接沿用原路径,默认及已发布二进制也不含这项自动调度。见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)与技术方案 5.3–5.4;后文未标当前的语法、退出0/1与闭环承诺均为目标契约。 +对已初始化且使用规范绝对路径的 `--workspace`,TypeScript/TSX 候选回退改用[反馈 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json):按源码范围保存同一张 ESLint 原生确认阻塞任务,仅同步成功时返回真实 `setup.task_id`。重复扫描和后续 WASM 无恢复节点不关闭任务。逐个疑似位置的持久关联及能力匹配原生复检仍未接通,见[局部验收](../tests/acceptance/typescript-syntax-confirmation-task.md)。 + ## 1. 命令体系与协作路线 ```mermaid diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index b4f2ea67..835c0e24 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -183,7 +183,7 @@ Core now has a local pure setup-action selector taking an upstream-verified nati When some files have suspected issues and others are unresolved, use overall `incomplete` while retaining all usable suspected observations. `clean` requires nonempty, completely checked selected scope. -These are the target semantic fields, not a shipped project-wide lint/check report. A narrow [candidate precheck schema](../schemas/syntax-precheck-candidate.schema.json) and [strict Rust reader](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs) bind source SHA-256, pinned grammar identity and file dialect, recalculate status, and reject unknown versions or forged `clean`. Optional CLI single-file [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json), [TSX](../schemas/eslint-local-feedback-v0.4.schema.json) and [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) producers include recovery positions and setup guidance; host rendering and durable task references remain absent. The bundled grammars remain unvalidated, so these reports cannot return `clean`. Missing required native execution remains incomplete (`3`); a suspected parser issue alone is not a confirmed violation (`1`). If a dedicated syntax-only operation is later introduced, its success must be explicitly scoped to syntax; no new `syntax` command is claimed here. +These are the target semantic fields, not a shipped project-wide lint/check report. A narrow [candidate precheck schema](../schemas/syntax-precheck-candidate.schema.json) and [strict Rust reader](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs) bind source SHA-256, pinned grammar identity and file dialect, recalculate status, and reject unknown versions or forged `clean`. Optional CLI single-file [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json), [TSX](../schemas/eslint-local-feedback-v0.4.schema.json) and [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) producers include recovery positions and setup guidance. With an initialized workspace, TypeScript/TSX now uses [feedback 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json) and attaches one stable native-confirmation task to the source scope; individual recovery positions are not yet persisted into that task, and host rendering remains absent. The bundled grammars remain unvalidated, so these reports cannot return `clean`. Missing required native execution remains incomplete (`3`); a suspected parser issue alone is not a confirmed violation (`1`). If a dedicated syntax-only operation is later introduced, its success must be explicitly scoped to syntax; no new `syntax` command is claimed here. The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index efd8cff3..fc456289 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -183,7 +183,7 @@ flowchart LR 部分文件有疑似异常、另有文件未解析时,总体为 `incomplete`,同时保留所有可用疑似观察。`clean` 要求选定范围非空且全部完成检查。 -这些是目标语义字段,尚非已发行的项目级 lint/check 报告。局部的[候选初检 schema](../schemas/syntax-precheck-candidate.schema.json) 与 [Rust 严格读者](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs)已绑定源码 SHA-256、固定 grammar 身份和文件方言,重新计算状态,并拒绝未知版本及伪造的 `clean`。可选 CLI 的 [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json)、[TSX](../schemas/eslint-local-feedback-v0.4.schema.json) 与 [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) 单文件生产端已提供恢复位置和准备指引;宿主渲染与持久任务引用仍缺。内置 grammar 仍是未验收候选,因此不能返回 `clean`。保持既有退出语义:必需原生执行缺失仍为未完成(`3`);解析器疑似问题本身不是已确认违规(`1`)。若未来增加独立语法操作,其成功必须限定为语法初检;本文不宣称已有 `syntax` 命令。 +这些是目标语义字段,尚非已发行的项目级 lint/check 报告。局部的[候选初检 schema](../schemas/syntax-precheck-candidate.schema.json) 与 [Rust 严格读者](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs)已绑定源码 SHA-256、固定 grammar 身份和文件方言,重新计算状态,并拒绝未知版本及伪造的 `clean`。可选 CLI 的 [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json)、[TSX](../schemas/eslint-local-feedback-v0.4.schema.json) 与 [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) 单文件生产端已提供恢复位置和准备指引。TypeScript/TSX 在已初始化工作区改用[反馈 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json),按源码范围关联一张稳定的原生确认任务;逐个恢复位置尚未持久关联到任务,宿主渲染也未接通。内置 grammar 仍是未验收候选,因此不能返回 `clean`。保持既有退出语义:必需原生执行缺失仍为未完成(`3`);解析器疑似问题本身不是已确认违规(`1`)。若未来增加独立语法操作,其成功必须限定为语法初检;本文不宣称已有 `syntax` 命令。 可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index fc9a96c9..ca72cd77 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1036,8 +1036,8 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 - [ ] 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) 验收。正式全范围/嵌入区域/排除原因、持久化任务引用、所有消费者未知 major 拒绝与发布兼容仍缺,不勾选。 - [ ] 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,任务引用为空;Java 超大输入保留 worker 未完成,见 [TypeScript](../../../tests/acceptance/typescript-syntax-fallback-candidate.md) 与 [Java](../../../tests/acceptance/java-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)。WASM 疑似观察到确认任务的关联、失败尝试/无进展预算、推荐项非阻断投影和多模块任务尚缺,不勾选。 +- [ ] 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.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)。逐个疑似位置与任务证据的持久关联、失败尝试/无进展预算、推荐项非阻断投影和多模块任务尚缺,不勾选。 - [ ] 14.11 能力匹配复检。责任:core/adapters。规格:SP08。依赖:14.10。验收:同模块/输入/方言的原生语法能力确认才能关闭;仅安装、后续 WASM clean 或格式检查不能关闭;复发重开。 - [ ] 14.12 精确误报纠错。责任:core。规格:SP09。依赖:14.11及既有白名单提案。验收:绑定 grammar/规则/范围/版本和可信审批,变更失效;原生反证保留历史并进语料,不撤销原生义务。 - [ ] 14.13 缓存与失效恢复。责任:runtime/core。规格:SP10。依赖:14.7。验收:输入、grammar、runtime、规则和配置变化失效;不缓存不完整为 clean;升级回滚不丢历史。 diff --git a/schemas/eslint-local-feedback-v0.5.schema.json b/schemas/eslint-local-feedback-v0.5.schema.json new file mode 100644 index 00000000..1601a5f0 --- /dev/null +++ b/schemas/eslint-local-feedback-v0.5.schema.json @@ -0,0 +1,290 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:codeguard:eslint-local-feedback:0.5.0", + "title": "Incomplete TypeScript or TSX syntax precheck feedback with optional local confirmation task", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "status", + "local_coherent", + "coverage_proven", + "delivery_decision", + "reason", + "findings", + "suppressed_count", + "workbench_status", + "workbench", + "native", + "setup", + "syntax_precheck", + "next_action" + ], + "properties": { + "schema_version": { + "const": "0.5.0" + }, + "report_type": { + "const": "eslint_local_feedback" + }, + "status": { + "const": "incomplete" + }, + "local_coherent": { + "const": false + }, + "coverage_proven": { + "const": false + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "reason": { + "const": "eslint_execution_context_missing" + }, + "findings": { + "const": [] + }, + "suppressed_count": { + "const": 0 + }, + "workbench_status": { + "type": "string", + "minLength": 1 + }, + "workbench": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "minLength": 1 + }, + "new_findings": { + "type": "integer", + "minimum": 0 + }, + "new_blockers": { + "type": "integer", + "minimum": 0 + }, + "failed_reports": { + "type": "integer", + "minimum": 0 + }, + "next": { + "type": [ + "object", + "null" + ] + }, + "next_error": { + "type": [ + "string", + "null" + ] + }, + "task_id": { + "type": "string", + "pattern": "^CG-B-[0-9a-f]{32}$" + } + } + } + ] + }, + "native": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "reason" + ], + "properties": { + "status": { + "const": "not_run" + }, + "reason": { + "const": "explicit_context_missing" + } + } + }, + "setup": { + "type": "object", + "additionalProperties": false, + "required": [ + "requirement", + "reason", + "task_id" + ], + "properties": { + "requirement": { + "const": "required" + }, + "reason": { + "const": "native_confirmation_needed" + }, + "task_id": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^CG-B-[0-9a-f]{32}$" + } + ] + } + } + }, + "syntax_precheck": { + "type": "object", + "additionalProperties": false, + "required": [ + "backend", + "language", + "status", + "reason", + "source_path", + "source_sha256", + "grammar_sha256", + "grammar_qualified", + "selected_files", + "checked_files", + "truncated", + "observations" + ], + "properties": { + "backend": { + "const": "bundled_tree_sitter_wasm_candidate" + }, + "language": { + "enum": [ + "typescript", + "tsx" + ] + }, + "status": { + "const": "incomplete" + }, + "reason": { + "enum": [ + "not_run", + "source_unavailable", + "worker_executable_unavailable", + "worker_incomplete", + "recovery_budget_truncated", + "grammar_version_unqualified", + "position_mapping_incomplete" + ] + }, + "source_path": { + "type": "string", + "minLength": 1 + }, + "source_sha256": { + "$ref": "#/$defs/nullable_digest" + }, + "grammar_sha256": { + "$ref": "#/$defs/nullable_digest" + }, + "grammar_qualified": { + "const": false + }, + "selected_files": { + "const": 1 + }, + "checked_files": { + "enum": [ + 0, + 1 + ] + }, + "truncated": { + "type": "boolean" + }, + "observations": { + "type": "array", + "maxItems": 128, + "items": { + "$ref": "#/$defs/observation" + } + } + } + }, + "next_action": { + "type": "string", + "minLength": 1 + } + }, + "$defs": { + "nullable_digest": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + ] + }, + "observation": { + "type": "object", + "additionalProperties": false, + "required": [ + "classification", + "kind", + "syntax_kind", + "start_line", + "start_column", + "end_line", + "end_column", + "group_id" + ], + "properties": { + "classification": { + "const": "suspected" + }, + "kind": { + "enum": [ + "ERROR", + "MISSING" + ] + }, + "syntax_kind": { + "type": "string", + "minLength": 1 + }, + "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 + } + } + } + } +} diff --git a/tests/acceptance/typescript-syntax-confirmation-task.md b/tests/acceptance/typescript-syntax-confirmation-task.md new file mode 100644 index 00000000..e24802be --- /dev/null +++ b/tests/acceptance/typescript-syntax-confirmation-task.md @@ -0,0 +1,9 @@ +# TypeScript/TSX WASM 初检的原生确认任务:局部验收(2026-09-29) + +对应 OpenSpec 14.9、14.10。可选 `wasm-precheck` 构建中,`codeguard lint typescript <单文件> --workspace <已初始化工作区> --format json` 仅在未观察到项目本地 ESLint 包时运行候选语法初检。CLI 不把恢复节点写成已确认源码 finding,而是复用 ESLint 前置报告、`work sync` 和 `next`,按工作区与源码范围生成一张稳定的原生确认阻塞任务。未提供工作区时沿用 0.3.0(TypeScript)或 0.4.0(TSX)报告,任务 ID 为 null;提供工作区时报告升为[0.5.0](../../schemas/eslint-local-feedback-v0.5.schema.json),同步成功后才返回真实 `setup.task_id`。 + +先写端到端反例:已初始化工作区的语法初检返回 `workbench=not_connected`,断言失败。接线后,连续两次对含语法恢复节点的源码初检,只产生一个 `node.eslint.preparation`、`kind=blocker` 的任务,第二次新增阻塞数为零;把源码改成无恢复节点后仍复用同一开放任务,`delivery_decision=not_evaluated`。超过 1 MiB 的源码使初检未运行时,也保留单独的未完成诊断和真实环境任务,不产生源码 finding。`next_action` 明确要求同一输入的适用原生语法复检,不能把 WASM 零恢复、安装成功或任务勾选当作关闭证据。工作台不可用时不填虚构任务 ID。 + +当前持久报告只保存源码范围、源码 SHA-256、前置诊断和报告摘要;CLI 当轮反馈显示的**逐个疑似位置尚未持久关联**到该任务。任务也没有能力匹配的原生关闭收据,Java 初检和多模块调度未接入这条工作台路径。因此 14.9、14.10、14.11 均不勾选,不能据此宣称完整修复闭环或语法能力已发布。 + +定向验证:`CARGO_NET_OFFLINE=true cargo test --locked -p codeguard-cli --features wasm-precheck --test typescript_syntax_fallback_candidate --test eslint_lint_cli --test grammar_status_cli`,共 21 项通过、4 项要求真实 ESLint 10 的用例忽略;新增端到端反例覆盖 TypeScript 重扫、源码改动后不自动关闭、TSX 独立任务、初检未运行与 `next` 和 `setup.task_id` 一致。`cargo clippy --locked -p codeguard-cli --features wasm-precheck --all-targets -- -D warnings`、`cargo fmt --all -- --check`、`python3 scripts/check_layering.py`、`openspec validate introduce-rust-codeguard-cli --strict` 与 `git diff --check` 均通过。真实 ESLint 10 运行、完整工作区 CI 及宿主对话验收不由这些局部测试证明。