Agent Skill
2/7/2026

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.

J
jasonkuhrt
1GitHub Stars
1Views
npx skills add jasonkuhrt/dotfiles

SKILL.md

Namecreating-adts
DescriptionThis 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 tools
  • 02-homebrew.sh — Homebrew
  • 03-brew-bundle.shbrew bundle from home/Brewfile (re-runs on Brewfile change)

After scripts configure the system:

  • 04-node-toolchain.sh — Node.js via fnm
  • 05-npm-globals.sh — global npm packages from home/npm/global-packages.txt
  • 06-fisher.sh — Fisher plugin manager for fish
  • 07-fisher-plugins.sh — fish plugins from home/.config/fish/fish_plugins
  • 08-macos-defaults.sh — keyboard repeat, trackpad speed, Finder preferences
  • 09-dock-apps.sh — Dock layout from home/dock/apps.txt
  • 11-dprint-update.sh — dprint formatter plugins
  • 12-skills-sync.sh — Claude Code skill installation
  • 13-git-ssh.sh — SSH key for GitHub
  • 14-neovim-plugins.sh — Neovim plugin install via lazy.nvim
  • 19-agentsview.sh — pinned agentsview CLI 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
Skills Info
Original Name:creating-adtsAuthor:jasonkuhrt