Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
dc57ec3
fix(record): route generated-file modifies and multi-line deletions t…
geekgonecrazy Oct 13, 2026
ac2a8af
fix(globalize): fail closed on line/vertex divergence in insert ancho…
geekgonecrazy Oct 13, 2026
3d6db78
fix(globalize): escalate line/vertex divergence to the sentinel whole…
geekgonecrazy Oct 13, 2026
a608047
fix(import): restore the linearized first-parent walk for plain imports
geekgonecrazy Oct 14, 2026
f1a3987
fix(record): sentinel-route records whose semantic branch count diver…
geekgonecrazy Oct 14, 2026
ecb6751
fix(record): count lines, not newlines, for the modify's old-line mod…
geekgonecrazy Oct 14, 2026
fdc3c30
fix(record): sentinel-route EOF-boundary edits on files without a tra…
geekgonecrazy Oct 14, 2026
202290d
chore(debug): env-gated first-diff probe for staged path verification…
geekgonecrazy Oct 14, 2026
bbf67c5
fix(globalize): escalate insert vertex/line divergence to whole-file …
geekgonecrazy Oct 14, 2026
3aebf1b
fix(import): sentinel-route modifies when semantic branches diverge f…
geekgonecrazy Oct 14, 2026
4ddfd7f
fix(agent): honor bridge opt-in for native hook and view operations
geekgonecrazy Oct 14, 2026
6138000
fix(git): preserve file identity and byte-exact imports
vinceblock99 Oct 15, 2026
73b5646
fix(import): retain sibling ancestry during complete-closure imports
geekgonecrazy Oct 15, 2026
d0ced4e
fix(ci): preserve raw Git tree paths across platforms and exercise me…
geekgonecrazy Oct 15, 2026
0b36794
fix(import): release borrowed tree entry before advancing raw-path tr…
geekgonecrazy Oct 15, 2026
2b2b4ca
fix(git): preserve closure and file identity during full-history impo…
vinceblock99 Oct 15, 2026
284ebde
fix(git): resolve full-history import CI failures (#262)
vinceblock99 Oct 15, 2026
255e5ff
test(agent): make WIP preservation failure deterministic across Unix …
geekgonecrazy Oct 15, 2026
6f48839
fix(diff): render textual deltas for whole-file replacements (#263)
vinceblock99 Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions atomic-cli/src/commands/agent/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,30 @@ impl Command for Hooks {
}
}
}
None => find_repository_root().map_err(|e| {
// Log to stderr — the agent should continue even if Atomic can't find a repo
eprintln!("[atomic] Warning: {}", e);
e
})?,
None => match find_repository_root() {
Ok(root) => root,
Err(error @ CliError::RepositoryNotFound { .. }) => {
// Same state as the hints path: the hook ran outside an
// Atomic repository (or the repository disappeared since
// the caller's probe). Nothing to record — exit quietly
// instead of failing the hook (and, through the agent
// bridge plugins' strict tool propagation, the tool call).
log::warn!(
"hooks: no Atomic repository for {} {}: {}",
self.agent_name,
self.verb,
error
);
if let Some(response) = agent.stdout_response(hook_type) {
println!("{}", response);
}
return Ok(());
}
// Only the genuine not-an-Atomic-repository state is quiet.
// Any other discovery failure (I/O and friends) stays fatal:
// swallowing it would hide real breakage behind exit 0.
Err(error) => return Err(error),
},
};

// Hooks are never suppressed under a managed run: sessions adopt its
Expand Down
25 changes: 25 additions & 0 deletions atomic-cli/src/commands/diff/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,31 @@ where
_ => FileDiff::modified(file_path),
};

// A whole-file rewrite's tombstones carry identities, not old text.
// Render its textual delta using recorded before-content, even when
// context is disabled or only statistics were requested.
if let Some((before_content, after_content)) =
atomic_repository::diff_engine::whole_file_replacement_text(
ops,
&change.contents,
|| before(file_path),
)
{
let result = diff_text(&before_content, &after_content, Algorithm::Myers);
let old_lines: Vec<_> = before_content.split(|&b| b == b'\n').collect();
let new_lines: Vec<_> = after_content.split(|&b| b == b'\n').collect();
for mut hunk in
build_hunks_from_diff(&result, &old_lines, &new_lines, config.context_lines)
{
hunk.lines = Diff::repair_diff_lines(hunk.lines);
file_diff.add_hunk(hunk);
}
file_diff.compute_stats();
stats.add_file(file_diff.stats.clone());
file_diffs.push(file_diff);
continue;
}

// Pass 1: flatten line operations into numbered changed lines.
let mut insertions = 0usize;
let mut deletions = 0usize;
Expand Down
41 changes: 38 additions & 3 deletions atomic-cli/src/commands/git/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,8 @@ impl Command for Import {
let branches = self.get_all_branches(&git_repo)?;

let mut total_imported = 0;
let checked_out_branch = current_git_branch(&git_repo);
let mut checked_out_verification = None;
for branch_name in branches {
let preserve_branch_working_copy =
preserve_current_view && original_view != branch_name;
Expand All @@ -882,6 +884,13 @@ impl Command for Import {
"missing prevalidated import plan for '{branch_name}'"
))
})?;
if checked_out_branch.as_deref() == Some(branch_name.as_str()) {
if let (Some(expected), Some(raw)) =
(plan.expected_git_tree(), plan.raw_git_tree())
{
checked_out_verification = Some((branch_name.clone(), expected, raw));
}
}

// Ensure the view exists
if !repo
Expand Down Expand Up @@ -948,8 +957,17 @@ impl Command for Import {
print_info(
"Working copy materialization deferred to the explicit command boundary (metadata-only reactive budget).",
);
} else if let Some(checked_out) = current_git_branch(&git_repo) {
// Importing every branch must finish on the checked-out branch,
// not whichever ref happened to sort last. Its complete target
// tree has passed the same preflight and publication checks as
// single-branch import, so adopt Git's existing materialization.
repo.align_to_view(working_copy, &checked_out)
.map_err(|e| CliError::Internal(e.into()))?;
print_info("Using Git working copy as imported materialization.");
reindex_working_copy(&repo, working_copy);
} else {
// Materialize the working copy from the graph
// Detached imports have no checked-out branch to adopt.
print_info("Materializing working copy...");
match repo.materialize(working_copy) {
Ok(result) => {
Expand Down Expand Up @@ -986,6 +1004,22 @@ impl Command for Import {
"Imported {} total changes across all branches",
total_imported
));
if !self.skip_checkpoint_refresh {
if let Some((view, expected, raw)) = checked_out_verification {
// Use the same canonical tree proof as single-branch import.
// The generic native-status fallback interprets escaped Git
// names as disk names and is not an import equivalence proof.
super::bridge::refresh_checkpoint_after_verified_import(
&repo,
working_copy,
&git_repo,
&view,
&expected,
&raw,
)?;
checkpoint_refreshed = true;
}
}
} else {
// Import single branch
let branch_name = self.branch.clone().unwrap_or(default_branch);
Expand Down Expand Up @@ -1250,15 +1284,16 @@ fn reindex_working_copy(repo: &Repository, working_copy: WorkingCopyId) {
let mut entries: Vec<(String, i64, u32, u64, Hash)> = Vec::new();

for file in &tracked {
let abs = repo_root.join(&file.path);
let path = file.path.to_string_lossy();
let abs = repo_root.join(super::parallel::canonical_tree_path(&path));
if let Ok(metadata) = std::fs::metadata(&abs) {
let mtime = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
let duration = mtime
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default();
if let Ok(bytes) = std::fs::read(&abs) {
entries.push((
file.path.to_string_lossy().replace('\\', "/"),
path.into_owned(),
duration.as_secs() as i64,
duration.subsec_nanos(),
metadata.len(),
Expand Down
Loading
Loading