creating-adts
This skill should be used when the user asks to "create an ADT", "scaffold a discriminated union", "add a tagged union", "create Effect Schema union", or needs to create algebraic data types following the ADT library pattern with proper member files and barrel exports.
SKILL.md
| Name | creating-adts |
| Description | This skill should be used when the user asks to "create an ADT", "scaffold a discriminated union", "add a tagged union", "create Effect Schema union", or needs to create algebraic data types following the ADT library pattern with proper member files and barrel exports. |
dotfiles
Personal macOS system configuration deployed by dotctl. Everything lives in a single home/ source tree. The directory layout is the configuration — no manifest to maintain, no filename prefixes to learn.
New Machine Setup
git clone git@github.com:jasonkuhrt/dotfiles.git ~/projects/jasonkuhrt/dotfiles
cd ~/projects/jasonkuhrt/dotfiles
bun install
just up
Paste your age private key from your password manager into ~/.config/dotctl/age-key.txt before the first just up if you have encrypted secrets. Complete remaining manual steps in docs/manual-setup.md.
Daily Use
just up # full deploy (scripts + symlinks + manifest)
just edit ~/.config/ghostty/config # open the source file for any managed target
just status # deployment health
just doctor # 55 invariant checks
just explain ~/.config/dprint # how a target is managed
just up is convergent and idempotent. Run it whenever you pull, edit config, or feel uncertain.
How It Works
dotctl walks home/ and creates symlinks into $HOME. Three conventions determine what happens to each entry.
Directory symlinks. A directory without a .spread marker is symlinked whole. home/.ssh/ becomes ~/.ssh -> repo/home/.ssh. Edits inside the target write directly into the repo.
Spread directories. A .spread file inside a directory tells dotctl to create the directory as a real folder in $HOME and symlink each child individually. This is how home/.config/ works — dotctl owns starship.toml and fish/ without claiming all of ~/.config/. Spread is recursive: a spread directory can contain both spread subdirectories (with their own .spread) and whole-directory symlinks (without one).
Use .spread when an application writes runtime state alongside your config (logs, caches, sockets). Without it, those writes go into the repo.
Files. Regular files at the home/ root or inside spread directories become individual symlinks.
Special conventions
Encrypted files (.age). home/.aws/credentials.age is decrypted with age at deploy time and written as ~/.aws/credentials with mode 0600. The age identity lives at ~/.config/dotctl/age-key.txt. Keep it in your password manager — it is the only secret you need to transfer between machines.
Modify scripts (.modify sidecars). A file F with a companion F.modify script uses merge deployment. The sidecar receives the source path via $DOTCTL_SOURCE, writes merged output to stdout. This lets you combine managed configuration with machine-local values.
Skipped entries. Brewfile, dock/, and npm/ live in home/ but are not deployed — they exist so setup scripts can reference them. Additional skips are configurable via homeRootSkip in dotctl.config.json.
Source Tree
home/
.aws/ # dir-symlink (contains credentials.age)
.agentsview/ # spread (shared config, local DB/uploads/runtime keys)
.spread
.bookmarks/ # dir-symlink (bookmarks sync config)
.claude/ # spread (skills, rules, hooks — runtime writes transcripts/)
.spread
.config/ # spread (owns specific app configs, not all of ~/.config)
.spread
fish/ # spread (config + fish_variables; fish writes completions/)
.spread
gh/ # spread
.spread
ghostty/ # dir-symlink (pure config)
starship.toml # file symlink
...
.gitconfig # file symlink
.ssh/ # spread (config + known_hosts; SSH writes sockets/)
.spread
Library/ # spread
.spread
LaunchAgents/ # spread (plist files)
.spread
Brewfile # skipped — read by brew-bundle setup script
dock/ # skipped — read by dock-apps setup script
npm/ # skipped — read by npm-globals setup script
Setup Scripts
Scripts in scripts/setup/ run during just up in two phases: before/ (prerequisites) and after/ (configuration that depends on deployed files). Each phase has once/ (first run only) and onchange/ (re-runs when watched files change) subdirectories.
Before scripts install foundational dependencies:
01-xcode-cli.sh— Xcode command-line tools02-homebrew.sh— Homebrew03-brew-bundle.sh—brew bundlefromhome/Brewfile(re-runs on Brewfile change)
After scripts configure the system:
04-node-toolchain.sh— Node.js via fnm05-npm-globals.sh— global npm packages fromhome/npm/global-packages.txt06-fisher.sh— Fisher plugin manager for fish07-fisher-plugins.sh— fish plugins fromhome/.config/fish/fish_plugins08-macos-defaults.sh— keyboard repeat, trackpad speed, Finder preferences09-dock-apps.sh— Dock layout fromhome/dock/apps.txt11-dprint-update.sh— dprint formatter plugins12-skills-sync.sh— Claude Code skill installation13-git-ssh.sh— SSH key for GitHub14-neovim-plugins.sh— Neovim plugin install via lazy.nvim19-agentsview.sh— pinnedagentsviewCLI install into~/.local/share/agentsview
Heal Agent
A macOS launchd agent runs dotctl heal every 5 minutes. Some applications replace symlinks with regular files during atomic saves — the healer detects this drift and restores the symlinks. just up installs and loads the agent automatically.
Configuration
dotctl.config.json in the repo root:
{
"sourceDir": "home",
"scriptsDir": "scripts/setup",
"stateDir": "~/.local/state/dotfiles-symlink",
"homeRootSkip": ["Brewfile", "dock", "npm"],
"age": {
"identity": "~/.config/dotctl/age-key.txt",
"recipient": "age1..."
},
"heal": {
"label": "com.jasonkuhrt.dotfiles-symlink-heal",
"intervalSeconds": 300
}
}
Further Reading
- Karabiner — mental model, failure layers, and recovery workflows
- Symlink platform — lane model, capture policy, runtime state
- CLI tools — installed tools and shell abbreviations
- Worktrunk — git worktree manager for parallel AI agents
- Neovim — LazyExtras, AI stack, plugin decisions
- Known limitations — workarounds and manual intervention
- Decisions — architecture decision records