@@ -5,7 +5,9 @@ use crate::check_budget::{
55} ;
66use crate :: discovery:: discover;
77use crate :: next_command:: read_local_brief;
8- use crate :: python_lint_scan:: { PythonLintScanRequest , python_lint_feedback, scan_python_lint} ;
8+ use crate :: python_lint_scan:: {
9+ PythonLintScanRequest , python_lint_feedback, scan_python_lint, select_checker,
10+ } ;
911use crate :: work_sync:: { save_local_report, sync_local_workspace} ;
1012use crate :: workspace_refresh:: read_workspace_baseline;
1113use codeguard_adapters:: { bundled_ruff_rulepack, legacy_registry} ;
@@ -14,7 +16,7 @@ use codeguard_runtime::{
1416} ;
1517use serde_json:: Value ;
1618use sha2:: { Digest , Sha256 } ;
17- use std:: collections:: BTreeMap ;
19+ use std:: collections:: { BTreeMap , BTreeSet } ;
1820use std:: fs;
1921use std:: os:: unix:: fs:: PermissionsExt ;
2022use std:: path:: { Path , PathBuf } ;
@@ -26,6 +28,7 @@ struct Arguments {
2628 root : PathBuf ,
2729 json : bool ,
2830 ruff_tool : Option < PathBuf > ,
31+ selected_paths : Vec < String > ,
2932 timeout_ms : u64 ,
3033 timeout_source : & ' static str ,
3134}
@@ -74,19 +77,34 @@ pub fn run(args: &[String]) -> ExitCode {
7477 }
7578 }
7679 let deadline = started + Duration :: from_millis ( parsed. timeout_ms ) ;
77- let mut feedback = match scan_and_sync_report_with_deadline (
78- & root,
79- parsed. ruff_tool . as_deref ( ) ,
80- deadline,
81- & AtomicBool :: new ( false ) ,
82- ) {
80+ let mut feedback = match if parsed. selected_paths . is_empty ( ) {
81+ scan_and_sync_report_with_deadline (
82+ & root,
83+ parsed. ruff_tool . as_deref ( ) ,
84+ deadline,
85+ & AtomicBool :: new ( false ) ,
86+ )
87+ } else {
88+ scan_selected_report_with_deadline (
89+ & root,
90+ parsed. ruff_tool . as_deref ( ) ,
91+ & parsed. selected_paths ,
92+ deadline,
93+ & AtomicBool :: new ( false ) ,
94+ )
95+ } {
8396 Ok ( feedback) => feedback,
8497 Err ( _) => {
8598 eprintln ! ( "内置语言清单损坏" ) ;
8699 return ExitCode :: from ( 4 ) ;
87100 }
88101 } ;
89102 annotate_conversation_budget ( & mut feedback, parsed. timeout_ms , parsed. timeout_source ) ;
103+ feedback[ "schema_version" ] = Value :: String ( "0.13.0" . into ( ) ) ;
104+ if parsed. selected_paths . is_empty ( ) {
105+ feedback[ "scan_scope" ] = Value :: String ( "discovered_python" . into ( ) ) ;
106+ feedback[ "requested_paths" ] = Value :: Null ;
107+ }
90108 let request_cancelled = codeguard_runtime:: sigint_cancellation_requested ( )
91109 || feedback[ "incomplete_reasons" ]
92110 . as_array ( )
@@ -184,6 +202,41 @@ pub fn annotate_conversation_budget(feedback: &mut Value, timeout_ms: u64, sourc
184202 document. insert ( "execution_budget" . into ( ) , budget_record ( timeout_ms, source) ) ;
185203}
186204
205+ /// 编辑快反馈仅观察指定文件,不把局部报告作为完整工作区报告导入工作台。
206+ fn scan_selected_report_with_deadline (
207+ root : & Path ,
208+ ruff_tool : Option < & Path > ,
209+ selected_paths : & [ String ] ,
210+ deadline : Instant ,
211+ cancelled : & AtomicBool ,
212+ ) -> Result < Value , & ' static str > {
213+ let mut feedback = scan_local_report_scoped_with_deadline (
214+ root,
215+ ruff_tool,
216+ Some ( selected_paths) ,
217+ deadline,
218+ cancelled,
219+ ) ?;
220+ let document = feedback. as_object_mut ( ) . expect ( "反馈根为对象" ) ;
221+ document. insert ( "scan_scope" . into ( ) , Value :: String ( "selected_files" . into ( ) ) ) ;
222+ document. insert ( "requested_paths" . into ( ) , serde_json:: json!( selected_paths) ) ;
223+ document. insert (
224+ "backlog_status" . into ( ) ,
225+ Value :: String ( "not_synced_scoped" . into ( ) ) ,
226+ ) ;
227+ document. insert ( "backlog_sync" . into ( ) , Value :: Null ) ;
228+ document. insert (
229+ "repair_brief_status" . into ( ) ,
230+ Value :: String ( "unavailable" . into ( ) ) ,
231+ ) ;
232+ document. insert (
233+ "repair_brief_reason" . into ( ) ,
234+ Value :: String ( "not_synced_scoped" . into ( ) ) ,
235+ ) ;
236+ document. insert ( "next" . into ( ) , Value :: Null ) ;
237+ Ok ( feedback)
238+ }
239+
187240/// 对项目运行同一条局部原生 Ruff 检查链,返回可供持久同步的 0.9 报告。
188241///
189242/// 参数 `root` 是规范化的项目根,`ruff_tool` 是可选的绝对可执行路径;
@@ -203,13 +256,33 @@ pub fn scan_local_report_with_deadline(
203256 ruff_tool : Option < & Path > ,
204257 deadline : Instant ,
205258 cancelled : & AtomicBool ,
259+ ) -> Result < Value , & ' static str > {
260+ scan_local_report_scoped_with_deadline ( root, ruff_tool, None , deadline, cancelled)
261+ }
262+
263+ fn scan_local_report_scoped_with_deadline (
264+ root : & Path ,
265+ ruff_tool : Option < & Path > ,
266+ selected_paths : Option < & [ String ] > ,
267+ deadline : Instant ,
268+ cancelled : & AtomicBool ,
206269) -> Result < Value , & ' static str > {
207270 let registry = legacy_registry ( ) . map_err ( |_| "registry_invalid" ) ?;
208271 let discovery = discover ( root, & registry, & NativeObservation ) ;
209272 let run_id = run_id ( ) ;
210- let configured = discovery. checker_configurations . iter ( ) . any ( |checker| {
211- checker. checker_id == "python.ruff" && checker. configuration == "configured"
212- } ) ;
273+ let configured = selected_paths. map_or_else (
274+ || {
275+ discovery. checker_configurations . iter ( ) . any ( |checker| {
276+ checker. checker_id == "python.ruff" && checker. configuration == "configured"
277+ } )
278+ } ,
279+ |paths| {
280+ paths. iter ( ) . any ( |path| {
281+ select_checker ( & discovery, path)
282+ . is_some_and ( |checker| checker. configuration == "configured" )
283+ } )
284+ } ,
285+ ) ;
213286 let tool = if configured {
214287 prepare_tool ( root, ruff_tool, deadline, & run_id, cancelled)
215288 } else {
@@ -230,6 +303,7 @@ pub fn scan_local_report_with_deadline(
230303 & PythonLintScanRequest {
231304 root,
232305 discovery : & discovery,
306+ selected_paths,
233307 tool : tool. path ,
234308 tool_unavailable_reason : tool. unavailable_reason ,
235309 expected_tool_sha256 : tool. sha256 ,
@@ -357,12 +431,13 @@ fn parse_args(args: &[String]) -> Result<Arguments, String> {
357431 let mut root = None ;
358432 let mut json = false ;
359433 let mut ruff_tool = None ;
434+ let mut selected_paths = Vec :: new ( ) ;
360435 let mut timeout_ms = 0 ;
361436 let mut timeout_seen = false ;
362437 let mut index = 1 ;
363438 while index < args. len ( ) {
364439 let arg = & args[ index] ;
365- if arg == "--format" || arg == "--ruff-tool" || arg == "--timeout" {
440+ if arg == "--format" || arg == "--ruff-tool" || arg == "--timeout" || arg == "--file" {
366441 index += 1 ;
367442 let value = args. get ( index) . ok_or_else ( || format ! ( "{arg} 缺少值" ) ) ?;
368443 if arg == "--format" {
@@ -373,6 +448,8 @@ fn parse_args(args: &[String]) -> Result<Arguments, String> {
373448 }
374449 timeout_ms = parse_check_timeout ( value) ?;
375450 timeout_seen = true ;
451+ } else if arg == "--file" {
452+ selected_paths. push ( value. clone ( ) ) ;
376453 } else if ruff_tool. replace ( PathBuf :: from ( value) ) . is_some ( ) {
377454 return Err ( "--ruff-tool 重复" . into ( ) ) ;
378455 }
@@ -388,11 +465,34 @@ fn parse_args(args: &[String]) -> Result<Arguments, String> {
388465 if ruff_tool. as_ref ( ) . is_some_and ( |tool| !tool. is_absolute ( ) ) {
389466 return Err ( "--ruff-tool 必须是绝对路径" . into ( ) ) ;
390467 }
468+ let mut distinct = BTreeSet :: new ( ) ;
469+ let mut total_bytes = 0 ;
470+ for path in & selected_paths {
471+ if path. is_empty ( )
472+ || path. len ( ) > 512
473+ || path. starts_with ( '/' )
474+ || path. contains ( '\\' )
475+ || path. chars ( ) . any ( char:: is_control)
476+ || path
477+ . split ( '/' )
478+ . any ( |part| part. is_empty ( ) || part == "." || part == ".." )
479+ {
480+ return Err ( "--file 必须是有效的工作区相对源码路径" . into ( ) ) ;
481+ }
482+ if distinct. insert ( path. clone ( ) ) {
483+ total_bytes += path. len ( ) ;
484+ }
485+ }
486+ if distinct. len ( ) > 8 || total_bytes > 2048 {
487+ return Err ( "--file 超出局部快检范围预算,请改用批量检查" . into ( ) ) ;
488+ }
489+ let selected_paths = distinct. into_iter ( ) . collect ( ) ;
391490 let ( timeout_ms, timeout_source) = select_check_timeout ( timeout_seen. then_some ( timeout_ms) ) ?;
392491 Ok ( Arguments {
393492 root : root. unwrap_or_else ( || PathBuf :: from ( "." ) ) ,
394493 json,
395494 ruff_tool,
495+ selected_paths,
396496 timeout_ms,
397497 timeout_source,
398498 } )
@@ -532,6 +632,11 @@ fn create_scratch(run_id: &str) -> std::io::Result<PrivateScratch> {
532632
533633fn print_human ( feedback : & Value ) {
534634 println ! ( "CodeGuard Python lint:局部原生检查;完整质量门禁尚未评估" ) ;
635+ println ! (
636+ "本轮范围:{};目标:{}" ,
637+ feedback[ "scan_scope" ] . as_str( ) . unwrap_or( "unknown" ) ,
638+ feedback[ "requested_paths" ]
639+ ) ;
535640 println ! (
536641 "修复记录:{}" ,
537642 feedback[ "backlog_status" ] . as_str( ) . unwrap_or( "unknown" )
0 commit comments