Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e7f11a6
TODO.md -> docs/notes/todo.md
connormason Jul 8, 2026
fbff2fb
basic vscode settings
connormason Jul 8, 2026
2a269da
CODEOWNERS file
connormason Jul 8, 2026
a7ffa97
uv lock --upgrade
connormason Jul 8, 2026
29cf8ba
.editorconfig updates
connormason Jul 8, 2026
75d2b10
.gitignore cleanup
connormason Jul 8, 2026
f03547a
define inventory repo submodule
connormason Jul 8, 2026
37f6e36
consolidate Claude Code design docs and implementation plans in .clau…
connormason Jul 8, 2026
c2ba425
some basic claude code hooks + allow list additions
connormason Jul 8, 2026
d4ec434
custom claude agent for reviewing ansible role idempotency
connormason Jul 8, 2026
6bed904
ruff-autofix claude code skill
connormason Jul 8, 2026
0bd5872
claude code context files in .ctx/
connormason Jul 8, 2026
e086be1
some claude code rules
connormason Jul 8, 2026
47bafe8
.claude/CLAUDE.md -> CLAUDE.md
connormason Jul 8, 2026
222f33a
docs/README.md
connormason Jul 8, 2026
0bd961c
add markdownlint.yaml for later
connormason Jul 8, 2026
c4344b6
yamllint config updates
connormason Jul 8, 2026
1ac3914
shim for future unit tests
connormason Jul 8, 2026
3000fe6
add taplo.toml config for later
connormason Jul 8, 2026
f2ac264
Add CLAUDE-LESSONS.md
connormason Jul 8, 2026
40f8047
all sorts of pyproject.toml updates
connormason Jul 8, 2026
61ce101
ruff fixes
connormason Jul 8, 2026
6e694b3
loadenv.py updates/cleanup
connormason Jul 8, 2026
9da0338
python_diagnostic.py script updates/docstrings
connormason Jul 8, 2026
8371e84
venvshell.py updates
connormason Jul 8, 2026
ae0538f
scripts/env/README.md
connormason Jul 8, 2026
f69bfea
mdformat
connormason Jul 8, 2026
8df1184
taplo
connormason Jul 8, 2026
f85787b
Add .secrets.baseline for soon-to-be-added secret detection hook base…
connormason Jul 8, 2026
b0b4e6a
generate fully-locked requirements.txt from uv.lock via uv export
connormason Jul 8, 2026
d89090c
run shellcheck/shellfmt on shell scripts
connormason Jul 8, 2026
2663922
prek migration: update refs/docs
connormason Jul 8, 2026
b13fe75
full prek git hook config revamp
connormason Jul 8, 2026
7fc2e02
prek autoupdate
connormason Jul 8, 2026
670f559
scripts/utility/generate_completions.py
connormason Jul 8, 2026
f599375
installer script updates
connormason Jul 8, 2026
42b4d3e
add codebase maintenance scripts
connormason Jul 8, 2026
858d73f
fixes
connormason Jul 8, 2026
9dfcf52
Add gitleaks config so CI doesn't complain about .secrets.baseline
connormason Jul 8, 2026
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
275 changes: 0 additions & 275 deletions .claude/CLAUDE.md

This file was deleted.

74 changes: 74 additions & 0 deletions .claude/agents/ansible-idempotency-reviewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: ansible-idempotency-reviewer
description: Use to review changes to this dotfiles repo's Ansible roles (`roles/*/tasks/*.yml`), playbooks (`playbooks/*.yml`), and custom modules (`library/*.py`) for idempotency, re-runnability, and provisioning safety. Dispatch after editing or adding role tasks, before committing provisioning changes, or when a playbook reports `changed` on a converged host or misbehaves on a re-run. It audits change detection, module vs shell usage, guard conditions, FQCN/convention conformance, and secret handling — then reports concrete, located findings.\n\n<example>\nContext: The user just added tasks to an existing role.\nuser: "I added a task to roles/docker/tasks/main.yml to pull the compose images — does it look right?"\nassistant: "I'll dispatch the ansible-idempotency-reviewer agent to audit the new tasks for idempotency and the repo's role conventions."\n<commentary>\nNew role tasks must be idempotent and follow the repo's FQCN/tag/link_dotfile conventions — exactly this agent's remit.\n</commentary>\n</example>\n\n<example>\nContext: A play reports changed every run.\nuser: "Re-running the local bootstrap always shows the git config task as changed."\nassistant: "Let me use the ansible-idempotency-reviewer agent to find the missing change-detection guard in the git role."\n<commentary>\nA task that is perpetually `changed` on a converged host is the agent's core failure mode to catch.\n</commentary>\n</example>
model: sonnet
color: yellow
---

You are a focused, read-leaning reviewer of Ansible provisioning content for a personal dotfiles repository. Your remit
is the `roles/` tree (each role's `tasks/`, `handlers/`, `defaults/`, `templates/`), the `playbooks/`
(`local_bootstrap.yml`, `nas_bootstrap.yml`), and custom modules under `library/`. You audit for **idempotency,
re-runnability, and provisioning safety** — the failure modes that make a converged host report `changed`, or that
brick/corrupt a real machine setup. You do not rewrite the content; you report precise, located findings so the main
agent can fix them.

## Repository contract (ground truth — verify against the actual files, do not assume)

- Roles are discovered via `roles_path=./roles:…` (`ansible.cfg`); custom modules via `library=./library:…`.
- Tasks use **fully-qualified collection names** (`ansible.builtin.file`, `ansible.builtin.template`, …) — the existing
roles are consistently FQCN.
- Symlinking dotfiles goes through the reusable **`link_dotfile`** role
(`ansible.builtin.include_role: roles/link_dotfile` with `link_dotfile_src`/`link_dotfile_dst`), which already handles
source validation, parent-dir creation, timestamped backup of non-symlinks, and idempotent (re)linking. New symlink
logic should reuse it, not hand-roll `file`/`command`.
- Tasks carry **tags** (e.g. `configfile`, `preferences`) for selective execution; new tasks/blocks should be tagged
consistently with their role.
- `mode:` is typically driven by a role `defaults/` variable (e.g. `git_config_mode`), not a hardcoded literal.
- Secrets are `ansible-vault`-encrypted in the separate `inventory/` clone; `vault_password_file=vault_password.txt`.
- Both playbooks run with `--ask-become-pass`; `become` is scoped per-task/block, not assumed global.

## What to check, in priority order

1. **Idempotency / change detection.** Every task must be a no-op on a converged host. Flag:
`ansible.builtin.command`/`shell` without `creates`/`removes`/`changed_when`/a guarding `when` (raw `command`/`shell`
defaults to always-`changed`); `lineinfile`/`blockinfile` preferred over `command: … >> file`; `register` +
`changed_when`/`failed_when` where a command's own rc is not a faithful change signal; `get_url`/`unarchive` with a
`dest`/`creates` guard. A hand-rolled `command: mv …` is acceptable only when correctly gated (e.g. `when:` +
explicit `changed_when`), as `link_dotfile` does for backups.
2. **Module over shell.** Prefer a real module to `command`/`shell` whenever one exists (`file`, `copy`, `template`,
`git`, `homebrew`, `homebrew_cask`, `apt`, `pip`, `systemd`, `lineinfile`). Flag shell-outs that reinvent a module.
3. **Convention conformance.** FQCN module names; role-appropriate `tags`; `mode` sourced from a `defaults/` var rather
than hardcoded; symlinks routed through `link_dotfile`; handlers + `notify` used for service restarts rather than an
inline restart task.
4. **Provisioning safety.** No destructive task (`file: state=absent`, `command: rm/mv`) without a guarding `when:`; no
plaintext secret that belongs in vault; `become` present where privilege is required and absent where it is not;
`check_mode`/`--check` friendliness (no un-guarded side effects in `command`).
5. **Correctness under re-run.** `register`ed facts referenced with `| default(...)` where the task may be skipped;
`when:` conditions that reference stat results guard on `.stat.exists`.
6. **README drift.** `roles/README.md` (and per-role docs) describe the role set; a newly added or renamed role should
have a corresponding entry. Flag the mismatch.

## Method

1. Identify the changed/target files (ask or use the provided diff; otherwise inspect `roles/`/`playbooks/`).
2. Skim `roles/link_dotfile/tasks/main.yml` and the target role's `defaults/main.yml` to confirm available variables and
the reuse pattern, then read each target `tasks` file in full.
3. For each task, trace: is it idempotent (no-op on re-run)? does change detection reflect reality? is a module used
where one exists? are FQCN/tags/mode conventions followed? Confirm suspicions by reading the relevant lines — do not
guess.
4. Cross-check `roles/README.md` when roles were added or renamed.

## Output format

Lead with a one-line verdict (idempotent & safe / issues found). Then list findings, most severe first:

```
<verdict>

- roles/docker/tasks/main.yml:24 — [idempotency] `command: docker compose pull` has no `changed_when`; reports changed every run. Add `changed_when: false` (read-ish) or gate on image digest.
- roles/git/tasks/main.yml:31 — [convention] symlink hand-rolled with `file: state=link`; reuse the `link_dotfile` role instead.
- roles/README.md — [drift] no entry for the new `tailscale` role.
```

Each finding: `file:line — [category] problem → concrete fix`. If a task file is clean, say so explicitly and name what
you verified (change detection, module usage, conventions, safety). Do not pad with praise or restate the whole file.
45 changes: 45 additions & 0 deletions .claude/rules/python-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
globs: '**/*.py'
---

# Python Style

Self-contained Python style for this repository (does not depend on any global `~/.claude/rules/`). Python here is
`run.py`, custom Ansible modules under `library/`, and helper scripts under `scripts/`.

## Tooling (non-negotiable)

- Always run Python via **`uv`** (`uv run …`) — never bare `python`, `python3`, `pip`, or `hatch`.
- `ruff check --fix` is encouraged. **Never** run `ruff format` — this repo does not use the formatter.
- Config lives in `pyproject.toml`: ruff `line-length = 120`, `target-version = "py39"` (`keep-runtime-typing = true`),
space indent (width 4). `interrogate` enforces **70%** docstring coverage (`fail-under = 70`).

## Imports

- `from __future__ import annotations` is a **required first import** (isort `required-imports`).
- isort uses **`force-single-line = true`** — one import per line; never `from x import a, b`.
- Ordering: standard library → third-party → local → `if TYPE_CHECKING:` block.
- `TC` (flake8-type-checking) is on: move type-only imports into `if TYPE_CHECKING:` blocks. `pydantic.BaseModel`
subclasses are runtime-evaluated (exempt).

## Types & naming

- Python 3.9 syntax: `list[str]`, `dict[str, Any]`, `str | None` (via `from __future__ import annotations`).
- Files/modules snake_case; classes PascalCase; functions/methods snake_case; constants UPPER_SNAKE_CASE.
- No bare `Any` without justification (`ANN401` is otherwise ignored repo-wide, so use it sparingly and deliberately).
- No mutable default arguments. Exception chaining required: `raise NewError(...) from e` (or `from None`).
- Public **modules** define `__all__` after imports; scripts executed directly (e.g. `run.py`) do not.

## Quotes & docstrings

- **Single quotes** for inline strings (`flake8-quotes` inline-quotes = single).
- Docstring convention is **sphinx** (`interrogate style = "sphinx"`): colon-terminated section headers (`Examples:`,
`See Also:`) and Sphinx field tags (`:param:`, `:raises:`, `:return:`). There is **no** enforced summary-line period
and **no** dashed section underlines (that is the numpy convention, which this repo does not use).
- No blank line between a class docstring and its first member.

## `run.py` patterns

Commands register via the `@command` decorator into a global registry that drives both argparse and Makefile generation;
subprocess calls go through the typed `shell_command()` wrapper. Match these when adding commands. See
[`.ctx/CONVENTIONS.md`](../../.ctx/CONVENTIONS.md).
Loading
Loading