@@ -287,6 +287,62 @@ pub fn read_state_status<P: AsRef<Path>>(state_file: P) -> Result<FeatureStatus>
287287 Ok ( FeatureStatus :: Unknown )
288288}
289289
290+ /// 根據步驟取得預期狀態
291+ pub fn expected_status_for_step ( step : Step ) -> FeatureStatus {
292+ match step {
293+ Step :: None => FeatureStatus :: Unknown ,
294+ Step :: P1 => FeatureStatus :: P1DoneIntDeployed ,
295+ Step :: P2 => FeatureStatus :: P2E2EPassed ,
296+ Step :: P3 => FeatureStatus :: P3RefactoredIntDeployed ,
297+ Step :: P4 => FeatureStatus :: Ready ,
298+ }
299+ }
300+
301+ /// 更新 STATE.md 檔案中的狀態
302+ pub fn update_state_status < P : AsRef < Path > > (
303+ state_file : P ,
304+ feature_key : & str ,
305+ new_status : FeatureStatus ,
306+ ) -> Result < ( ) > {
307+ let path = state_file. as_ref ( ) ;
308+ let status_str = new_status. to_string ( ) ;
309+
310+ // 確保父目錄存在
311+ if let Some ( parent) = path. parent ( ) {
312+ std:: fs:: create_dir_all ( parent) ?;
313+ }
314+
315+ if path. exists ( ) {
316+ // 更新現有檔案
317+ let content = std:: fs:: read_to_string ( path)
318+ . with_context ( || format ! ( "無法讀取狀態檔案:{}" , path. display( ) ) ) ?;
319+
320+ let re = Regex :: new ( r"STATUS[::]\s*\S+" ) ?;
321+
322+ let new_content = if re. is_match ( & content) {
323+ // 替換現有 STATUS
324+ re. replace ( & content, format ! ( "STATUS: {}" , status_str) )
325+ . to_string ( )
326+ } else {
327+ // 在檔案開頭加入 STATUS
328+ format ! ( "STATUS: {}\n \n {}" , status_str, content)
329+ } ;
330+
331+ std:: fs:: write ( path, new_content)
332+ . with_context ( || format ! ( "無法寫入狀態檔案:{}" , path. display( ) ) ) ?;
333+ } else {
334+ // 建立新檔案
335+ let content = format ! (
336+ "# {} State\n \n FEATURE_KEY: {}\n STATUS: {}\n \n ## Notes\n \n (Auto-generated by runner)\n " ,
337+ feature_key, feature_key, status_str
338+ ) ;
339+ std:: fs:: write ( path, content)
340+ . with_context ( || format ! ( "無法建立狀態檔案:{}" , path. display( ) ) ) ?;
341+ }
342+
343+ Ok ( ( ) )
344+ }
345+
290346// ============================================================================
291347// 功能資訊
292348// ============================================================================
@@ -398,4 +454,65 @@ LAST_DONE="p2"
398454 FeatureStatus :: P1DoneIntDeployed
399455 ) ;
400456 }
457+
458+ #[ test]
459+ fn test_expected_status_for_step ( ) {
460+ assert_eq ! ( expected_status_for_step( Step :: None ) , FeatureStatus :: Unknown ) ;
461+ assert_eq ! (
462+ expected_status_for_step( Step :: P1 ) ,
463+ FeatureStatus :: P1DoneIntDeployed
464+ ) ;
465+ assert_eq ! (
466+ expected_status_for_step( Step :: P2 ) ,
467+ FeatureStatus :: P2E2EPassed
468+ ) ;
469+ assert_eq ! (
470+ expected_status_for_step( Step :: P3 ) ,
471+ FeatureStatus :: P3RefactoredIntDeployed
472+ ) ;
473+ assert_eq ! ( expected_status_for_step( Step :: P4 ) , FeatureStatus :: Ready ) ;
474+ }
475+
476+ #[ test]
477+ fn test_update_state_status_creates_file ( ) {
478+ let temp_dir = std:: env:: temp_dir ( ) . join ( "test_state_md" ) ;
479+ std:: fs:: create_dir_all ( & temp_dir) . unwrap ( ) ;
480+ let state_file = temp_dir. join ( "STATE.md" ) ;
481+
482+ // 確保檔案不存在
483+ let _ = std:: fs:: remove_file ( & state_file) ;
484+
485+ // 建立新檔案
486+ update_state_status ( & state_file, "test-feature" , FeatureStatus :: Ready ) . unwrap ( ) ;
487+
488+ let content = std:: fs:: read_to_string ( & state_file) . unwrap ( ) ;
489+ assert ! ( content. contains( "STATUS: READY" ) ) ;
490+ assert ! ( content. contains( "FEATURE_KEY: test-feature" ) ) ;
491+
492+ // 清理
493+ let _ = std:: fs:: remove_file ( & state_file) ;
494+ let _ = std:: fs:: remove_dir ( & temp_dir) ;
495+ }
496+
497+ #[ test]
498+ fn test_update_state_status_updates_existing ( ) {
499+ let temp_dir = std:: env:: temp_dir ( ) . join ( "test_state_md_update" ) ;
500+ std:: fs:: create_dir_all ( & temp_dir) . unwrap ( ) ;
501+ let state_file = temp_dir. join ( "STATE.md" ) ;
502+
503+ // 建立初始檔案
504+ std:: fs:: write ( & state_file, "# Test\n \n STATUS: P1_DONE_DEPLOYED\n \n Some content" ) . unwrap ( ) ;
505+
506+ // 更新狀態
507+ update_state_status ( & state_file, "test-feature" , FeatureStatus :: Ready ) . unwrap ( ) ;
508+
509+ let content = std:: fs:: read_to_string ( & state_file) . unwrap ( ) ;
510+ assert ! ( content. contains( "STATUS: READY" ) ) ;
511+ assert ! ( !content. contains( "P1_DONE_DEPLOYED" ) ) ;
512+ assert ! ( content. contains( "Some content" ) ) ;
513+
514+ // 清理
515+ let _ = std:: fs:: remove_file ( & state_file) ;
516+ let _ = std:: fs:: remove_dir ( & temp_dir) ;
517+ }
401518}
0 commit comments