diff --git a/crates/pi-ast/src/block.rs b/crates/pi-ast/src/block.rs index fd551d9a6..792f1d5f9 100644 --- a/crates/pi-ast/src/block.rs +++ b/crates/pi-ast/src/block.rs @@ -298,6 +298,19 @@ mod tests { assert_eq!(resolve(TS_EXAMPLE, "x.unknownext", 2), None); } + #[test] + fn resolves_zsh_if_block_in_extensionless_rc_file() { + // Regression: an extensionless shell rc file (`zshrc`/`.zshrc`) must + // infer the bash grammar so `replace block` / `insert after block` + // works. Previously `Path::extension` returned `None`, leaving block + // ops permanently unresolvable on these files. + let code = "ZSH_COMPDUMP=x\nif [[ -f \"$ZSH_COMPDUMP\" ]]; then\n compinit -C\nelse\n compinit\nfi\n"; + let span = Some(BlockRange { start_line: 2, end_line: 6 }); + assert_eq!(resolve(code, "modules/zsh/zshrc", 2), span); + assert_eq!(resolve(code, ".zshrc", 2), span); + assert_eq!(resolve(code, "/home/u/.bashrc", 2), span); + } + #[test] fn resolves_top_level_python_def() { let code = "x = 1\ndef greet():\n return 1\n"; diff --git a/crates/pi-ast/src/language/mod.rs b/crates/pi-ast/src/language/mod.rs index a7b93768b..506e22682 100644 --- a/crates/pi-ast/src/language/mod.rs +++ b/crates/pi-ast/src/language/mod.rs @@ -627,6 +627,31 @@ fn from_extension(path: &Path) -> Option { return Some(SupportLang::Dockerfile); } + // Extensionless shell rc/profile files. `Path::extension` returns `None` + // for both bare (`zshrc`) and dotfile (`.zshrc`) forms, so they would + // otherwise resolve to no language and disable block-aware ops on them. + let stem = name.strip_prefix('.').unwrap_or(name); + if matches!( + stem, + "zshrc" + | "zshenv" + | "zprofile" + | "zlogin" + | "zlogout" + | "zsh_aliases" + | "bashrc" + | "bash_profile" + | "bash_login" + | "bash_logout" + | "bash_aliases" + | "profile" + | "kshrc" + | "mkshrc" + | "shrc" + ) { + return Some(SupportLang::Bash); + } + let ext = path.extension()?.to_str()?; SupportLang::all_langs() .iter() diff --git a/packages/natives/CHANGELOG.md b/packages/natives/CHANGELOG.md index 8e06fc00a..9fbbdb623 100644 --- a/packages/natives/CHANGELOG.md +++ b/packages/natives/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `blockRangeAt` (and thus the edit tool's `replace block` / `insert after block` ops) failing on extensionless shell rc/profile files. `Path::extension` returns `None` for both bare (`zshrc`) and dotfile (`.zshrc`, `.bashrc`) forms, so language inference fell through to "unrecognized" and block resolution was permanently unresolvable on those files — an agent retrying the block op would loop on the same error. Known shell rc/profile basenames (`zshrc`/`zshenv`/`zprofile`/`zlogin`/`zlogout`/`bashrc`/`bash_profile`/`bash_login`/`bash_logout`/`bash_aliases`/`profile`/`kshrc`/`mkshrc`/`shrc`, with or without a leading dot) now resolve to the bash grammar. + ## [15.11.0] - 2026-06-10 ### Breaking Changes