Thank you for your interest in contributing to Gentle AI (gga) — a Go TUI installer for AI agent environments.
Before you dive in, please read this guide fully. We have a structured workflow to keep the project organized and maintainable.
- Issue-First Workflow
- Label System
- Development Setup
- Testing
- Commit Convention
- Delivery Strategy for SDD Changes
- Pull Request Rules
- Code of Conduct
No PR without an issue. No exceptions.
This project follows a strict issue-first workflow:
- Open an issue using the appropriate template (Bug Report or Feature Request)
- Wait for approval — a maintainer will add the
status:approvedlabel when the issue is ready to be worked on - Comment on the issue to let others know you're working on it
- Open a PR referencing the approved issue
PRs that are not linked to an approved issue will be automatically rejected by CI.
Start at the Community Roadmap.
Everything labelled up-for-grabs is scoped, carries status:approved so a PR can be opened, and is unclaimed. Comment that you are taking it and go.
An issue without that label is usually waiting on information (status:needs-info) or on an architectural decision (status:needs-design). Those want discussion first — implementing before the decision lands means the work gets thrown away.
| Label | Description |
|---|---|
type:bug |
Bug fix |
type:feature |
New feature or enhancement |
type:docs |
Documentation only |
type:refactor |
Code refactoring, no functional changes |
type:chore |
Build, CI, tooling changes |
type:breaking-change |
Breaking change |
| Label | Description |
|---|---|
size:exception |
Maintainer-approved exception for PRs above the 400 changed-line review budget |
| Label | Description |
|---|---|
status:needs-review |
Newly opened, awaiting maintainer review |
status:approved |
Approved for implementation — work can begin |
status:in-progress |
Being worked on |
status:blocked |
Blocked by another issue or external dependency |
status:wont-fix |
Out of scope or won't be addressed |
| Label | Description |
|---|---|
priority:critical |
Blocking issues, security vulnerabilities |
priority:high |
Important, affects many users |
priority:medium |
Normal priority |
priority:low |
Nice to have |
- Go 1.24+
- Docker (for E2E tests)
- Git
git clone https://github.com/Gentleman-Programming/gentle-ai.git
cd gentle-ai
go build -o gga ../ggaRun the full unit test suite:
go test ./...Run tests for a specific package:
go test ./internal/tui/...Run with verbose output:
go test -v ./...E2E tests are Docker-based shell scripts. Docker must be running.
cd e2e
chmod +x docker-test.sh
./docker-test.sh
⚠️ E2E tests spin up containers to simulate real installation environments. They may take a few minutes to complete.
bench/ is a separate Go module, so root-module tests do not validate it. For benchmark-module changes, run these commands from bench/:
go build ./...
go vet ./...
go test ./...The portable core includes j52 through j56 and j58 under a normal product
binary. j57 is source-coupled, so build its tagged product binary from the
repository root, then run it from bench/:
# From the repository root.
go build -tags bench_fixture -o /path/to/gentle-ai ./cmd/gentle-ai
# From bench/, after building gentle-ai-bench above.
./gentle-ai-bench run --binary /path/to/gentle-ai --axis source-coupled --only j57-sdd-authority-drift-during-discovery-fails-closedBenchmark validation applies to review-lifecycle, gate, recovery, delivery, benchmark implementation/corpus/classifier, and benchmark-claim changes. For measured product-behavior changes, use driven mode and report the command, tested binary or commit, selected subset or axes, and result summary. Compare before and after only when claiming a measured friction change. For unrelated changes, mark benchmark validation N/A with a brief reason.
Some unit tests require OS-level capabilities that are restricted on Windows by default.
Tests that create symbolic links (e.g. in internal/components/filemerge) will be skipped automatically on Windows builds where the process lacks SeCreateSymbolicLinkPrivilege (ERROR_PRIVILEGE_NOT_HELD, errno 1314). This is a Windows security policy, not a bug in the code.
To run these tests without restrictions, choose one of:
- Enable Developer Mode — Settings → System → For developers → Developer Mode. This grants symlink creation to all processes without admin rights.
- Run as Administrator — open your terminal as Administrator before running
go test ./.... - Grant the privilege explicitly via Group Policy:
Local Security Policy → User Rights Assignment → Create symbolic links.
On Linux and macOS these tests always run without any extra setup.
This project uses Conventional Commits.
Commit messages must match this pattern:
^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9\._-]+\))?!?: .+
<type>(<optional-scope>)!: <description>
[optional body]
[optional footer]
| Type | Purpose |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
refactor |
Code change (no behavior change) |
chore |
Maintenance, dependencies, tooling |
style |
Formatting, linting (no logic change) |
perf |
Performance improvement |
test |
Adding or updating tests |
build |
Build system or external deps |
ci |
CI configuration |
revert |
Reverts a previous commit |
feat(tui): add progress bar to installation steps
fix(agent): correct Claude Code detection on macOS
docs: update contributing guide
chore(deps): bump bubbletea to v0.26
refactor(pipeline): extract step executor
style: fix linter warnings in catalog package
perf(system): cache OS detection result
test(installer): add coverage for catalog step execution
build: update goreleaser config for arm64
ci: split unit and e2e test jobs
revert: undo model picker redesign
Add ! after the type/scope and include a BREAKING CHANGE: footer:
feat(cli)!: rename --config flag to --config-file
BREAKING CHANGE: the --config flag has been renamed to --config-file.
Update your scripts and aliases accordingly.
Breaking changes map to the type:breaking-change label.
Branch names must match this pattern:
^(feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)\/[a-z0-9._-]+$
Rules:
- All lowercase
- Use hyphens, dots, or underscores as separators (no spaces, no uppercase)
- Description must be short and descriptive
Examples: feat/user-login, fix/crash-on-startup, docs/api-reference, ci/add-e2e-job
Before sdd-apply starts, the SDD conductor checks the Review Workload Forecast from sdd-tasks. This protects reviewers from one giant, exhausting PR when the work should be split.
| Strategy | Use when | What happens before apply |
|---|---|---|
ask-on-risk |
Default. You want the conductor to pause only when the forecast is risky. | If the forecast is high or above 400 changed lines, it asks whether to split or proceed with size:exception. |
auto-chain |
You already know the change should be reviewed in slices. | The apply phase implements the next chained/stacked PR slice using work-unit commits. |
single-pr |
The change is small or must land atomically. | If the forecast exceeds 400 changed lines, apply stops until a maintainer approves size:exception. |
exception-ok |
A maintainer already accepted a large PR. | Apply continues and records that the PR has maintainer-approved size:exception. |
Decision checklist:
- Can one reviewer understand this in about 60 minutes?
- Is the PR at or below 400 changed lines?
- Does each work-unit commit include its code, tests, and docs together?
- If the answer is “no” to any item, choose
auto-chainor get explicitsize:exceptionapproval.
Mental model: work-unit commits are the bricks; chained PRs are the wall sections. Don’t make reviewers inspect the whole building in one sitting.
Keep PRs at or below 400 changed lines (additions + deletions). This is a deliberate cognitive-load limit: a PR should be reviewable in roughly 60 minutes without pushing reviewers into fatigue.
If your change cannot fit that budget, split it into chained or stacked PRs so each review remains focused. Large generated/vendor/migration diffs may use the size:exception label, but only when a maintainer agrees the large diff is unavoidable.
Structure commits by deliverable unit, not by file type. A good commit includes the code, tests, and docs needed to understand and verify one behavior or workflow.
- Prefer
feat(auth): validate tokens at loginover separatemodels,services, andtestscommits. - Keep rollback reasonable: reverting one commit should not remove unrelated work.
- When a PR grows near 400 changed lines, promote work-unit commits into chained or stacked PRs.
Review feedback should be warm, direct, and useful quickly. Start with the actionable point, explain why when needed, and avoid recapping the PR before giving feedback.
- There is a linked approved issue (
Closes #<N>) - The PR is at or below 400 changed lines, or a maintainer approved
size:exception - Commits are organized by deliverable work unit
- All unit tests pass (
go test ./...) - E2E tests pass (
cd e2e && ./docker-test.sh) - Benchmark validation completed, or this change is not applicable to the benchmark (explain why in the Test Plan).
- Commits follow Conventional Commits format
- Code is self-reviewed
Use the same Conventional Commits format as commit messages:
feat(tui): add keyboard shortcut help overlay
fix(agent): handle missing HOME env var gracefully
All PRs go through automated checks:
| Check | What It Verifies |
|---|---|
| Check PR Cognitive Load | PR stays within 400 changed lines (additions + deletions) unless labelled size:exception |
| Check Issue Reference | PR body contains Closes/Fixes/Resolves #N |
| Check Issue Has status:approved | The linked issue has been approved by a maintainer |
| Check PR Has type: Label* | Exactly one type:* label is applied |
| Unit Tests | go test ./... passes |
| E2E Tests | cd e2e && ./docker-test.sh passes |
All checks must pass before a PR can be merged.
In the PR body, include one of:
Closes #42
Fixes #42
Resolves #42
Be respectful. We're building something together.
- Critique code, not people
- Be constructive in reviews
- Welcome newcomers
Violations may result in removal from the project.
Use GitHub Discussions — not issues — for questions, ideas, and general conversation.