A good release workflow answers one question: what evidence do we have that this change is safe to ship? It does not begin with a tool category. It begins with the code, the intended behavior, and the environment where the software will run.
Local-first tooling is useful because most of that evidence already exists on the developer's machine: the working tree, the exact build, local logs, test output, database state, API responses, and the reasoning that connects them. Keeping those signals close reduces context switching and makes the review reproducible even when a hosted service is unavailable.
This guide defines a workflow that is small enough for one developer and disciplined enough for a team.
What local-first means in practice
Local-first does not mean offline-only, anti-cloud, or isolated work. It means the local workspace remains the authoritative place where a developer can inspect and operate on a project. Cloud services can add collaboration, CI capacity, remote backups, and deployment control, but they do not become a prerequisite for understanding a change.
A credible local-first workflow has four properties:
- Repository content is usable without uploading it to a third party.
- Core inspection features keep working without an account or network connection.
- Secrets and credentials are stored in an operating-system credential vault, not browser storage or project files.
- Cloud integrations are explicit, narrow, and reversible.
That boundary matters when a repository is private, regulated, under embargo, or simply too valuable to scatter across unnecessary systems.
Step 1: define the intended outcome
Before reading the diff, write the expected outcome in one or two sentences. A useful statement names the user, the changed behavior, and a measurable success condition.
When a signed-in customer opens the billing page, they can manage their subscription in one action, and a failed request produces a clear, recoverable error.
This prevents a common review failure: proving that the implementation is internally consistent without proving that it solves the requested problem.
Turn the outcome into a short evidence list:
- The intended code path is present.
- Authorization is enforced at the boundary.
- Success and failure states are visible to the user.
- The external dependency is called safely.
- A test or deterministic manual check verifies the behavior.
Step 2: inspect repository state before the diff
Start with the shape of the change. Check the current branch, upstream relationship, staged files, unstaged files, untracked files, and ignored files. A correct diff can still ship from the wrong branch or omit an essential file.
git status --short --branch
git diff --stat
git diff --cached --stat
git ls-files --others --exclude-standard
Look for unexpected generated output, lockfile changes, migrations without application code, or application code without its migration. Confirm that the change does not quietly include build artifacts, local databases, coverage reports, or credential files.
The key question is not "are there many files?" It is "does every changed file belong to the intended outcome?"
Step 3: review the change in layers
Reading one large diff from top to bottom creates fatigue. Review in layers instead.
First, review boundaries: routes, commands, public functions, database migrations, configuration, and permission checks. Second, review the core behavior. Third, review user-facing states and operational details. Finally, review tests and documentation against the outcome.
For every layer, ask:
- What input enters here?
- Where is it validated and normalized?
- What authority is required?
- Which state changes are permanent?
- What happens when the dependency times out?
- What information can appear in logs or errors?
This approach catches architectural mistakes before spending time on line-level style.
For a deeper diff method, use How to Review a Git Diff Without Missing the Real Risk.
Step 4: verify behavior at the nearest useful boundary
Run the cheapest check that can disprove the change, then move outward.
- Type checking and static analysis
- Focused unit tests
- Module or service integration tests
- Build in production mode
- A targeted end-to-end path
- Manual review of high-risk UX states
Do not use a full end-to-end suite as the first signal. It is slower and often produces less precise failures. Do not stop at unit tests when the risk lives at a database, network, filesystem, or process boundary.
Record the exact commands and results. "Tests pass" is weak evidence. "Authentication service tests, API integration tests, and the production frontend build pass on commit abc123" can be reproduced.
Step 5: inspect data and API side effects
Application behavior is often correct in memory and wrong at the boundary. Exercise the actual request shape and inspect the resulting data.
For an API change, verify status codes, response schemas, authorization, idempotency, timeouts, and safe errors. For a database change, verify constraints, indexes, forward migration, and the behavior of both old and new records.
Use read-only database access by default. Make write mode a deliberate transition with a visible target and transaction boundary. Never test destructive queries against an ambiguous connection.
The Safe Database Inspection Before a Release guide provides a detailed sequence.
Step 6: scan for release risk
Risk scanning is broader than secret scanning. A release review should look for:
- New environment variables without deployment configuration
- Credentials, tokens, private keys, or connection strings
- Dependency additions and lockfile drift
- Permission or role changes
- Destructive database operations
- New outbound domains
- Missing loading, empty, and error states
- Logging of request bodies or personal information
- Platform-specific filesystem or shell assumptions
A scanner can identify patterns, but the reviewer must connect them to behavior. A new URL is harmless in a documentation link and critical in an OAuth callback allowlist.
Step 7: prepare the release artifact and narrative
Build from a clean, known commit. Produce checksums for distributable artifacts and sign updater metadata with a protected key. Keep the signing key outside the repository and outside ordinary application storage.
Release notes should explain impact, not repeat commit subjects. Organize them around user-visible changes, fixes, security implications, compatibility, and required actions. See Release Notes Developers and Customers Can Actually Use.
Before publishing, verify the artifact that customers will receive, not only the source tree that produced it.
A compact release evidence record
Keep a short record beside the release:
| Evidence | Result | | --- | --- | | Intended outcome | One-sentence acceptance statement | | Commit | Immutable commit identifier | | Static checks | Commands and successful result | | Tests | Focused suites and environment | | Data checks | Migration and constraint verification | | Security checks | Secrets, dependencies, permissions | | Artifact | Filename, size, SHA-256, signature | | Rollback | Trigger and recovery action |
This is not bureaucracy. It is compressed operational memory. When a release fails, it tells you what was actually verified.
The workflow is the product
Developer productivity is not the number of buttons available. It is how quickly a developer can move from uncertainty to justified confidence. A local-first workflow shortens that path by keeping evidence near the code and making external access intentional.
Start with the intended outcome, review boundaries before details, verify behavior at the right depth, inspect side effects, and publish an artifact you can identify and recover. Tools should make that sequence easier to repeat. They should never replace the reasoning that makes a release trustworthy.