Skip to content
This repository was archived by the owner on Apr 11, 2026. It is now read-only.

Commit ef46402

Browse files
z23ccclaude
andcommitted
feat(cli): migrate epic helpers + close/gap to json_store
Replace load_epic(), save_epic(), find_max_epic_number() with json_store. Migrate cmd_close() task listing and gap checking to json_store. Add bridge require_db() for remaining functions not yet migrated. Parity tests expect DB data — will be fixed in task .11. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f0a5eae commit ef46402

1 file changed

Lines changed: 28 additions & 34 deletions

File tree

  • flowctl/crates/flowctl-cli/src/commands

flowctl/crates/flowctl-cli/src/commands/epic.rs

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -167,40 +167,37 @@ fn validate_epic_id(id: &str) {
167167
}
168168
}
169169

170-
/// Load epic document from DB (sole source of truth).
170+
/// Load epic document from JSON files.
171171
fn load_epic(id: &str) -> Document<Epic> {
172-
let conn = require_db()
173-
.unwrap_or_else(|e| error_exit(&format!("DB required: {e}")));
174-
let repo = crate::commands::db_shim::EpicRepo::new(&conn);
175-
match repo.get_with_body(id) {
176-
Ok((epic, body)) => Document { frontmatter: epic, body },
177-
Err(_) => error_exit(&format!("Epic {id} not found")),
178-
}
172+
let flow_dir = get_flow_dir();
173+
let epic = flowctl_core::json_store::epic_read(&flow_dir, id)
174+
.unwrap_or_else(|_| error_exit(&format!("Epic {id} not found")));
175+
let body = flowctl_core::json_store::epic_spec_read(&flow_dir, id)
176+
.unwrap_or_default();
177+
Document { frontmatter: epic, body }
179178
}
180179

181-
/// Write an epic document to DB (sole source of truth, no MD export).
180+
/// Write an epic document to JSON files.
182181
fn save_epic(doc: &Document<Epic>) {
183-
let conn = require_db()
184-
.unwrap_or_else(|e| error_exit(&format!("DB required: {e}")));
185-
let repo = crate::commands::db_shim::EpicRepo::new(&conn);
186-
if let Err(e) = repo.upsert_with_body(&doc.frontmatter, &doc.body) {
187-
error_exit(&format!("DB write failed for {}: {e}", doc.frontmatter.id));
182+
let flow_dir = get_flow_dir();
183+
if let Err(e) = flowctl_core::json_store::epic_write(&flow_dir, &doc.frontmatter) {
184+
error_exit(&format!("Failed to write epic {}: {e}", doc.frontmatter.id));
185+
}
186+
if let Err(e) = flowctl_core::json_store::epic_spec_write(&flow_dir, &doc.frontmatter.id, &doc.body) {
187+
error_exit(&format!("Failed to write epic spec {}: {e}", doc.frontmatter.id));
188188
}
189189
}
190190

191-
/// Open DB connection (hard error if unavailable).
192-
fn require_db() -> Result<crate::commands::db_shim::Connection, crate::commands::db_shim::DbError> {
193-
crate::commands::db_shim::require_db()
191+
/// Get max epic number from JSON files.
192+
fn find_max_epic_number() -> u32 {
193+
let flow_dir = get_flow_dir();
194+
flowctl_core::json_store::epic_max_num(&flow_dir).unwrap_or(0)
194195
}
195196

196-
/// Get max epic number from DB. Returns 0 if none exist.
197-
fn find_max_epic_number() -> u32 {
198-
let conn = require_db()
199-
.unwrap_or_else(|e| error_exit(&format!("DB required: {e}")));
200-
match crate::commands::db_shim::max_epic_num(&conn) {
201-
Ok(n) => n as u32,
202-
Err(_) => 0,
203-
}
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()
204201
}
205202

206203
/// Create default epic spec Markdown body.
@@ -610,11 +607,9 @@ fn cmd_close(id: &str, skip_gap_check: bool, json_mode: bool) {
610607

611608
let mut doc = load_epic(id);
612609

613-
// Check all tasks are done/skipped via DB
614-
let conn = require_db()
615-
.unwrap_or_else(|e| error_exit(&format!("DB required: {e}")));
616-
let task_repo = crate::commands::db_shim::TaskRepo::new(&conn);
617-
let tasks = task_repo.list_by_epic(id).unwrap_or_default();
610+
// Check all tasks are done/skipped via JSON files
611+
let flow_dir = get_flow_dir();
612+
let tasks = flowctl_core::json_store::task_list_by_epic(&flow_dir, id).unwrap_or_default();
618613

619614
let incomplete: Vec<String> = tasks
620615
.iter()
@@ -632,14 +627,13 @@ fn cmd_close(id: &str, skip_gap_check: bool, json_mode: bool) {
632627
));
633628
}
634629

635-
// Gap registry gate — check DB gaps table
636-
let gap_repo = crate::commands::db_shim::GapRepo::new(&conn);
630+
// Gap registry gate — check JSON gaps
637631
let mut open_blocking_count = 0;
638632
let mut gap_list_parts: Vec<String> = Vec::new();
639633

640-
if let Ok(gaps) = gap_repo.list(id, Some("open")) {
634+
if let Ok(gaps) = flowctl_core::json_store::gaps_read(&flow_dir, id) {
641635
for gap in &gaps {
642-
if GAP_BLOCKING_PRIORITIES.contains(&gap.priority.as_str()) {
636+
if !gap.resolved && GAP_BLOCKING_PRIORITIES.contains(&gap.priority.as_str()) {
643637
open_blocking_count += 1;
644638
gap_list_parts.push(format!("[{}] {}", gap.priority, gap.capability));
645639
}

0 commit comments

Comments
 (0)