This document provides guidelines for AI agents and human contributors working on devboost. The goal is to maintain super high quality code that follows 2025 best practices, prioritizes ease of use, and makes it trivially easy to add new modules.
- Zero prompts: Users should never be asked questions during execution. All decisions should be made automatically with sensible defaults.
- Fully configurable: Everything must be configurable via
~/.devboost.yaml, but defaults should work perfectly out of the box. - Idempotent: Safe to run multiple times. Always check state before making changes.
- Non-destructive: Never modify user's existing files directly. Use managed blocks/includes.
-
Bash best practices:
- Use
set -euo pipefailat the top of all scripts - Quote all variables:
"$var"not$var - Use
[[ ]]for conditionals (not[ ]) - Prefer
command -voverwhich - Use
readonlyfor constants - Avoid
evalunless absolutely necessary (and document why)
- Use
-
Error handling:
- Always check return codes
- Provide helpful error messages with context
- Use
db_log_errorfor errors,db_log_warnfor warnings - Never silently fail (unless explicitly handling expected failures)
-
Performance:
- Minimize external command calls
- Cache results when appropriate (OS detection, config parsing)
- Use efficient string operations
- Avoid unnecessary subshells
-
Readability:
- Clear function names:
db_module_foo_applynotapply_foo - Consistent naming:
db_*prefix for all functions - Comments explain why, not what
- Keep functions focused (single responsibility)
- Clear function names:
Adding a new module should be trivial:
- Create
modules/module_foo.sh:
# Foo module
db_module_foo_register() {
db_register_module "foo" \
"db_module_foo_plan" \
"db_module_foo_apply" \
"db_module_foo_doctor" # optional
}
db_module_foo_plan() {
local enable=$(db_yaml_get '.foo.enable' 'true')
if [[ "$enable" != "true" ]]; then
return 0
fi
# Check what would change
if [[ ! -f "$HOME/.foo/config" ]]; then
db_log_info "Would create: $HOME/.foo/config"
fi
}
db_module_foo_apply() {
local enable=$(db_yaml_get '.foo.enable' 'true')
if [[ "$enable" != "true" ]]; then
return 0
fi
# Do the work idempotently
db_ensure_dir "$HOME/.foo"
local content=$(db_render_foo_config)
db_write_file "$HOME/.foo/config" "$content"
}
# Optional: diagnostics
db_module_foo_doctor() {
db_command_exists foo && db_log_success "foo: found" || db_log_error "foo: not found"
}- Add to
build.sh:
cat modules/module_foo.sh
echo ""
# ... in registration section:
db_module_foo_register- Rebuild:
./build.sh
That's it! The module is now part of the system.
- Always check enable flag:
local enable=$(db_yaml_get '.module.enable' 'true') - Use core helpers:
db_write_file,db_upsert_block,db_backup_file,db_ensure_dir - Respect dry-run: Check
DB_DRY_RUNbefore making changes - Provide defaults: All config values should have sensible defaults
- Research before defaulting: Before choosing a default value, always research developer community preferences and best practices on the internet
- Justify defaults: Defaults should be chosen based on what works best for developers, not just what the tool's default is
- Document reasoning: When a default differs from the tool's default, document why in code comments
- Idempotent operations: Check if something exists before creating it
- Use config system:
db_yaml_getfor all configuration - Log appropriately: Use
db_log_info,db_log_success,db_log_warn,db_log_error
Follow the CBEAMS commit message style:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentation onlystyle: Formatting, missing semicolons, etc.refactor: Code change that neither fixes a bug nor adds a featureperf: Performance improvementtest: Adding or updating testschore: Maintenance tasks
Examples:
feat(zsh): add support for custom znap path
Allow users to configure znap installation path via
.zsh.znap_path in config file. Defaults to ~/.zsh-snap
if not specified.
Closes #42
fix(starship): correct git_status format syntax
The format string was using invalid variable concatenation.
Changed to use $all_status only, which is the correct
starship syntax.
Fixes #38
docs(readme): add installation instructions
Adds quick start section with curl-based installation
and build-from-source instructions.
Rules:
- Separate subject from body with a blank line
- Limit subject line to 50 characters
- Capitalize subject line
- Do not end subject line with a period
- Use imperative mood ("add" not "adds" or "added")
- Wrap body at 72 characters
- Use body to explain what and why vs. how
devboost follows Semantic Versioning with OS/tooling-specific adjustments:
Format: MAJOR.MINOR.PATCH
-
MAJOR: Breaking changes that require user action
- Config file schema changes (old configs won't work)
- Removal of features
- Changes to default behavior that users rely on
- Users should be aware: If they're on an older MAJOR version, they may need to update their config
-
MINOR: New features, backwards compatible
- New modules
- New config options (with defaults)
- Enhancements to existing modules
- Users can upgrade safely: Old configs still work
-
PATCH: Bug fixes, backwards compatible
- Fixes to existing functionality
- Performance improvements
- Documentation updates
- Users should upgrade: Fixes issues they may be experiencing
Version Compatibility:
- Users can always upgrade PATCH versions safely
- Users can upgrade MINOR versions safely (new features available)
- Users upgrading MAJOR versions should review changelog for breaking changes
- The script should detect if user's config is from an older MAJOR version and warn (but not block)
Version Detection:
# In core_main.sh or similar
DB_VERSION="1.0.0"
DB_CONFIG_VERSION=$(db_yaml_get '.version' '')
if [[ -n "$DB_CONFIG_VERSION" ]]; then
local config_major=$(echo "$DB_CONFIG_VERSION" | cut -d. -f1)
local script_major=$(echo "$DB_VERSION" | cut -d. -f1)
if [[ "$config_major" -lt "$script_major" ]]; then
db_log_warn "Config file version ($DB_CONFIG_VERSION) is older than script version ($DB_VERSION)"
db_log_warn "Please review CHANGELOG.md for breaking changes"
fi
fiVersioning Best Practices:
- Update version in
core/core_main.sh(DB_VERSION) - Tag releases:
git tag -a v1.0.0 -m "Release 1.0.0" - Maintain
CHANGELOG.mdwith:- Breaking changes (MAJOR)
- New features (MINOR)
- Bug fixes (PATCH)
REQUIRED Before Pushing to origin/main:
- Bump version if needed (MAJOR for breaking changes, MINOR for new features, PATCH for fixes)
- Build the script:
./build.shmust succeed (builds todevboost.shin root) - Verify build:
bash -n devboost.shmust pass - Commit both: version bump in
core/core_main.shAND the builtdevboost.shin root
Version Bump Guidelines:
- PATCH (1.0.0 → 1.0.1): Bug fixes, documentation updates, internal improvements
- MINOR (1.0.0 → 1.1.0): New features, new modules, new config options (backwards compatible)
- MAJOR (1.0.0 → 2.0.0): Breaking changes, config schema changes, removed features
Never push to main without:
- ✅ Version bumped (if changes warrant it)
- ✅ Script built (
./build.sh- builds to rootdevboost.sh) - ✅ Build verified (
bash -n devboost.sh) - ✅ Built
devboost.shcommitted to repository - ✅ All tests passing (including any new tests for the feature)
- ✅ Changes documented (README, CHANGELOG, or code comments as appropriate)
Complete Workflow for Changes:
- Create a feature branch:
git checkout -b feat/<short-description> - Make your changes in small, logical commits (see Commit Message Style)
- Write/update tests for the changes
- Run all tests and ensure they pass:
./tests/run-tests.sh - Build and verify:
./build.sh && bash -n devboost.sh - Update documentation (README, CHANGELOG, etc.)
- Bump version if needed (in
core/core_main.sh) and rebuild:./build.sh - Push the branch and open a PR against
main - PRs must pass all checks before merging — never push directly to
main
Why PRs matter for a public repo:
- They create a reviewable record of intent and rationale
- They keep
mainalways in a releasable state - They allow external contributors to follow the same path as maintainers
- Squash or rebase before merging to keep
git logreadable
All tests must pass before a change can be considered done.
Before submitting changes, you must run and pass all applicable tests:
- Build test:
./build.shmust succeed - Syntax check:
bash -n devboost.shmust pass - Plan test:
./devboost.sh planshould show expected changes - Idempotency test: Run
applytwice, second run should be no-op - Config test: Test with minimal config and full config
- Platform tests:
- macOS:
./tests/test-macos.shmust pass - Linux:
./tests/test-linux.sh allmust pass (if Docker is available) - If you can't test on a platform, note it in your PR and ask for help
- macOS:
Test Execution:
# Build first
./build.sh
# Test on macOS (sandboxed, safe)
./tests/test-macos.sh
# Test on all Linux distributions (requires Docker)
./tests/test-linux.sh all
# Or test individual distributions
./tests/test-linux.sh ubuntu
./tests/test-linux.sh debian
./tests/test-linux.sh fedora
./tests/test-linux.sh archFailure is not an option: If tests fail, the change is not complete. Fix the issues or document why the failure is acceptable (with maintainer approval).
- Code comments: Explain why, not what
- Function docs: Brief comment above each exported function
- Config docs: Document all config options in
.devboost.yaml.example - README: Keep up to date with new features
- CHANGELOG: Document all user-facing changes
- Never execute user input: All user input should be validated
- Use absolute paths: When possible, use absolute paths for security
- Sanitize paths: Validate file paths before operations
- Backup before modify: Always backup files before modifying
- Principle of least privilege: Don't require sudo unless necessary
- OS detection: Use
db_detect_osand checkDB_OS - Package managers: Use
db_install_packagesabstraction - Path differences: Handle macOS (
/opt/homebrew) vs Linux paths - Test on both: When possible, test on macOS and Linux
- Minimize external calls: Cache results when appropriate
- Batch operations: Group similar operations together
- Lazy evaluation: Only do work when needed
- Efficient checks: Use
command -vnotwhich,test -fnotls
- Create
modules/module_foo.sh - Implement
db_module_foo_register() - Implement
db_module_foo_plan()(check enable flag) - Implement
db_module_foo_apply()(idempotent, use helpers) - Add to
build.sh(file inclusion + registration) - Add config options to
.devboost.yaml.example - Test:
./build.sh && ./devboost.sh plan - Test:
./devboost.sh apply(idempotent) - Update README if user-facing
- Update CHANGELOG
- Commit with CBEAMS style
Check if enabled:
local enable=$(db_yaml_get '.module.enable' 'true')
if [[ "$enable" != "true" ]]; then
return 0
fiWrite file with backup:
local content="..."
db_write_file "$HOME/.file" "$content"Inject block into existing file:
local block="..."
db_upsert_block "$HOME/.file" "# start marker" "# end marker" "$block"Check command exists:
if ! db_command_exists tool; then
db_log_warn "tool not found, skipping"
return 0
fiRespect dry-run:
if [[ "${DB_DRY_RUN:-false}" == "true" ]]; then
db_log_info "Would do X"
return 0
fi
# Do actual workIf you're unsure about implementation details:
- Check existing modules for patterns
- Review core framework functions in
core/ - Test your changes thoroughly
- Ask for review if needed
Remember: Ease of use > Performance > Cleverness