feat(core): Improve stability, logging, and LLM context handling
This commit is contained in:
@@ -84,6 +84,7 @@ Download the latest release for your platform:
|
|||||||
- Commit history follows local branches, so **Jump Here** can navigate backward and forward while `HEAD` is detached. Jumping to a commit that is the unique tip of a local branch reattaches that branch.
|
- Commit history follows local branches, so **Jump Here** can navigate backward and forward while `HEAD` is detached. Jumping to a commit that is the unique tip of a local branch reattaches that branch.
|
||||||
- Starting monitoring reconciles Git changes already present in the working tree, including changes made while monitoring was paused, and commits only those reported paths through the normal guarded monitoring pipeline.
|
- Starting monitoring reconciles Git changes already present in the working tree, including changes made while monitoring was paused, and commits only those reported paths through the normal guarded monitoring pipeline.
|
||||||
- Adding any folder immediately enables monitoring in the UI. Existing Git repositories start their watcher at once; non-repositories stay armed and attach the watcher automatically when **Init Repo** completes, without another Play click.
|
- Adding any folder immediately enables monitoring in the UI. Existing Git repositories start their watcher at once; non-repositories stay armed and attach the watcher automatically when **Init Repo** completes, without another Play click.
|
||||||
|
- Automatic commit-message rewriting performs all Git safety checks before contacting Ollama, bounds diffs to the configured model context, requests an exact JSON schema, and stops after the first failed automatic attempt instead of retrying on each scheduler tick.
|
||||||
- Automatic stashing, forced checkout, rebase abort, and hard-reset recovery are not used by monitoring or commit-message rewriting.
|
- Automatic stashing, forced checkout, rebase abort, and hard-reset recovery are not used by monitoring or commit-message rewriting.
|
||||||
- Commit squashing is temporarily disabled until it uses the same transactional worktree mechanism.
|
- Commit squashing is temporarily disabled until it uses the same transactional worktree mechanism.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"productName": "Auto-Git",
|
"productName": "Auto-Git",
|
||||||
"name": "Auto-Git",
|
"name": "Auto-Git",
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"description": "Auto-Git: Git-Überwachung mit automatischer LLM-Commit-Message und README-Erstellung",
|
"description": "Auto-Git: Git-Überwachung mit automatischer LLM-Commit-Message und README-Erstellung",
|
||||||
"author": "Victor Giers",
|
"author": "Victor Giers",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
12
src-tauri/Cargo.lock
generated
12
src-tauri/Cargo.lock
generated
@@ -273,7 +273,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "auto-git"
|
name = "auto-git"
|
||||||
version = "1.0.0"
|
version = "1.0.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"auto-launch",
|
"auto-launch",
|
||||||
@@ -291,6 +291,7 @@ dependencies = [
|
|||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
"wait-timeout",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4798,6 +4799,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "auto-git"
|
name = "auto-git"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
description = "Auto-Git: Git monitoring with automatic LLM commit messages and README generation"
|
description = "Auto-Git: Git monitoring with automatic LLM commit messages and README generation"
|
||||||
authors = ["Victor Giers"]
|
authors = ["Victor Giers"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ const MAX_SQUASH_COMMIT_MESSAGE_CHARS: usize = 160;
|
|||||||
const OLLAMA_BASE_URL: &str = "http://127.0.0.1:11434";
|
const OLLAMA_BASE_URL: &str = "http://127.0.0.1:11434";
|
||||||
const ROSE_TITLEBAR_COLOR: Color = Color(255, 241, 242, 255);
|
const ROSE_TITLEBAR_COLOR: Color = Color(255, 241, 242, 255);
|
||||||
const GIT_PROCESS_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
const GIT_PROCESS_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||||
|
const MAX_COMMIT_MESSAGE_PROMPT_CHARS: usize = 32_000;
|
||||||
|
const MAX_COMMIT_DIFF_TOTAL_CHARS: usize = 18_000;
|
||||||
|
const OLLAMA_COMMIT_CONTEXT_TOKENS: u64 = 16_384;
|
||||||
// Automatic rewriting is safe to enable because all history mutation happens in an isolated
|
// Automatic rewriting is safe to enable because all history mutation happens in an isolated
|
||||||
// worktree and the real branch is updated only after tree validation and a compare-and-swap.
|
// worktree and the real branch is updated only after tree validation and a compare-and-swap.
|
||||||
const AUTO_HISTORY_REWRITE_ENABLED: bool = true;
|
const AUTO_HISTORY_REWRITE_ENABLED: bool = true;
|
||||||
@@ -150,7 +153,6 @@ const IGNORED_NAMES: &[&str] = &[
|
|||||||
"bin",
|
"bin",
|
||||||
"pkg",
|
"pkg",
|
||||||
"vendor",
|
"vendor",
|
||||||
"Cargo.lock",
|
|
||||||
"*.gem",
|
"*.gem",
|
||||||
".bundle",
|
".bundle",
|
||||||
"vendor/bundle",
|
"vendor/bundle",
|
||||||
@@ -217,7 +219,6 @@ const IGNORED_NAMES: &[&str] = &[
|
|||||||
"DerivedDataCache",
|
"DerivedDataCache",
|
||||||
"Intermediate",
|
"Intermediate",
|
||||||
"Saved",
|
"Saved",
|
||||||
"*.lock",
|
|
||||||
"*.7z",
|
"*.7z",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -531,10 +532,9 @@ fn normalize_store(store: &mut StoreData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for folder in &mut store.folders {
|
for folder in &mut store.folders {
|
||||||
let repo_exists = Path::new(&folder.path).join(".git").exists();
|
|
||||||
let path_exists = Path::new(&folder.path).exists();
|
let path_exists = Path::new(&folder.path).exists();
|
||||||
folder.needs_relocation = !path_exists;
|
folder.needs_relocation = !path_exists;
|
||||||
if !repo_exists || folder.needs_relocation {
|
if folder.needs_relocation {
|
||||||
folder.monitoring = false;
|
folder.monitoring = false;
|
||||||
}
|
}
|
||||||
// A persisted flag can only describe a process from a previous app lifetime. Never resume
|
// A persisted flag can only describe a process from a previous app lifetime. Never resume
|
||||||
@@ -967,16 +967,46 @@ fn stream_ollama(
|
|||||||
temperature: f64,
|
temperature: f64,
|
||||||
app: &AppHandle,
|
app: &AppHandle,
|
||||||
) -> CommandResult<String> {
|
) -> CommandResult<String> {
|
||||||
stream_ollama_request(prompt, model, temperature, app, false)
|
stream_ollama_request(prompt, model, temperature, app, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream_ollama_json(
|
fn stream_ollama_json(
|
||||||
prompt: &str,
|
prompt: &str,
|
||||||
model: &str,
|
model: &str,
|
||||||
temperature: f64,
|
temperature: f64,
|
||||||
|
hashes: &[String],
|
||||||
app: &AppHandle,
|
app: &AppHandle,
|
||||||
) -> CommandResult<String> {
|
) -> CommandResult<String> {
|
||||||
stream_ollama_request(prompt, model, temperature, app, true)
|
stream_ollama_request(
|
||||||
|
prompt,
|
||||||
|
model,
|
||||||
|
temperature,
|
||||||
|
app,
|
||||||
|
Some(commit_message_json_schema(hashes)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_message_json_schema(hashes: &[String]) -> Value {
|
||||||
|
let mut properties = serde_json::Map::new();
|
||||||
|
let mut required = Vec::new();
|
||||||
|
for hash in hashes {
|
||||||
|
let short = short_hash(hash);
|
||||||
|
properties.insert(
|
||||||
|
short.clone(),
|
||||||
|
json!({
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "A concise single-line commit message"
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
required.push(short);
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"type": "object",
|
||||||
|
"properties": properties,
|
||||||
|
"required": required,
|
||||||
|
"additionalProperties": false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream_ollama_request(
|
fn stream_ollama_request(
|
||||||
@@ -984,7 +1014,7 @@ fn stream_ollama_request(
|
|||||||
model: &str,
|
model: &str,
|
||||||
temperature: f64,
|
temperature: f64,
|
||||||
app: &AppHandle,
|
app: &AppHandle,
|
||||||
json_mode: bool,
|
response_format: Option<Value>,
|
||||||
) -> CommandResult<String> {
|
) -> CommandResult<String> {
|
||||||
ensure_ollama_running()?;
|
ensure_ollama_running()?;
|
||||||
emit(app, "cat-begin", ());
|
emit(app, "cat-begin", ());
|
||||||
@@ -997,10 +1027,14 @@ fn stream_ollama_request(
|
|||||||
"model": model,
|
"model": model,
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"stream": true,
|
"stream": true,
|
||||||
"options": { "temperature": temperature }
|
"options": {
|
||||||
|
"temperature": temperature,
|
||||||
|
"num_ctx": OLLAMA_COMMIT_CONTEXT_TOKENS,
|
||||||
|
"num_predict": 2048
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if json_mode {
|
if let Some(response_format) = response_format {
|
||||||
payload["format"] = json!("json");
|
payload["format"] = response_format;
|
||||||
}
|
}
|
||||||
let mut response = client
|
let mut response = client
|
||||||
.post(format!("{OLLAMA_BASE_URL}/api/generate"))
|
.post(format!("{OLLAMA_BASE_URL}/api/generate"))
|
||||||
@@ -1081,7 +1115,7 @@ fn generate_llm_message_for_commit(
|
|||||||
) -> CommandResult<String> {
|
) -> CommandResult<String> {
|
||||||
let hashes = vec![hash.to_string()];
|
let hashes = vec![hash.to_string()];
|
||||||
let prompt = generate_llm_commit_prompt(folder_path, &hashes)?;
|
let prompt = generate_llm_commit_prompt(folder_path, &hashes)?;
|
||||||
let llm_raw = stream_ollama_json(&prompt, model, 0.3, app)?;
|
let llm_raw = stream_ollama_json(&prompt, model, 0.3, &hashes, app)?;
|
||||||
let parsed = parse_llm_commit_messages(&llm_raw)?;
|
let parsed = parse_llm_commit_messages(&llm_raw)?;
|
||||||
let validated = validate_llm_commit_messages(parsed, &hashes)?;
|
let validated = validate_llm_commit_messages(parsed, &hashes)?;
|
||||||
validated.get(hash).cloned().ok_or_else(|| {
|
validated.get(hash).cloned().ok_or_else(|| {
|
||||||
@@ -1092,15 +1126,26 @@ fn generate_llm_message_for_commit(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn truncate_prompt_text(value: &str, max_chars: usize) -> String {
|
||||||
|
let Some((cutoff, _)) = value.char_indices().nth(max_chars) else {
|
||||||
|
return value.to_string();
|
||||||
|
};
|
||||||
|
format!("{}\n[diff truncated by Auto-Git]", &value[..cutoff])
|
||||||
|
}
|
||||||
|
|
||||||
fn get_commits_for_llm(folder_path: &str, hashes: &[String]) -> CommandResult<Vec<Value>> {
|
fn get_commits_for_llm(folder_path: &str, hashes: &[String]) -> CommandResult<Vec<Value>> {
|
||||||
let mut commits = Vec::new();
|
let mut commits = Vec::new();
|
||||||
|
let diff_budget = MAX_COMMIT_DIFF_TOTAL_CHARS / hashes.len().max(1);
|
||||||
for hash in hashes {
|
for hash in hashes {
|
||||||
let diff = run_git(folder_path, &["diff", &format!("{hash}^!")])?;
|
let diff = run_git(
|
||||||
|
folder_path,
|
||||||
|
&["show", "--format=", "--no-ext-diff", "--unified=2", hash],
|
||||||
|
)?;
|
||||||
let msg = run_git(folder_path, &["show", "-s", "--format=%B", hash])?;
|
let msg = run_git(folder_path, &["show", "-s", "--format=%B", hash])?;
|
||||||
commits.push(json!({
|
commits.push(json!({
|
||||||
"hash": short_hash(hash),
|
"hash": short_hash(hash),
|
||||||
"message": msg.trim(),
|
"message": msg.trim(),
|
||||||
"diff": diff
|
"diff": truncate_prompt_text(&diff, diff_budget)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Ok(commits)
|
Ok(commits)
|
||||||
@@ -1124,10 +1169,11 @@ COMMITS (as JSON):
|
|||||||
{}"#,
|
{}"#,
|
||||||
serde_json::to_string_pretty(&commits).map_err(|e| e.to_string())?
|
serde_json::to_string_pretty(&commits).map_err(|e| e.to_string())?
|
||||||
);
|
);
|
||||||
if prompt.len() > 200_000 {
|
if prompt.chars().count() > MAX_COMMIT_MESSAGE_PROMPT_CHARS {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"LLM prompt too large ({} chars) for {folder_path}",
|
"Commit-message prompt is still too large after bounded diff truncation ({} chars; maximum {}). No LLM request was sent for {folder_path}.",
|
||||||
prompt.len()
|
prompt.chars().count(),
|
||||||
|
MAX_COMMIT_MESSAGE_PROMPT_CHARS
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(prompt)
|
Ok(prompt)
|
||||||
@@ -1375,7 +1421,7 @@ fn set_executable(_path: &Path) -> CommandResult<()> {
|
|||||||
fn run_llm_commit_rewrite(app: AppHandle, folder_path: String) -> CommandResult<()> {
|
fn run_llm_commit_rewrite(app: AppHandle, folder_path: String) -> CommandResult<()> {
|
||||||
let state = app.state::<AppState>();
|
let state = app.state::<AppState>();
|
||||||
let _operation = begin_repo_operation(&state, &folder_path, "history rewrite")?;
|
let _operation = begin_repo_operation(&state, &folder_path, "history rewrite")?;
|
||||||
let (hashes, original_birthday) = {
|
let hashes = {
|
||||||
let mut store = state.store.lock().map_err(|e| e.to_string())?;
|
let mut store = state.store.lock().map_err(|e| e.to_string())?;
|
||||||
let Some(folder) = store.folders.iter_mut().find(|f| f.path == folder_path) else {
|
let Some(folder) = store.folders.iter_mut().find(|f| f.path == folder_path) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -1395,15 +1441,18 @@ fn run_llm_commit_rewrite(app: AppHandle, folder_path: String) -> CommandResult<
|
|||||||
return Err("Another rewrite is already running for this repository.".to_string());
|
return Err("Another rewrite is already running for this repository.".to_string());
|
||||||
}
|
}
|
||||||
let hashes = folder.llm_candidates.clone();
|
let hashes = folder.llm_candidates.clone();
|
||||||
let original_birthday = folder.first_candidate_birthday;
|
|
||||||
folder.rewrite_in_progress = true;
|
folder.rewrite_in_progress = true;
|
||||||
folder.rewrite_started_at = Some(now_ms());
|
folder.rewrite_started_at = Some(now_ms());
|
||||||
(hashes, original_birthday)
|
hashes
|
||||||
};
|
};
|
||||||
save_store(&state)?;
|
save_store(&state)?;
|
||||||
|
|
||||||
let mut error: Option<String> = None;
|
let mut error: Option<String> = None;
|
||||||
let result = (|| -> CommandResult<()> {
|
let result = (|| -> CommandResult<()> {
|
||||||
|
// Do every Git safety check that can block the rewrite before issuing an LLM request.
|
||||||
|
// A dirty index/worktree, detached HEAD, conflict, or in-progress Git operation must cost
|
||||||
|
// zero model calls.
|
||||||
|
safe_repo_snapshot(&folder_path, true)?;
|
||||||
let prompt = generate_llm_commit_prompt(&folder_path, &hashes)?;
|
let prompt = generate_llm_commit_prompt(&folder_path, &hashes)?;
|
||||||
let model = {
|
let model = {
|
||||||
let store = state.store.lock().map_err(|e| e.to_string())?;
|
let store = state.store.lock().map_err(|e| e.to_string())?;
|
||||||
@@ -1412,7 +1461,7 @@ fn run_llm_commit_rewrite(app: AppHandle, folder_path: String) -> CommandResult<
|
|||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| "qwen2.5-coder:7b".to_string())
|
.unwrap_or_else(|| "qwen2.5-coder:7b".to_string())
|
||||||
};
|
};
|
||||||
let llm_raw = stream_ollama_json(&prompt, &model, 0.3, &app)?;
|
let llm_raw = stream_ollama_json(&prompt, &model, 0.3, &hashes, &app)?;
|
||||||
let message_map =
|
let message_map =
|
||||||
validate_llm_commit_messages(parse_llm_commit_messages(&llm_raw)?, &hashes)?;
|
validate_llm_commit_messages(parse_llm_commit_messages(&llm_raw)?, &hashes)?;
|
||||||
reword_commits_transactionally(&folder_path, &message_map, &hashes)?;
|
reword_commits_transactionally(&folder_path, &message_map, &hashes)?;
|
||||||
@@ -1448,13 +1497,7 @@ fn run_llm_commit_rewrite(app: AppHandle, folder_path: String) -> CommandResult<
|
|||||||
let buffer = folder.llm_buffer.clone();
|
let buffer = folder.llm_buffer.clone();
|
||||||
if error.is_some() {
|
if error.is_some() {
|
||||||
folder.llm_candidates = retry_candidates;
|
folder.llm_candidates = retry_candidates;
|
||||||
folder.first_candidate_birthday = original_birthday.or_else(|| {
|
pause_automatic_rewrite_after_failure(folder);
|
||||||
if folder.llm_candidates.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(now_ms())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
folder.llm_candidates = buffer;
|
folder.llm_candidates = buffer;
|
||||||
folder.first_candidate_birthday = if folder.llm_candidates.is_empty() {
|
folder.first_candidate_birthday = if folder.llm_candidates.is_empty() {
|
||||||
@@ -1472,6 +1515,20 @@ fn run_llm_commit_rewrite(app: AppHandle, folder_path: String) -> CommandResult<
|
|||||||
save_store(&state)?;
|
save_store(&state)?;
|
||||||
emit(&app, "repo-updated", folder_path.clone());
|
emit(&app, "repo-updated", folder_path.clone());
|
||||||
|
|
||||||
|
if let Some(err) = error.as_deref() {
|
||||||
|
emit(
|
||||||
|
&app,
|
||||||
|
"monitoring-error",
|
||||||
|
json!({
|
||||||
|
"path": folder_path,
|
||||||
|
"code": "AUTO_REWRITE_PAUSED",
|
||||||
|
"message": format!(
|
||||||
|
"Automatic commit-message rewriting was paused after one failed attempt. No automatic retry will run until a new commit is recorded or you explicitly retry. {err}"
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
error.map_or(Ok(()), Err)
|
error.map_or(Ok(()), Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1492,6 +1549,11 @@ fn automatic_rewrite_eligible(folder: &FolderObj) -> bool {
|
|||||||
&& !folder.llm_candidates.is_empty()
|
&& !folder.llm_candidates.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pause_automatic_rewrite_after_failure(folder: &mut FolderObj) {
|
||||||
|
folder.lines_changed = 0;
|
||||||
|
folder.first_candidate_birthday = None;
|
||||||
|
}
|
||||||
|
|
||||||
fn line_rewrite_due(folder: &FolderObj, threshold: i64) -> bool {
|
fn line_rewrite_due(folder: &FolderObj, threshold: i64) -> bool {
|
||||||
automatic_rewrite_eligible(folder) && folder.lines_changed >= threshold
|
automatic_rewrite_eligible(folder) && folder.lines_changed >= threshold
|
||||||
}
|
}
|
||||||
@@ -1629,7 +1691,7 @@ fn auto_commit(app: AppHandle, folder_path: String, mut paths: Vec<String>) -> C
|
|||||||
folder.llm_buffer.push(new_head.clone());
|
folder.llm_buffer.push(new_head.clone());
|
||||||
} else {
|
} else {
|
||||||
folder.llm_candidates.push(new_head.clone());
|
folder.llm_candidates.push(new_head.clone());
|
||||||
if folder.llm_candidates.len() == 1 {
|
if folder.first_candidate_birthday.is_none() {
|
||||||
folder.first_candidate_birthday = Some(now_ms());
|
folder.first_candidate_birthday = Some(now_ms());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4883,6 +4945,63 @@ mod tests {
|
|||||||
assert!(!time_rewrite_due(&folder, 1, 120_000));
|
assert!(!time_rewrite_due(&folder, 1, 120_000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_automatic_rewrite_is_not_retried_by_the_scheduler() {
|
||||||
|
let mut folder = FolderObj {
|
||||||
|
path: "/tmp/example".to_string(),
|
||||||
|
monitoring: true,
|
||||||
|
needs_relocation: false,
|
||||||
|
lines_changed: 1_543,
|
||||||
|
llm_candidates: vec!["a".repeat(40)],
|
||||||
|
llm_buffer: Vec::new(),
|
||||||
|
first_candidate_birthday: Some(60_000),
|
||||||
|
last_head_hash: None,
|
||||||
|
rewrite_in_progress: false,
|
||||||
|
rewrite_started_at: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
pause_automatic_rewrite_after_failure(&mut folder);
|
||||||
|
|
||||||
|
assert_eq!(folder.lines_changed, 0);
|
||||||
|
assert_eq!(folder.first_candidate_birthday, None);
|
||||||
|
assert!(!line_rewrite_due(&folder, 10));
|
||||||
|
assert!(!time_rewrite_due(&folder, 1, 120_000));
|
||||||
|
assert_eq!(folder.llm_candidates.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn structured_commit_output_requires_every_requested_hash() {
|
||||||
|
let hashes = vec!["aaaaaaa111111111111111111111111111111111".to_string()];
|
||||||
|
let schema = commit_message_json_schema(&hashes);
|
||||||
|
|
||||||
|
assert_eq!(schema["type"], "object");
|
||||||
|
assert_eq!(schema["additionalProperties"], false);
|
||||||
|
assert_eq!(schema["required"], json!(["aaaaaaa"]));
|
||||||
|
assert_eq!(schema["properties"]["aaaaaaa"]["type"], "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn existing_non_repo_can_remain_armed_for_monitoring() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
let mut store = StoreData::default();
|
||||||
|
store.folders.push(FolderObj {
|
||||||
|
path: temp.path().to_string_lossy().to_string(),
|
||||||
|
monitoring: true,
|
||||||
|
needs_relocation: false,
|
||||||
|
lines_changed: 0,
|
||||||
|
llm_candidates: Vec::new(),
|
||||||
|
llm_buffer: Vec::new(),
|
||||||
|
first_candidate_birthday: None,
|
||||||
|
last_head_hash: None,
|
||||||
|
rewrite_in_progress: false,
|
||||||
|
rewrite_started_at: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
normalize_store(&mut store);
|
||||||
|
|
||||||
|
assert!(store.folders[0].monitoring);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn commit_summary_uses_the_javascript_field_names() {
|
fn commit_summary_uses_the_javascript_field_names() {
|
||||||
let summary = CommitSummary {
|
let summary = CommitSummary {
|
||||||
@@ -4971,6 +5090,19 @@ mod tests {
|
|||||||
assert!(parse_llm_commit_messages("```json\n{\"aaaaaaa\":\"message\"}\n```").is_err());
|
assert!(parse_llm_commit_messages("```json\n{\"aaaaaaa\":\"message\"}\n```").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commit_message_prompt_is_bounded_before_any_model_call() {
|
||||||
|
let temp = init_test_repo();
|
||||||
|
let path = temp.path().to_str().unwrap();
|
||||||
|
let contents = "let generated_value = 1;\n".repeat(10_000);
|
||||||
|
let hash = test_commit(path, "large.rs", &contents, "auto-git: large change");
|
||||||
|
|
||||||
|
let prompt = generate_llm_commit_prompt(path, &[hash]).unwrap();
|
||||||
|
|
||||||
|
assert!(prompt.chars().count() <= MAX_COMMIT_MESSAGE_PROMPT_CHARS);
|
||||||
|
assert!(prompt.contains("[diff truncated by Auto-Git]"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn root_commit_can_be_reworded_without_a_parent_revision() {
|
fn root_commit_can_be_reworded_without_a_parent_revision() {
|
||||||
let temp = init_test_repo();
|
let temp = init_test_repo();
|
||||||
@@ -5186,6 +5318,10 @@ mod tests {
|
|||||||
root,
|
root,
|
||||||
&temp.path().join("node_modules/package/index.js")
|
&temp.path().join("node_modules/package/index.js")
|
||||||
));
|
));
|
||||||
|
assert!(!is_default_ignored(
|
||||||
|
root,
|
||||||
|
&temp.path().join("src-tauri/Cargo.lock")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Auto-Git",
|
"productName": "Auto-Git",
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"identifier": "com.victorgiers.auto-git",
|
"identifier": "com.victorgiers.auto-git",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist-tauri"
|
"frontendDist": "../dist-tauri"
|
||||||
|
|||||||
Reference in New Issue
Block a user