@@ -194,11 +194,6 @@ fn find_max_epic_number() -> u32 {
194194 flowctl_core:: json_store:: epic_max_num ( & flow_dir) . unwrap_or ( 0 )
195195}
196196
197- /// Bridge: DB connection for functions not yet migrated to json_store.
198- /// TODO(fn-24): Remove once all epic commands use json_store.
199- fn require_db ( ) -> Result < crate :: commands:: db_shim:: Connection , crate :: commands:: db_shim:: DbError > {
200- crate :: commands:: db_shim:: require_db ( )
201- }
202197
203198/// Create default epic spec Markdown body.
204199fn create_epic_spec_body ( id : & str , title : & str ) -> String {
@@ -460,12 +455,9 @@ fn cmd_set_title(id: &str, new_title: &str, json_mode: bool) {
460455
461456 let specs_dir = flow_dir. join ( SPECS_DIR ) ;
462457
463- // Check collision (if ID changed) via DB
458+ // Check collision (if ID changed) via JSON
464459 if new_id != old_id {
465- let conn = require_db ( )
466- . unwrap_or_else ( |e| error_exit ( & format ! ( "DB required: {e}" ) ) ) ;
467- let repo = crate :: commands:: db_shim:: EpicRepo :: new ( & conn) ;
468- if repo. get ( & new_id) . is_ok ( ) {
460+ if flowctl_core:: json_store:: epic_read ( & flow_dir, & new_id) . is_ok ( ) {
469461 error_exit ( & format ! (
470462 "Epic {new_id} already exists. Choose a different title."
471463 ) ) ;
@@ -501,11 +493,8 @@ fn cmd_set_title(id: &str, new_title: &str, json_mode: bool) {
501493 new_doc. frontmatter . updated_at = Utc :: now ( ) ;
502494 save_epic ( & new_doc) ;
503495
504- // Update task records in DB
505- let conn = require_db ( )
506- . unwrap_or_else ( |e| error_exit ( & format ! ( "DB required: {e}" ) ) ) ;
507- let task_repo = crate :: commands:: db_shim:: TaskRepo :: new ( & conn) ;
508- let tasks = task_repo. list_by_epic ( old_id) . unwrap_or_default ( ) ;
496+ // Update task records via JSON files
497+ let tasks = flowctl_core:: json_store:: task_list_by_epic ( & flow_dir, old_id) . unwrap_or_default ( ) ;
509498 let mut task_renames: Vec < ( String , String ) > = Vec :: new ( ) ;
510499 for task in & tasks {
511500 if let Ok ( p) = parse_id ( & task. id ) {
@@ -540,23 +529,19 @@ fn cmd_set_title(id: &str, new_title: &str, json_mode: bool) {
540529 } )
541530 . collect ( ) ;
542531 updated_task. updated_at = Utc :: now ( ) ;
543- let _ = task_repo. upsert ( & updated_task) ;
532+ // Delete old files, write new ones
533+ let _ = flowctl_core:: json_store:: task_delete ( & flow_dir, & task. id ) ;
534+ let _ = flowctl_core:: json_store:: task_write_definition ( & flow_dir, & updated_task) ;
544535 }
545536
546- // Update depends_on_epics in other epics that reference old_id (via DB)
537+ // Update depends_on_epics in other epics that reference old_id
547538 let mut updated_deps_in: Vec < String > = Vec :: new ( ) ;
548- let epic_repo = crate :: commands:: db_shim:: EpicRepo :: new ( & conn) ;
549- if let Ok ( all_epics) = epic_repo. list ( None ) {
550- let dep_repo = crate :: commands:: db_shim:: DepRepo :: new ( & conn) ;
539+ if let Ok ( all_epics) = flowctl_core:: json_store:: epic_list ( & flow_dir) {
551540 for other_epic in & all_epics {
552541 if other_epic. id == new_id || other_epic. id == old_id {
553542 continue ;
554543 }
555544 if other_epic. depends_on_epics . contains ( & old_id. to_string ( ) ) {
556- // Update: remove old dep, add new one
557- let _ = dep_repo. remove_epic_dep ( & other_epic. id , old_id) ;
558- let _ = dep_repo. add_epic_dep ( & other_epic. id , & new_id) ;
559- // Also update the Epic struct's depends_on_epics
560545 let mut updated_other = other_epic. clone ( ) ;
561546 updated_other. depends_on_epics = updated_other
562547 . depends_on_epics
@@ -570,7 +555,7 @@ fn cmd_set_title(id: &str, new_title: &str, json_mode: bool) {
570555 } )
571556 . collect ( ) ;
572557 updated_other. updated_at = Utc :: now ( ) ;
573- let _ = epic_repo . upsert ( & updated_other) ;
558+ let _ = flowctl_core :: json_store :: epic_write ( & flow_dir , & updated_other) ;
574559 updated_deps_in. push ( other_epic. id . clone ( ) ) ;
575560 }
576561 }
@@ -677,14 +662,10 @@ fn cmd_reopen(id: &str, json_mode: bool) {
677662 let flow_dir = ensure_flow_exists ( ) ;
678663 validate_epic_id ( id) ;
679664
680- // Check if archived (check .archive/ dir for specs/reviews)
665+ // Check if archived
681666 let archive_path = flow_dir. join ( ARCHIVE_DIR ) . join ( id) ;
682667 if archive_path. exists ( ) {
683- // Check if epic is marked archived in DB
684- let conn = require_db ( )
685- . unwrap_or_else ( |e| error_exit ( & format ! ( "DB required: {e}" ) ) ) ;
686- let repo = crate :: commands:: db_shim:: EpicRepo :: new ( & conn) ;
687- if let Ok ( epic) = repo. get ( id) {
668+ if let Ok ( epic) = flowctl_core:: json_store:: epic_read ( & flow_dir, id) {
688669 if epic. archived {
689670 error_exit ( & format ! (
690671 "Epic {id} is archived. Unarchive it first before reopening."
@@ -793,15 +774,11 @@ fn cmd_archive(id: &str, force: bool, json_mode: bool) {
793774fn cmd_clean ( json_mode : bool ) {
794775 let flow_dir = ensure_flow_exists ( ) ;
795776
796- let conn = require_db ( )
797- . unwrap_or_else ( |e| error_exit ( & format ! ( "DB required: {e}" ) ) ) ;
798- let epic_repo = crate :: commands:: db_shim:: EpicRepo :: new ( & conn) ;
799-
800777 let mut archived: Vec < String > = Vec :: new ( ) ;
801778
802- if let Ok ( epics) = epic_repo . list ( Some ( "done" ) ) {
779+ if let Ok ( epics) = flowctl_core :: json_store :: epic_list ( & flow_dir ) {
803780 for epic in & epics {
804- if !epic. archived {
781+ if epic . status == EpicStatus :: Done && !epic. archived {
805782 cmd_archive_silent ( & epic. id , & flow_dir) ;
806783 archived. push ( epic. id . clone ( ) ) ;
807784 }
@@ -827,14 +804,11 @@ fn cmd_clean(json_mode: bool) {
827804/// Silent archive helper for clean command (no output).
828805/// Sets archived=true in DB, moves only specs and reviews to .archive/.
829806fn cmd_archive_silent ( id : & str , flow_dir : & Path ) {
830- // Set archived=true in DB
831- if let Ok ( conn) = require_db ( ) {
832- let repo = crate :: commands:: db_shim:: EpicRepo :: new ( & conn) ;
833- if let Ok ( mut epic) = repo. get ( id) {
834- epic. archived = true ;
835- epic. updated_at = Utc :: now ( ) ;
836- let _ = repo. upsert ( & epic) ;
837- }
807+ // Set archived=true in JSON
808+ if let Ok ( mut epic) = flowctl_core:: json_store:: epic_read ( flow_dir, id) {
809+ epic. archived = true ;
810+ epic. updated_at = Utc :: now ( ) ;
811+ let _ = flowctl_core:: json_store:: epic_write ( flow_dir, & epic) ;
838812 }
839813
840814 let archive_dir = flow_dir. join ( ARCHIVE_DIR ) . join ( id) ;
@@ -870,11 +844,9 @@ fn cmd_add_dep(epic_id: &str, dep_id: &str, json_mode: bool) {
870844 error_exit ( "Epic cannot depend on itself" ) ;
871845 }
872846
873- // Verify dep epic exists in DB
874- let conn = require_db ( )
875- . unwrap_or_else ( |e| error_exit ( & format ! ( "DB required: {e}" ) ) ) ;
876- let repo = crate :: commands:: db_shim:: EpicRepo :: new ( & conn) ;
877- if repo. get ( dep_id) . is_err ( ) {
847+ // Verify dep epic exists
848+ let flow_dir = get_flow_dir ( ) ;
849+ if flowctl_core:: json_store:: epic_read ( & flow_dir, dep_id) . is_err ( ) {
878850 error_exit ( & format ! ( "Epic {dep_id} not found" ) ) ;
879851 }
880852
@@ -893,12 +865,6 @@ fn cmd_add_dep(epic_id: &str, dep_id: &str, json_mode: bool) {
893865 return ;
894866 }
895867
896- // Update DB via DepRepo
897- let dep_repo = crate :: commands:: db_shim:: DepRepo :: new ( & conn) ;
898- if let Err ( e) = dep_repo. add_epic_dep ( epic_id, dep_id) {
899- error_exit ( & format ! ( "Failed to add epic dep: {e}" ) ) ;
900- }
901-
902868 doc. frontmatter . depends_on_epics . push ( dep_id. to_string ( ) ) ;
903869 doc. frontmatter . updated_at = Utc :: now ( ) ;
904870 save_epic ( & doc) ;
@@ -933,14 +899,6 @@ fn cmd_rm_dep(epic_id: &str, dep_id: &str, json_mode: bool) {
933899 return ;
934900 }
935901
936- // Update DB via DepRepo
937- let conn = require_db ( )
938- . unwrap_or_else ( |e| error_exit ( & format ! ( "DB required: {e}" ) ) ) ;
939- let dep_repo = crate :: commands:: db_shim:: DepRepo :: new ( & conn) ;
940- if let Err ( e) = dep_repo. remove_epic_dep ( epic_id, dep_id) {
941- error_exit ( & format ! ( "Failed to remove epic dep: {e}" ) ) ;
942- }
943-
944902 doc. frontmatter
945903 . depends_on_epics
946904 . retain ( |d| d != dep_id) ;
@@ -1141,9 +1099,8 @@ fn cmd_audit(id: &str, force: bool, json_mode: bool) {
11411099 let epic_doc = load_epic ( id) ;
11421100 let epic_body = epic_doc. body . clone ( ) ;
11431101
1144- // Load tasks from DB.
1145- let conn = require_db ( ) . ok ( ) ;
1146- let tasks: Vec < flowctl_core:: types:: Task > = load_epic_tasks ( conn. as_ref ( ) , & flow_dir, id) ;
1102+ // Load tasks from JSON.
1103+ let tasks: Vec < flowctl_core:: types:: Task > = flowctl_core:: json_store:: task_list_by_epic ( & flow_dir, id) . unwrap_or_default ( ) ;
11471104
11481105 // Shape task summaries for the payload.
11491106 let task_entries: Vec < serde_json:: Value > = tasks
@@ -1255,12 +1212,10 @@ pub fn cmd_replay(json_mode: bool, epic_id: &str, dry_run: bool, force: bool) {
12551212 ensure_flow_exists ( ) ;
12561213 validate_epic_id ( epic_id) ;
12571214
1258- let conn = require_db ( )
1259- . unwrap_or_else ( |e| error_exit ( & format ! ( "DB required: {e}" ) ) ) ;
1215+ let flow_dir = get_flow_dir ( ) ;
12601216
1261- // Load tasks from DB
1262- let task_repo = crate :: commands:: db_shim:: TaskRepo :: new ( & conn) ;
1263- let tasks = task_repo. list_by_epic ( epic_id) . unwrap_or_default ( ) ;
1217+ // Load tasks from JSON
1218+ let tasks = flowctl_core:: json_store:: task_list_by_epic ( & flow_dir, epic_id) . unwrap_or_default ( ) ;
12641219 if tasks. is_empty ( ) {
12651220 error_exit ( & format ! ( "No tasks found for epic {}" , epic_id) ) ;
12661221 }
@@ -1304,11 +1259,12 @@ pub fn cmd_replay(json_mode: bool, epic_id: &str, dry_run: bool, force: bool) {
13041259 return ;
13051260 }
13061261
1307- // Reset all tasks to todo in DB only
1262+ // Reset all tasks to todo via JSON state
13081263 let mut reset_count = 0 ;
13091264 for task in & to_reset {
1310- if let Err ( e) = task_repo. update_status ( & task. id , flowctl_core:: state_machine:: Status :: Todo ) {
1311- eprintln ! ( "Warning: failed to reset {} in DB: {}" , task. id, e) ;
1265+ let blank = flowctl_core:: json_store:: TaskState :: default ( ) ;
1266+ if let Err ( e) = flowctl_core:: json_store:: state_write ( & flow_dir, & task. id , & blank) {
1267+ eprintln ! ( "Warning: failed to reset {} state: {}" , task. id, e) ;
13121268 }
13131269 reset_count += 1 ;
13141270 }
@@ -1328,20 +1284,6 @@ pub fn cmd_replay(json_mode: bool, epic_id: &str, dry_run: bool, force: bool) {
13281284 }
13291285}
13301286
1331- /// Load tasks for an epic from DB (sole source of truth).
1332- fn load_epic_tasks (
1333- conn : Option < & crate :: commands:: db_shim:: Connection > ,
1334- _flow_dir : & Path ,
1335- epic_id : & str ,
1336- ) -> Vec < flowctl_core:: types:: Task > {
1337- if let Some ( c) = conn {
1338- let task_repo = crate :: commands:: db_shim:: TaskRepo :: new ( c) ;
1339- if let Ok ( tasks) = task_repo. list_by_epic ( epic_id) {
1340- return tasks;
1341- }
1342- }
1343- Vec :: new ( )
1344- }
13451287
13461288// ── Diff command ────────────��───────────────────────────���──────────
13471289
@@ -1452,8 +1394,7 @@ pub fn cmd_diff(json_mode: bool, epic_id: &str) {
14521394
14531395/// Load branch name for an epic from DB (sole source of truth).
14541396fn load_epic_branch ( epic_id : & str ) -> Option < String > {
1455- let conn = require_db ( ) . ok ( ) ?;
1456- let epic_repo = crate :: commands:: db_shim:: EpicRepo :: new ( & conn) ;
1457- let epic = epic_repo. get ( epic_id) . ok ( ) ?;
1397+ let flow_dir = get_flow_dir ( ) ;
1398+ let epic = flowctl_core:: json_store:: epic_read ( & flow_dir, epic_id) . ok ( ) ?;
14581399 epic. branch_name . filter ( |b| !b. is_empty ( ) )
14591400}
0 commit comments