This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
Automating software releases with CLI scripts works well until something goes wrong. A developer pushes a commit titled feat!: breaking change on a 0.2.0 crate, and a naive tool bumps it to 1.0.0 instead of 0.3.0. Or a release script runs in CI, creates an annotated tag, and fails halfway through manifest updates, leaving the repository in an inconsistent state.
Even worse is the release chore itself: collating commits across ranges, figuring out breaking changes, drafting human-readable changelogs, updating multiple package manifests, and verifying test suites. It is the textbook definition of tedious maintenance work that developers put off until the last minute.
I built ReleaseWeave because I wanted a desktop cockpit for release decisions that removes this friction. Instead of firing off blind commands or manually piecing together release notes, ReleaseWeave inspects the commit range, calculates SemVer recommendations, runs local verification gates, drafts release notes using local open-weight AI, and previews diffs before anything touches disk or Git remotes.
Here is how it is put together, why I chose GPUI over a webview, and how the internals work.
What I built
ReleaseWeave is a native desktop application written in Rust and GPUI for managing software releases.
It connects directly to local Git repositories and automates the release pipeline:
- Automated commit analysis: Scans commit history, parses Conventional Commits, and groups changes into Features, Fixes, Performance, Docs, and Breaking Changes.
-
Zero-version aware SemVer bumping: Recommends next versions adhering strictly to SemVer 2.0 clause 4 for
0.xinitial development releases. -
Local AI release notes: Generates structured changelogs using local Ollama models (
qwen2.5-coder:7borgemma:7b), with strict schema validation and commit provenance tracking. -
Two-pass atomic manifest updating: Previews edits across
Cargo.toml,package.json,pyproject.toml, andCHANGELOG.mdwith SHA-256 pre-flight checks and atomic replacements. - Built-in readiness gates: Runs test and lint commands with timeout guards, process reaping, and output redaction for secret tokens.
Demo
The application compiles into a single native binary that opens in milliseconds and renders at the monitor refresh rate:
Repository: release-weave (v0.2.0)
Incoming Commits: 14 commits since v0.2.0
Detected Changes: 2 Breaking Changes, 5 Features, 7 Fixes
Recommended Bump: Minor (0.2.0 -> 0.3.0) [Zero-version initial development mode]
Readiness Status: All 3 gates passed (cargo check, cargo test, clippy)
Notes Status: Generated via local Ollama (14/14 commits verified)
Code
The complete source code is open source under the Apache License 2.0 on GitHub:
The lazy payoff
Writing release notes and coordinating version updates is slow, error-prone busywork.
In a typical release flow, a maintainer:
- Runs
git log --oneline <last-tag>..HEAD. - Reads through dozens of commit subjects to identify breaking changes.
- Decides the version bump level.
- Manually edits
Cargo.toml,package.json, or other manifest files. - Writes out a formatted changelog by hand.
- Runs tests and git tag commands.
For an active repository, that routine takes 20 to 30 minutes of manual effort per release, with constant risk of typos, forgotten manifest files, or broken version bumps.
With ReleaseWeave:
- The entire commit history is grouped and analyzed instantly.
- SemVer logic calculates the exact bump level according to specification rules.
- Local open-source AI drafts readable release notes in 3 seconds.
- Every declared change links to its source commit hash, so you never have to cross-check whether the model invented something.
- A single click applies all manifest updates atomically.
The routine goes from half an hour of tedious collation to under 30 seconds of visual review.
Why open innovation matters
Release tools operate directly on proprietary codebases, internal commit logs, and contributor names. Handing raw git commit streams to a closed third-party cloud API introduces serious privacy, telemetry, and security concerns.
Open innovation is central to ReleaseWeave:
- Local privacy: Release notes are generated locally using open-weight models via Ollama. No repository data, commit messages, or developer identities ever leave the local machine.
- Zero token costs: Maintainers do not need paid API keys, credit cards, or third-party cloud accounts to generate release notes.
- Model freedom: Because it uses an open interface, developers can run Qwen 2.5 Coder, Gemma, Llama 3, or any local model fine-tuned for code.
- Deterministic fallback: If Ollama is not installed or offline, the system falls back instantly to a deterministic Keep a Changelog generator without failing the release workflow.
Choosing GPUI over a webview
Most modern developer desktop utilities reach for Tauri or Electron. Both rely on a browser engine to render the interface. For a tool meant to sit open alongside a code editor, I wanted low memory use and quick startup without spinning up a web runtime.
ReleaseWeave uses GPUI through gpui-kit. GPUI is the GPU-accelerated UI framework created for the Zed editor. It draws directly to the screen using Metal on macOS, Vulkan or OpenGL on Linux, and DirectX on Windows. The entire application compiles to a single native binary. Idle memory sits around 40 MB, and interactions render at the display refresh rate.
The architecture strictly decouples the UI from the domain. Nothing in src/domain/, src/services/, or src/storage/ imports GPUI. The entire release pipeline (commit scanning, version calculation, readiness checks, and manifest generation) can run headlessly in integration tests without opening a window:
// tests/pipeline.rs runs the full release flow offline without UI dependencies
let repo = git2::Repository::open(repo_path).expect("open fixture");
let range = GitService::resolve_range(&repo, Some("v0.2.0"), "HEAD")?;
let commits = GitService::commits_in_range(&repo, &range)?;
let current_ver = VersionService::read_current_version(&repo, repo_path)?;
let bump = VersionService::recommend_bump(¤t_ver, &commits);
assert_eq!(bump.level, BumpLevel::Minor); // Zero-version breaking change: 0.2.0 -> 0.3.0
Zero-version SemVer awareness
Standard SemVer 2.0 has an explicit clause for initial development versions: in 0.y.z, anything may change at any time, and breaking changes do not increment the major version number. A breaking change in 0.2.0 results in 0.3.0, not 1.0.0.
Many automated release scripts get this wrong. They look for ! or BREAKING CHANGE: in the conventional commit message and unconditionally bump the major version digit.
ReleaseWeave checks the active version first:
pub fn recommend_bump(current: &Version, commits: &[CommitInfo]) -> BumpRecommendation {
let mut has_breaking = false;
let mut has_feat = false;
let mut has_fix = false;
for commit in commits {
if commit.is_breaking {
has_breaking = true;
} else if commit.category == ChangeCategory::Feature {
has_feat = true;
} else if commit.category == ChangeCategory::Fix {
has_fix = true;
}
}
let is_zero_version = current.major == 0;
let (level, next_version) = if has_breaking {
if is_zero_version {
(BumpLevel::Minor, Version::new(0, current.minor + 1, 0))
} else {
(BumpLevel::Major, Version::new(current.major + 1, 0, 0))
}
} else if has_feat {
(BumpLevel::Minor, Version::new(current.major, current.minor + 1, 0))
} else if has_fix {
(BumpLevel::Patch, Version::new(current.major, current.minor, current.patch + 1))
} else {
(BumpLevel::None, current.clone())
};
BumpRecommendation { level, recommended_version: next_version, .. }
}
The UI displays the recommendation alongside the list of commits that triggered it, so you can override it or add prerelease suffixes (-rc.1, -beta) before applying changes.
Atomic two-pass manifest updating
Release tools that write directly to files can corrupt project state if an error occurs midway through. If you update package.json, then hit an error while updating CHANGELOG.md, the repository is left dirty with partial edits.
ReleaseWeave uses a two-pass apply system:
-
Pass 1 (Pre-flight validation): The engine reads
Cargo.toml,package.json,pyproject.toml, andCHANGELOG.md. It generates proposed patches in memory and calculates SHA-256 hashes of the files currently on disk. If any file changed on disk between preview generation and apply confirmation, the apply aborts immediately. -
Pass 2 (Atomic replacement): For each patch, the new content is written to a hidden sibling file (
.{filename}.rw-tmp). Once written successfully,std::fs::renamereplaces the target file atomically.
// Pass 1: Verify all hashes match disk before making any modifications
for patch in &plan.patches {
if patch.old_content.is_some() {
let current_content = fs::read_to_string(&patch.path)?;
let current_sha256 = sha256_hex(current_content.as_bytes());
if Some(¤t_sha256) != patch.old_sha256.as_ref() {
return Err(AppError::Validation(format!(
"File '{}' was modified on disk since preview. Apply aborted.",
patch.relative_path
)));
}
}
}
// Pass 2: Write temporary files and rename atomically
for patch in &plan.patches {
let parent = patch.path.parent().unwrap_or(Path::new("."));
let filename = patch.path.file_name().and_then(|f| f.to_str()).unwrap_or("tmp");
let tmp_path = parent.join(format!(".{filename}.rw-tmp"));
fs::write(&tmp_path, &patch.new_content)?;
if let Err(e) = fs::rename(&tmp_path, &patch.path) {
let _ = fs::remove_file(&tmp_path);
return Err(AppError::Io(e));
}
}
If the apply fails at any point in Pass 1, not a single byte on disk has been changed.
Local AI release notes with commit provenance
Release notes generated by language models often look plausible while hallucinating features or bug fixes that were never in the commits.
To keep AI notes grounded, ReleaseWeave uses local Ollama instances (qwen2.5-coder:7b by default) with strict schema validation and commit provenance checking.
The prompt frames the commit data between explicit delimiters (<<<COMMITS_START>>> and <<<COMMITS_END>>>) with delimiter sanitization on all untrusted Git metadata. The model is constrained to return a JSON array where each entry includes the exact commit hashes supporting that change.
Once the response returns, the parser validates each declared commit hash against the actual commit list scanned from Git:
pub fn validate_and_convert_items(
raw_response: &str,
valid_hashes: &HashSet<String>,
provider_name: &str,
model_name: &str,
) -> Result<Vec<ChangeItem>> {
let parsed = parse_ai_json(raw_response)?;
let mut items = Vec::new();
for raw_item in parsed.items {
let mut verified_sources = Vec::new();
let mut has_unverified = false;
for hash in raw_item.commit_hashes.unwrap_or_default() {
let clean = hash.trim();
let is_valid = valid_hashes.iter().any(|vh| vh.starts_with(clean) || clean.starts_with(vh));
if is_valid {
verified_sources.push(SourceRef::Commit(clean.to_string()));
} else {
has_unverified = true;
}
}
let confidence = if has_unverified && verified_sources.is_empty() {
0.3 // Unsourced item flag
} else if has_unverified {
0.7
} else {
1.0
};
items.push(ChangeItem {
title: raw_item.title,
confidence_score: confidence,
verified_sources,
// ...
});
}
Ok(items)
}
In the UI, any item the model made up displays an amber Unsourced tag with 30% confidence, giving the developer an immediate visual warning before publishing.
Security boundaries and readiness checks
A release tool has access to local files and shell commands. ReleaseWeave enforces several checks on execution:
- Readiness commands (like
cargo testornpm run lint) go through a command policy that rejects direct shell interpreter invocations (sh,bash,cmd.exe,powershell.exe) and inline execution arguments (-c,/c). - Executable resolution searches the target working directory for Windows batch scripts (
.bat,.cmd) to catch argument metacharacters (&,|,<,>,^,%,") before WindowsCreateProcesscan invokecmd.exe. - Process timeouts spawn child processes with stdout/stderr pipes, and if a check times out, the child process is terminated immediately with
child.kill()and reaped withchild.wait()to avoid orphan leaks. - API tokens for GitHub and GitLab are stored in the system keyring (
keyringcrate) instead of plaintext files or SQLite. - All terminal output and logs pass through a redactor that scrubs GitHub PATs (
ghp_***), GitLab PATs (glpat-***), Bearer tokens, and private keys.
What is next
The codebase currently stands around 18,000 lines of Rust across domain, service, storage, and GPUI views.
A few areas still need attention:
- Monorepo multi-package batching: Currently, you select one package at a time from the package switcher. Batching version bumps across coordinated workspace crates in a single release plan is in progress.
- Linux packaging: GPUI requires Wayland or X11 libraries and Vulkan drivers. Providing portable AppImage and Flatpak builds across differing glibc versions remains a testing focus.
The project is open source on GitHub: masyoudi/release-weave. If you are building desktop tools in Rust with GPUI or working on release automation, I would love to hear your thoughts.

Top comments (0)