Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Summary

<!-- Show the smallest control-flow sketch or file tree explaining the change. -->

## Evidence

- **Before:**
**After:**
- [ ] `bash scripts/check.sh` passes; CI covers Linux and macOS.
- [ ] README, CHANGELOG, VERSION, and relevant lessons in learnings/dump.md align.
- [ ] Hardware/manual checks and limitations are recorded when applicable.

## Merge Danger

**Door:** <!-- one-way or two-way -->
**Blast Radius:**
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

on:
pull_request:
push:
branches: [main]
workflow_dispatch:
workflow_call:

permissions:
contents: read

jobs:
regression:
name: Regression (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Linux test dependencies
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y jq zsh
- name: Run syntax, documentation, release, and regression checks
shell: bash
run: /bin/bash scripts/check.sh

lint:
name: Shell and workflow lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install linters
run: sudo apt-get update && sudo apt-get install -y shellcheck zsh
- name: Lint shell scripts
run: python3 scripts/check-repo.py --lint
- name: Install pinned actionlint
env:
GOBIN: ${{ runner.temp }}/actionlint-bin
run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.7
- name: Validate GitHub workflows
run: '"$RUNNER_TEMP/actionlint-bin/actionlint" -color'
60 changes: 60 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Release

on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: Stable SemVer tag matching VERSION (for example v0.1.0)
required: true
default: v0.1.0
type: string

permissions:
contents: read

concurrency:
group: release-${{ github.event.inputs.version || github.ref_name }}
cancel-in-progress: false

jobs:
checks:
uses: ./.github/workflows/ci.yml

publish:
name: Publish validated release
needs: checks
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Validate version, notes, and main ancestry
env:
RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }}
run: |
if [[ "$GITHUB_EVENT_NAME" == workflow_dispatch && "$GITHUB_REF" != refs/heads/main ]]; then
echo 'Dispatch releases from main only.' >&2
exit 1
fi
git fetch origin main
git merge-base --is-ancestor "$GITHUB_SHA" origin/main
python3 scripts/release.py --tag "$RELEASE_TAG" --notes "$RUNNER_TEMP/release-notes.md"
if git show-ref --verify --quiet "refs/tags/$RELEASE_TAG"; then
test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$GITHUB_SHA"
fi
- name: Publish GitHub release (existing releases are never replaced)
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }}
run: |
gh release create "$RELEASE_TAG" \
--target "$GITHUB_SHA" \
--title "$RELEASE_TAG" \
--notes-file "$RUNNER_TEMP/release-notes.md" \
--latest
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Thumbs.db
*.tmp
*.bak
*.backup
__pycache__/
*.pyc

# Local planning/PR notes
docs/plans/
Expand Down
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Repository workflow

Before changing code or documentation, read [learnings/dump.md](learnings/dump.md),
[README.md](README.md), and the latest entries in [CHANGELOG.md](CHANGELOG.md).
Use the lessons relevant to the files being changed.

- Add a regression test for changed behavior. Run `bash scripts/check.sh` before
submitting; report any checks or hardware verification you could not perform.
- Keep README usage, CHANGELOG user-visible changes, and VERSION aligned in the
same PR. Released changelog entries are historical; add new work to Unreleased.
- Update `learnings/dump.md` when a failure reveals a reusable lesson: record the
symptom, cause, prevention, and test or evidence. Revise obsolete guidance.
- Keep macOS scripts compatible with system Bash 3.2. Test authentication using
mocks; privileged operations and package updates require explicit human testing.
- Follow the release procedure in README; publish only validated main commits.
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.0] - 2026-10-19

### Added
- Opt-in `update-all --setup-touchid` using the local `mo` helper, with active PAM
checks, repeat-safe setup, and verification; package updates are not run by setup.
- Foreground macOS sudo validation before interactive Homebrew updates, reusing
cached credentials and native Touch ID/password fallback; `--no-auth` opt-out.
- Linux/macOS PR and main CI: shell syntax, ShellCheck errors, workflow lint,
maintained documentation links, release metadata, and existing/new regressions.
- Mocked authentication/setup, package-manager isolation, cask-ignore, timeout,
and release validation tests; shared `scripts/check.sh` runner.
- Stable SemVer `VERSION` and validated main-only GitHub release workflow for
`v0.1.0`, using the changelog for notes and read-only PR workflow permissions.
- Living `learnings/dump.md`, root agent guidance, and PR documentation checklist.
- Main branch protection requiring all three CI checks and up-to-date branches,
including administrator enforcement (configured in GitHub, not by workflows).

### Fixed
- Update-all's test sandbox intercepts the actual `cargo install-update` dispatcher,
isolates inherited Cargo/Rustup directories, and tests a deliberately broken
source guard without allowing real package updates.

### Changed
- Sourcing `scripts/update-all.sh` now exposes functions without starting updates.
- Authentication failure stops updates before they start. CI, noninteractive
sessions, non-macOS systems, and non-Homebrew-only modes skip upfront sudo.
- README documents setup, password fallback, limitations, rollback, regression
checks, branch protection requirements, and the post-merge release procedure.
- Physical Mac acceptance recorded from the repository owner: Touch ID succeeded
without a password prompt; release authorized on 2026-10-19.
- This initial SemVer release includes the previously unreleased changes below;
older dated entries are retained as historical notes.

### Changed
- Skills consolidated under a top-level `skills/` directory (#129)
- Moved `project-bootstrap` and `code-review-checklist` from `ai-prompts/skills/` to `skills/`
Expand Down
124 changes: 122 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

A modular, maintainable approach to managing shell configurations and development environment across multiple macOS machines.

**Current version: 0.1.0** — see [CHANGELOG.md](CHANGELOG.md) for release notes.

## Overview

This repository contains my personal dotfiles configuration, designed with modularity and maintainability in mind. The configuration is split into logical components, making it easy to understand, modify, and sync across different workstations.
Expand All @@ -14,6 +16,7 @@ This repository contains my personal dotfiles configuration, designed with modul
- **Cross-Machine Sync**: Easy synchronization between personal and work computers
- **Automated Setup**: One-command installation for new machines
- **Package Management**: Centralized Homebrew package management via Brewfile
- **Touch ID for Updates**: Opt-in macOS sudo setup, with native password fallback
- **Version Control**: All configurations tracked in Git for easy rollback and history
- **Oh My Zsh Integration**: Pre-configured with useful plugins and Powerlevel10k theme
- **Neovim Configuration**: Modern IDE-like setup with LSP, Treesitter, and productivity plugins
Expand Down Expand Up @@ -397,6 +400,42 @@ brew bundle dump --force --file=~/dotfiles/Brewfile
~/dotfiles/scripts/update-all.sh --ai-tools-only
```

#### Touch ID and password authentication (macOS)

Enable sudo Touch ID once from your terminal:

```bash
~/dotfiles/scripts/update-all.sh --setup-touchid
```

Authorize setup when prompted; macOS may accept Touch ID or request your password.
The command checks existing configuration, enables Touch ID, and verifies the
result without updating packages.
Repeating setup leaves Touch ID enabled. It requires a current local `mo` helper;
missing-helper errors include setup guidance. This is opt-in because it
changes system-wide sudo authentication.

On subsequent interactive macOS runs that include Homebrew, `update-all` validates
sudo **before** captured or timed installer output. A valid sudo timestamp is
reused; otherwise macOS offers Touch ID when available and retains its normal
password fallback (including unavailable hardware, lockout, or cancelled Touch ID).
No passwords are read or stored by this script, and Homebrew never runs as root.
Without the helper or configured Touch ID, normal password authentication still works.

`--npm-only`, `--pip-only`, and `--ai-tools-only` do not pre-authenticate. CI and
redirected/noninteractive runs skip the upfront prompt; downstream installers can
still require credentials. Use `--no-auth` to opt out of pre-authentication for a
Homebrew run. No sudo keepalive is installed; long updates may prompt again after
the normal sudo timestamp expires. Authentication failure stops the run before
updates start.

Touch ID requires supported hardware, enrolled fingerprints, and a compatible
local terminal session. macOS/PAM decides availability; this does not replace
password dialogs owned by apps or guarantee biometrics over SSH, in tmux, or with
a closed laptop lid. Use your password when Touch ID is unavailable. To undo the
system setting, run `mo touchid` and choose Disable; reverting this PR alone does
not undo previously enabled PAM settings.

### Updating AI/LLM CLI Tools

The dotfiles include automated updates for AI CLI tools like `gemini-cli`, `codex`, `orca`, `headroom`, and `claude-code`, as well as the supporting `rtk` and `gh-stack` CLI extensions (see [docs/ai-tools/INSTALLATION.md](docs/ai-tools/INSTALLATION.md) for install/update/uninstall details):
Expand All @@ -411,8 +450,10 @@ The dotfiles include automated updates for AI CLI tools like `gemini-cli`, `code
# Update only AI tools (alternative method)
source ~/dotfiles/scripts/update-all.sh && update_ai_tools

# Skip AI tools during system update
UPDATE_AI_TOOLS=false ~/dotfiles/scripts/update-all.sh
# Source without starting updates; disable a manager before calling main
source ~/dotfiles/scripts/update-all.sh
UPDATE_AI_TOOLS=false
main

# Manual update of individual tools
brew upgrade gemini-cli codex # Formula-based tools
Expand All @@ -426,6 +467,85 @@ brew upgrade --cask claude-code # Cask-based tools
- Integrated with package manager isolation flags
- Non-blocking: failures on one tool don't prevent updates to others

## Contributing and regression checks

Read [AGENTS.md](AGENTS.md) and [learnings/dump.md](learnings/dump.md) before changes.
Keep this README (usage), CHANGELOG (user-visible history), and the lessons (causes
and prevention) current in the same PR. Add behavior tests for each regression.

```bash
# macOS test dependencies (Linux CI installs jq and zsh with apt)
brew install jq
bash scripts/check.sh

# Optional local lint; CI always runs it
brew install shellcheck zsh
python3 scripts/check-repo.py --lint
```

Python 3, Git, jq, and Zsh are required. The runner checks repository shell syntax,
local links in the four maintained docs, VERSION/README/changelog consistency,
mocked update/authentication and release validation tests, and existing statusline
and skill tests. ShellCheck errors and actionlint workflow validation run in CI.
External links and the entire historical documentation library are not link-checked.
Every PR (including forks) and main push runs Linux and macOS checks with system
`/bin/bash`, including macOS Bash 3.2. Tests never invoke real sudo, setup helpers,
or package updates. Hosted runners cannot verify a physical fingerprint sensor.

Before merging an authentication change, manually check on a Touch ID Mac:

1. Run `update-all --setup-touchid` and authorize the change if requested.
2. Run `sudo -k`, then `update-all --brew-only --no-cleanup`; confirm Touch ID is
offered before package operations. **This command performs real updates.**
3. Repeat with Touch ID cancelled/unavailable and confirm password fallback.
4. Repeat setup; confirm it leaves Touch ID enabled. Test `--no-auth` as needed.

For authentication-only testing without package updates, replace the update
command in steps 2–3 with:

```bash
bash -c 'source "$HOME/dotfiles/scripts/update-all.sh"; prepare_update_auth'
```

Record the tested commit, macOS version, terminal, and each checklist outcome in
the PR. Never include passwords or fingerprint data. Passing mocked/hosted tests
does not satisfy this physical authentication gate.

The repository's main branch protection requires `Regression (ubuntu-latest)`,
`Regression (macos-latest)`, and `Shell and workflow lint`, with up-to-date branches
and enforcement for administrators. Approval requirements are unchanged. These
are GitHub repository settings, not applied by workflows; configure the same
requirements separately for forks or a replacement repository.

## Releases and semantic versioning

`VERSION` is the stable release source of truth (currently `0.1.0`); tags use
`vX.Y.Z`. Before `1.0.0`, minor versions may introduce incompatible changes and
patch versions contain backward-compatible fixes. At/after `1.0.0`, use standard
SemVer: major for breaking changes, minor for features, patch for fixes.

**0.1.0 acceptance:** On 2026-10-19, the repository owner confirmed local physical
Mac testing: Touch ID authentication succeeded without requesting a password, and
release was authorized. This is owner-reported hardware evidence, separate from
automated CI. See [PR #135](https://github.com/iamgerwin/dotfiles/pull/135) and
[releases](https://github.com/iamgerwin/dotfiles/releases) for the acceptance and
publication records.

1. Prepare a PR updating VERSION, the current version above, and a dated changelog
section. Keep an Unreleased section for future work. Update relevant lessons.
2. Merge only after PR checks pass and any required hardware verification is done.
3. From **main**, dispatch the Release workflow with `v0.1.0` for this release:
`gh workflow run release.yml --ref main -f version=v0.1.0`.
Alternatively push a `vX.Y.Z` tag pointing to a validated main commit.
4. The pipeline reruns the full CI suite, checks SemVer, matching metadata and
main ancestry, then creates the tag (for dispatch) and GitHub release using
that version's changelog notes. GitHub provides source archives automatically.

Releases are never published from unmerged PR branches, existing tags must point
to the selected commit, and existing releases are not overwritten. Release jobs
alone receive `contents: write`; PR checks have read-only permissions and no
secrets. Publish tags and releases only after the corresponding PR has merged.

## Customization

### Machine-Specific Settings
Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
Loading
Loading