Problem
fbuild-paths declares itself the single source of truth for all .fbuild paths (crates/fbuild-paths/src/lib.rs:3), but the .fbuild / .fbuild/build path segments are hardcoded as raw string literals in 69 files across the workspace — including production code:
fbuild-cli/src/cli/{ide,debug,symbols_cmd,ide_debug,clang_tools,args,dispatch,lib_select}.rs
fbuild-build-engine/src/{linker.rs,build_info.rs,symbol_analyzer,framework_libs.rs}
fbuild-daemon/src/{handlers/libraries.rs,handlers/emulator,models.rs}
fbuild-deploy/src/{lpc.rs,wlink.rs,wchisp.rs,probe_rs.rs,lpc_debugger_reflash.rs}
fbuild-core/src/{response_file.rs,path.rs,lib.rs}
- plus ~25 test files hand-rolling fixture layouts like
dir.join(".fbuild/build/uno/release/...")
Raw literals drift from the real layout rules (env-segment auto-collapse, FBUILD_BUILD_DIR override, PIO .build/pio layouts — see agents/docs/path-conventions.md) and silently break compile_cwd_from_output / BuildLayout consumers when the layout evolves.
Proposal
- New dylint lint
dylints/ban_raw_fbuild_path (pattern after the existing dylints/ban_raw_subprocess crate): flag string literals containing .fbuild outside crates/fbuild-paths/src/** (the sanctioned definition site).
- Export canonical primitives from
fbuild-paths: FBUILD_DIR_NAME / BUILD_DIR_NAME consts plus the existing project_fbuild_dir() / BuildLayout::resolve() so call sites have a mechanical replacement.
- Ratchet: land the lint with a baseline allowlist of the current 69 files; each file must be sanitized (route through
BuildLayout / the new consts) and removed from the allowlist; the allowlist may only shrink. CI fails on any new raw-literal site.
- Test fixtures should build layouts via
fbuild_paths::BuildLayout::new(project, env, profile).resolve() instead of hand-rolled join(".fbuild/build/...") chains.
Acceptance
Problem
fbuild-pathsdeclares itself the single source of truth for all.fbuildpaths (crates/fbuild-paths/src/lib.rs:3), but the.fbuild/.fbuild/buildpath segments are hardcoded as raw string literals in 69 files across the workspace — including production code:fbuild-cli/src/cli/{ide,debug,symbols_cmd,ide_debug,clang_tools,args,dispatch,lib_select}.rsfbuild-build-engine/src/{linker.rs,build_info.rs,symbol_analyzer,framework_libs.rs}fbuild-daemon/src/{handlers/libraries.rs,handlers/emulator,models.rs}fbuild-deploy/src/{lpc.rs,wlink.rs,wchisp.rs,probe_rs.rs,lpc_debugger_reflash.rs}fbuild-core/src/{response_file.rs,path.rs,lib.rs}dir.join(".fbuild/build/uno/release/...")Raw literals drift from the real layout rules (env-segment auto-collapse,
FBUILD_BUILD_DIRoverride, PIO.build/piolayouts — seeagents/docs/path-conventions.md) and silently breakcompile_cwd_from_output/BuildLayoutconsumers when the layout evolves.Proposal
dylints/ban_raw_fbuild_path(pattern after the existingdylints/ban_raw_subprocesscrate): flag string literals containing.fbuildoutsidecrates/fbuild-paths/src/**(the sanctioned definition site).fbuild-paths:FBUILD_DIR_NAME/BUILD_DIR_NAMEconsts plus the existingproject_fbuild_dir()/BuildLayout::resolve()so call sites have a mechanical replacement.BuildLayout/ the new consts) and removed from the allowlist; the allowlist may only shrink. CI fails on any new raw-literal site.fbuild_paths::BuildLayout::new(project, env, profile).resolve()instead of hand-rolledjoin(".fbuild/build/...")chains.Acceptance
dylints/ban_raw_fbuild_pathlint crate exists, wired into the dylint CI gate alongsideban_raw_subprocessfbuild-pathsdefinition sites remain)