feat: fix engine (Phase 1.6) + git-aware mv + windows-safe path output

- Edit engine: generic span edits (replace/insert/delete) with stale-index
  guard; mv and fix share one atomic apply/rollback/diff/--json pipeline
- jmove mv|check|fix commands; java rules: unused-import, missing-import
  (unique-FQN insert, ambiguous -> candidates for agents), import-order
  (google style; cross-rule byte conflicts prune by severity and converge)
- mv of tracked files goes through git mv (staged rename, --no-git opt-out,
  moved_via/would_move_via in --json, GIT_ERROR code)
- fix java extract_package losing single-segment packages (bare identifier,
  not scoped_identifier) — broke the FQN index for 'package p;' projects
- core::rel_str: project-relative paths rendered with '/' on every platform
  (human/JSON/diff headers/git pathspecs), Path::display() no longer used
  for output; e2e fixtures: java/fix, java/fix_missing, java/order
This commit is contained in:
loki5512344 2026-09-15 16:10:04 +02:00
parent 0216fd6523
commit 9dda5f4562
Signed by: boba
GPG key ID: 253067914055423B
37 changed files with 2521 additions and 234 deletions

View file

@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
use crate::core::index::Index;
use crate::core::plan::Rewrite;
use crate::core::{JmoveError, JmoveResult};
use crate::core::{JmoveError, JmoveResult, rel_str};
/// Rewrite set for moving `source.java` to `target.java` (both
/// project-relative, validated by the caller).
@ -28,7 +28,7 @@ pub(super) fn java_rewrites(
if target.extension().is_none_or(|e| e != "java") {
return Err(rejected(format!(
"'{}' is a .java file, the target must keep the .java extension",
source.display()
rel_str(source)
)));
}
let sdir = source.parent().unwrap_or(Path::new(""));
@ -40,7 +40,7 @@ pub(super) fn java_rewrites(
} else {
Err(rejected(format!(
"'{}' has no `package` declaration (default package); it can only be renamed inside its directory",
source.display()
rel_str(source)
)))
};
};
@ -50,15 +50,15 @@ pub(super) fn java_rewrites(
if !sdir.ends_with(Path::new(&pkg_path)) {
return Err(rejected(format!(
"package '{pkg}' does not match directory '{}'",
sdir.display()
rel_str(sdir)
)));
}
let src_root = strip_package_dir(sdir, pkg);
let rest = tdir.strip_prefix(&src_root).map_err(|_| {
rejected(format!(
"target directory '{}' is outside the Java source root '{}'",
tdir.display(),
src_root.display()
rel_str(tdir),
rel_str(&src_root)
))
})?;
let new_pkg = package_of(rest);
@ -124,7 +124,7 @@ fn file_stem(path: &Path) -> JmoveResult<String> {
.and_then(|s| s.to_str())
.map(str::to_owned)
.ok_or_else(|| {
JmoveError::InvalidArgument(format!("invalid file name '{}'", path.display()))
JmoveError::InvalidArgument(format!("invalid file name '{}'", rel_str(path)))
})
}