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

Commit a079843

Browse files
z23ccclaude
andcommitted
fix: remove fallback — three layers are canonical, no backward compat
- paths.rs: getters return direct paths (no if-exists-fallback) config_json → .flow-config/config.json (always) frecency → ~/.flow/projects/{slug}/frecency.json (always) memory_dir → ~/.flow/projects/{slug}/memory/ (always) - init.rs: migration uses MOVE (rename), not copy Old files removed after migration, not left behind - guard.rs: uses load_resolved() only, no .flow/ fallback - Tests: 3 fallback tests replaced with direct-path tests 381 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 033ff66 commit a079843

4 files changed

Lines changed: 43 additions & 69 deletions

File tree

bin/flowctl

-16 Bytes
Binary file not shown.

flowctl/crates/flowctl-cli/src/commands/admin/guard.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,7 @@ pub fn cmd_guard(json_mode: bool, layer: String) {
352352
};
353353

354354
// ── Priority chain: project-context → stack config → auto-detection ──
355-
// Try three-layer resolution first (.flow-config/ then .flow/), fall back to direct load.
356-
let pc = ProjectContext::load_resolved()
357-
.or_else(|| ProjectContext::load(&flow_dir));
355+
let pc = ProjectContext::load_resolved();
358356
let pc_commands = pc.as_ref().map(|ctx| {
359357
let gc = &ctx.guard_commands;
360358
let mut cmds: Vec<(String, String, String)> = Vec::new();

flowctl/crates/flowctl-cli/src/commands/admin/init.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,32 +111,32 @@ pub fn cmd_init(json: bool) {
111111
}
112112
}
113113

114-
// ── Migrate existing files to new locations (copy, not move) ───────
114+
// ── Migrate existing files to new locations (MOVE, not copy) ────────
115115

116116
// config.json → .flow-config/config.json
117117
let old_config = flow_dir.join("config.json");
118118
let new_config = config_dir.join("config.json");
119119
if old_config.exists() && !new_config.exists() {
120-
if fs::copy(&old_config, &new_config).is_ok() {
121-
actions.push("migrated config.json → .flow-config/".to_string());
120+
if fs::rename(&old_config, &new_config).is_ok() {
121+
actions.push("moved config.json → .flow-config/".to_string());
122122
}
123123
}
124124

125125
// project-context.md → .flow-config/project-context.md
126126
let old_pc = flow_dir.join("project-context.md");
127127
let new_pc = config_dir.join("project-context.md");
128128
if old_pc.exists() && !new_pc.exists() {
129-
if fs::copy(&old_pc, &new_pc).is_ok() {
130-
actions.push("migrated project-context.md → .flow-config/".to_string());
129+
if fs::rename(&old_pc, &new_pc).is_ok() {
130+
actions.push("moved project-context.md → .flow-config/".to_string());
131131
}
132132
}
133133

134134
// invariants.md → .flow-config/invariants.md
135135
let old_inv = flow_dir.join("invariants.md");
136136
let new_inv = config_dir.join("invariants.md");
137137
if old_inv.exists() && !new_inv.exists() {
138-
if fs::copy(&old_inv, &new_inv).is_ok() {
139-
actions.push("migrated invariants.md → .flow-config/".to_string());
138+
if fs::rename(&old_inv, &new_inv).is_ok() {
139+
actions.push("moved invariants.md → .flow-config/".to_string());
140140
}
141141
}
142142

@@ -145,9 +145,13 @@ pub fn cmd_init(json: bool) {
145145
let old_frec = flow_dir.join("frecency.json");
146146
let new_frec = paths.global_project_dir.join("frecency.json");
147147
if old_frec.exists() && !new_frec.exists() {
148-
if fs::copy(&old_frec, &new_frec).is_ok() {
149-
actions.push("migrated frecency.json → global".to_string());
148+
// rename may fail across filesystems, fall back to copy+delete
149+
if fs::rename(&old_frec, &new_frec).is_err() {
150+
if fs::copy(&old_frec, &new_frec).is_ok() {
151+
fs::remove_file(&old_frec).ok();
152+
}
150153
}
154+
actions.push("moved frecency.json → global".to_string());
151155
}
152156

153157
let old_mem = flow_dir.join("memory");
@@ -158,24 +162,24 @@ pub fn cmd_init(json: bool) {
158162
let dest = new_mem.join(entry.file_name());
159163
fs::copy(entry.path(), &dest).ok();
160164
}
161-
actions.push("migrated memory/ → global".to_string());
165+
fs::remove_dir_all(&old_mem).ok();
166+
actions.push("moved memory/ → global".to_string());
162167
}
163168
}
164169
}
165170

166171
// Create project-context.md with auto-detected stack info if missing
167172
// Write to .flow-config/ (primary location) instead of .flow/
168173
let project_context_path = config_dir.join("project-context.md");
169-
let fallback_context_path = flow_dir.join("project-context.md");
170-
if !project_context_path.exists() && !fallback_context_path.exists() {
174+
if !project_context_path.exists() {
171175
let content = generate_project_context(&cwd);
172176
if let Err(e) = fs::write(&project_context_path, content) {
173177
eprintln!("warning: failed to create project-context.md: {e}");
174178
} else {
175179
actions.push("created project-context.md (auto-detected stack)".to_string());
176180
}
177181
}
178-
let has_project_context = project_context_path.exists() || fallback_context_path.exists();
182+
let has_project_context = project_context_path.exists();
179183

180184
// Build output
181185
let message = if actions.is_empty() {

flowctl/crates/flowctl-core/src/paths.rs

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -54,49 +54,29 @@ impl FlowPaths {
5454

5555
// -- Convenience path getters ----------------------------------------
5656

57-
/// config.json: .flow-config/config.json (primary) -> .flow/config.json (fallback).
57+
/// config.json lives in .flow-config/
5858
pub fn config_json(&self) -> PathBuf {
59-
let primary = self.config_dir.join("config.json");
60-
if primary.exists() {
61-
return primary;
62-
}
63-
self.runtime_dir.join("config.json") // backward compat
59+
self.config_dir.join("config.json")
6460
}
6561

66-
/// project-context.md: .flow-config/ (primary) -> .flow/ (fallback).
62+
/// project-context.md lives in .flow-config/
6763
pub fn project_context(&self) -> PathBuf {
68-
let primary = self.config_dir.join("project-context.md");
69-
if primary.exists() {
70-
return primary;
71-
}
72-
self.runtime_dir.join("project-context.md")
64+
self.config_dir.join("project-context.md")
7365
}
7466

75-
/// invariants.md: .flow-config/ (primary) -> .flow/ (fallback).
67+
/// invariants.md lives in .flow-config/
7668
pub fn invariants(&self) -> PathBuf {
77-
let primary = self.config_dir.join("invariants.md");
78-
if primary.exists() {
79-
return primary;
80-
}
81-
self.runtime_dir.join("invariants.md")
69+
self.config_dir.join("invariants.md")
8270
}
8371

84-
/// frecency.json: ~/.flow/projects/{slug}/ (primary) -> .flow/ (fallback).
72+
/// frecency.json lives in ~/.flow/projects/{slug}/
8573
pub fn frecency(&self) -> PathBuf {
86-
let primary = self.global_project_dir.join("frecency.json");
87-
if primary.exists() {
88-
return primary;
89-
}
90-
self.runtime_dir.join("frecency.json")
74+
self.global_project_dir.join("frecency.json")
9175
}
9276

93-
/// memory directory: ~/.flow/projects/{slug}/memory/ (primary) -> .flow/memory/ (fallback).
77+
/// memory directory lives in ~/.flow/projects/{slug}/memory/
9478
pub fn memory_dir(&self) -> PathBuf {
95-
let primary = self.global_project_dir.join("memory");
96-
if primary.exists() {
97-
return primary;
98-
}
99-
self.runtime_dir.join("memory")
79+
self.global_project_dir.join("memory")
10080
}
10181
}
10282

@@ -269,44 +249,37 @@ mod tests {
269249
}
270250

271251
#[test]
272-
fn config_json_fallback_when_no_primary() {
252+
fn config_json_always_in_config_dir() {
273253
let tmp = tempfile::tempdir().unwrap();
274-
let runtime = tmp.path().join(".flow");
275254
let config = tmp.path().join(".flow-config");
276-
std::fs::create_dir_all(&runtime).unwrap();
277-
// Do NOT create .flow-config/config.json
255+
std::fs::create_dir_all(&config).unwrap();
278256

279257
let paths = FlowPaths {
280-
runtime_dir: runtime.clone(),
281-
config_dir: config,
258+
runtime_dir: tmp.path().join(".flow"),
259+
config_dir: config.clone(),
282260
global_project_dir: tmp.path().join("global"),
283261
project_root: tmp.path().to_path_buf(),
284262
slug: "test".to_string(),
285263
};
286264

287-
assert_eq!(paths.config_json(), runtime.join("config.json"));
265+
assert_eq!(paths.config_json(), config.join("config.json"));
288266
}
289267

290268
#[test]
291-
fn project_context_fallback() {
269+
fn project_context_always_in_config_dir() {
292270
let tmp = tempfile::tempdir().unwrap();
293-
let runtime = tmp.path().join(".flow");
294271
let config = tmp.path().join(".flow-config");
295-
std::fs::create_dir_all(&runtime).unwrap();
272+
std::fs::create_dir_all(&config).unwrap();
296273

297274
let paths = FlowPaths {
298-
runtime_dir: runtime.clone(),
299-
config_dir: config,
275+
runtime_dir: tmp.path().join(".flow"),
276+
config_dir: config.clone(),
300277
global_project_dir: tmp.path().join("global"),
301278
project_root: tmp.path().to_path_buf(),
302279
slug: "test".to_string(),
303280
};
304281

305-
// No .flow-config/project-context.md -> falls back to .flow/
306-
assert_eq!(
307-
paths.project_context(),
308-
runtime.join("project-context.md")
309-
);
282+
assert_eq!(paths.project_context(), config.join("project-context.md"));
310283
}
311284

312285
#[test]
@@ -329,20 +302,19 @@ mod tests {
329302
}
330303

331304
#[test]
332-
fn frecency_fallback() {
305+
fn frecency_always_in_global_dir() {
333306
let tmp = tempfile::tempdir().unwrap();
334-
let runtime = tmp.path().join(".flow");
335-
std::fs::create_dir_all(&runtime).unwrap();
307+
let global = tmp.path().join("global");
336308

337309
let paths = FlowPaths {
338-
runtime_dir: runtime.clone(),
310+
runtime_dir: tmp.path().join(".flow"),
339311
config_dir: tmp.path().join(".flow-config"),
340-
global_project_dir: tmp.path().join("nonexistent-global"),
312+
global_project_dir: global.clone(),
341313
project_root: tmp.path().to_path_buf(),
342314
slug: "test".to_string(),
343315
};
344316

345-
assert_eq!(paths.frecency(), runtime.join("frecency.json"));
317+
assert_eq!(paths.frecency(), global.join("frecency.json"));
346318
}
347319

348320
#[test]

0 commit comments

Comments
 (0)