├── .oh-my-zsh └── .oh-my-zsh │ └── custom │ └── plugins │ └── zsh-you-should-use │ ├── .gitignore │ ├── zsh-you-should-use.plugin.zsh │ ├── img │ ├── git.png │ ├── alias.png │ └── global.png │ ├── ISSUE_TEMPLATE.md │ ├── tests │ ├── test_buffer.zunit │ ├── test_global_aliases.zunit │ ├── test_git_aliases.zunit │ ├── test_you_should_use.zunit │ └── test_aliases.zunit │ ├── CHANGELOG.md │ ├── you-should-use.plugin.zsh │ └── README.rst ├── .gitignore ├── zsh ├── .pyenv_setup.sh ├── .nvm_setup ├── .zshrc └── .zsh_profile ├── opencode └── .config │ └── opencode │ └── opencode.json ├── gnupg └── .gnupg │ ├── gpg-agent.conf │ └── gpg.conf ├── tmux └── .tmux.conf.local ├── ghostty └── Library │ └── Application Support │ └── com.mitchellh.ghostty │ └── config ├── .changeset ├── config.json └── README.md ├── claude └── .claude │ ├── commands │ └── pr.md │ ├── settings.json │ ├── skills │ ├── expectations │ │ └── SKILL.md │ ├── refactoring │ │ └── SKILL.md │ ├── planning │ │ └── SKILL.md │ ├── tdd │ │ └── SKILL.md │ ├── react-testing │ │ └── SKILL.md │ └── testing │ │ └── SKILL.md │ ├── CLAUDE.md │ └── agents │ ├── progress-guardian.md │ ├── use-case-data-patterns.md │ ├── tdd-guardian.md │ ├── README.md │ └── learn.md ├── package.json ├── install.sh ├── LICENSE ├── .github └── workflows │ └── release.yml ├── alacritty └── .alacritty.toml ├── karabiner └── .config │ └── karabiner │ └── karabiner.json ├── MIGRATION.md ├── CONTRIBUTING.md ├── install-claude.sh └── zellij └── .config └── zellij └── config.kdl /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | *.swp 3 | .venv 4 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/zsh-you-should-use.plugin.zsh: -------------------------------------------------------------------------------- 1 | you-should-use.plugin.zsh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Claude Code runtime settings (local only, at repository root) 2 | /.claude/ 3 | 4 | # macOS 5 | .DS_Store 6 | 7 | # Dependencies 8 | node_modules/ 9 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/img/git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citypaul/.dotfiles/HEAD/.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/img/git.png -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/img/alias.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citypaul/.dotfiles/HEAD/.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/img/alias.png -------------------------------------------------------------------------------- /zsh/.pyenv_setup.sh: -------------------------------------------------------------------------------- 1 | if [ ! -d "$HOME/pyenv" ]; then 2 | python3 -m venv "$HOME/pyenv" 3 | fi 4 | source "$HOME/pyenv/bin/activate" 5 | pip install --upgrade pip setuptools >/dev/null 2>&1 6 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/img/global.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/citypaul/.dotfiles/HEAD/.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/img/global.png -------------------------------------------------------------------------------- /opencode/.config/opencode/opencode.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://opencode.ai/config.json", 3 | "instructions": [ 4 | "~/.claude/CLAUDE.md", 5 | "~/.claude/skills/*/SKILL.md" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /gnupg/.gnupg/gpg-agent.conf: -------------------------------------------------------------------------------- 1 | # See: https://samuelsson.dev/sign-git-commits-on-github-with-gpg-in-macos/ 2 | pinentry-program /opt/homebrew/bin/pinentry-mac 3 | 4 | # Cache passphrase for 8 hours (28800 seconds) 5 | default-cache-ttl 28800 6 | max-cache-ttl 28800 7 | -------------------------------------------------------------------------------- /tmux/.tmux.conf.local: -------------------------------------------------------------------------------- 1 | set-option -g default-shell /bin/zsh 2 | 3 | # Copied the mouse stuff from this Gist: https://gist.github.com/simme/1297707 4 | # More on mouse support http://floriancrouzat.net/2010/07/run-tmux-with-mouse-support-in-mac-os-x-terminal-app/ 5 | -------------------------------------------------------------------------------- /ghostty/Library/Application Support/com.mitchellh.ghostty/config: -------------------------------------------------------------------------------- 1 | macos-non-native-fullscreen = true 2 | macos-titlebar-style = transparent 3 | window-save-state = never 4 | macos-auto-secure-input = true 5 | macos-secure-input-indication = true 6 | font-size = 18 7 | theme = tokyonight -------------------------------------------------------------------------------- /zsh/.nvm_setup: -------------------------------------------------------------------------------- 1 | # setup nvm 2 | export NVM_DIR="$HOME/.nvm" 3 | [ -s "$(brew --prefix)/opt/nvm/nvm.sh" ] && \. "$(brew --prefix)/opt/nvm/nvm.sh" # This loads nvm 4 | [ -s "$(brew --prefix)/opt/nvm/etc/bash_completion.d/nvm" ] && \. "$(brew --prefix)/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion 5 | 6 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [], 11 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { 12 | "onlyUpdatePeerDependentsWhenOutOfRange": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue Details 2 | 3 | Please provide the following details when opening an issue: 4 | 5 | ## Operating System (uname -a) 6 | 7 | ## zsh version (zsh --version) 8 | 9 | ## you-should-use version (echo "$YSU_VERSION") 10 | 11 | ## How is zsh-you-should-use installed? 12 | 13 | - [ ] zplug 14 | - [ ] oh-my-zsh 15 | - [ ] Antigen 16 | - [ ] Other (please specify) 17 | 18 | ## Steps to reproduce the issue 19 | 20 | ## gist link to your zshrc 21 | -------------------------------------------------------------------------------- /claude/.claude/commands/pr.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Create a pull request following standards 3 | allowed-tools: Bash(git:*), Bash(gh:*) 4 | --- 5 | 6 | Current branch state: 7 | !`git log main..HEAD --oneline` 8 | 9 | Changes summary: 10 | !`git diff main...HEAD --stat` 11 | 12 | Create a PR with: 13 | 14 | ## Summary 15 | - 1-3 bullet points describing the changes 16 | - Focus on WHAT changed and WHY 17 | 18 | Note: No test plan section needed - TDD means tests are already written and passing. 19 | 20 | Use `gh pr create` with appropriate title and body. 21 | -------------------------------------------------------------------------------- /gnupg/.gnupg/gpg.conf: -------------------------------------------------------------------------------- 1 | # Use strong algorithms 2 | personal-cipher-preferences AES256 AES192 AES 3 | personal-digest-preferences SHA512 SHA384 SHA256 4 | personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed 5 | default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed 6 | 7 | # Use SHA512 for key signatures 8 | cert-digest-algo SHA512 9 | s2k-digest-algo SHA512 10 | s2k-cipher-algo AES256 11 | 12 | # Display preferences 13 | charset utf-8 14 | keyid-format 0xlong 15 | with-fingerprint 16 | 17 | # Minimize information leakage 18 | no-emit-version 19 | no-comments 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paulhammond/dotfiles", 3 | "version": "3.2.0", 4 | "private": true, 5 | "description": "Personal dotfiles including CLAUDE.md development framework and Claude Code agents", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/citypaul/.dotfiles.git" 9 | }, 10 | "keywords": [ 11 | "claude-code", 12 | "dotfiles", 13 | "tdd", 14 | "typescript", 15 | "development-guidelines" 16 | ], 17 | "author": "Paul Hammond", 18 | "license": "MIT", 19 | "scripts": { 20 | "changeset": "changeset", 21 | "version": "changeset version", 22 | "release": "changeset publish" 23 | }, 24 | "devDependencies": { 25 | "@changesets/cli": "^2.27.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GPG_AGENT_CONF=gnupg/.gnupg/gpg-agent.conf 4 | 5 | mkdir -p gnupg/.gnupg 6 | 7 | echo "# See: https://samuelsson.dev/sign-git-commits-on-github-with-gpg-in-macos/" >>$GPG_AGENT_CONF 8 | echo pinentry-program $(which pinentry-mac) >>$GPG_AGENT_CONF 9 | 10 | move_with_backup() { 11 | local src="$1" 12 | local backup="${src}.old" 13 | 14 | if [ -f "$src" ] && [ ! -f "$backup" ]; then 15 | mv "$src" "$backup" 16 | fi 17 | } 18 | 19 | FPATH=$FPATH:~/.zsh_autocomplete 20 | 21 | mkdir -p ~/.zsh_autocomplete 22 | zellij setup --generate-completion zsh >>~/.zsh_autocomplete/_zellij-completion 23 | 24 | move_with_backup ~/.zshrc 25 | 26 | move_with_backup "$HOME/Library/Application Support/com.mitchellh.ghostty/config" 27 | 28 | stow zsh tmux gnupg alacritty zellij .oh-my-zsh karabiner ghostty claude 29 | -------------------------------------------------------------------------------- /claude/.claude/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "opus", 3 | "enabledPlugins": { 4 | "feature-dev@claude-code-plugins": true, 5 | "frontend-design@claude-code-plugins": true, 6 | "hookify@claude-code-plugins": true, 7 | "learning-output-style@claude-code-plugins": true, 8 | "plugin-dev@claude-code-plugins": true, 9 | "security-guidance@claude-code-plugins": true 10 | }, 11 | "alwaysThinkingEnabled": true, 12 | "statusLine": { 13 | "type": "command", 14 | "command": "npx -y @owloops/claude-powerline --style=powerline" 15 | }, 16 | "hooks": { 17 | "PostToolUse": [ 18 | { 19 | "matcher": "Write|Edit", 20 | "hooks": [ 21 | { 22 | "type": "command", 23 | "command": "FILE=$(jq -r '.tool_input.file_path // empty'); if [[ \"$FILE\" == *.ts || \"$FILE\" == *.tsx ]]; then npx prettier --write \"$FILE\" 2>/dev/null && npx eslint --fix \"$FILE\" 2>/dev/null; fi; exit 0" 24 | } 25 | ] 26 | } 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Paul Hammond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/tests/test_buffer.zunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zunit 2 | 3 | @setup { 4 | load "../you-should-use.plugin.zsh" 5 | unset YSU_MESSAGE_POSITION 6 | _YSU_BUFFER="" 7 | } 8 | 9 | @teardown { 10 | rm -f output.txt 11 | } 12 | 13 | # We work around not being able to use `run` AND test variable values by redirecting 14 | # all output to a temporary file from which we can read. 15 | 16 | @test 'ysu - _write_ysu_buffer before' { 17 | YSU_MESSAGE_POSITION="before" 18 | export _YSU_BUFFER 19 | 20 | _write_ysu_buffer "hello world" 2> output.txt 21 | 22 | assert $state equals 0 23 | 24 | assert "$(< output.txt)" same_as "hello world" 25 | assert "$_YSU_BUFFER" is_empty 26 | } 27 | 28 | @test 'ysu - _write_ysu_buffer after' { 29 | YSU_MESSAGE_POSITION="after" 30 | export _YSU_BUFFER 31 | 32 | _write_ysu_buffer "hello world" 2> output.txt 33 | 34 | assert $state equals 0 35 | 36 | assert "$(< output.txt)" is_empty 37 | assert "$_YSU_BUFFER" same_as "hello world" 38 | } 39 | 40 | @test 'ysu - _write_ysu_buffer invalid' { 41 | YSU_MESSAGE_POSITION="invalid" 42 | export _YSU_BUFFER 43 | 44 | _write_ysu_buffer "" 2> output.txt 45 | 46 | assert $state equals 0 47 | 48 | expected="$(tput setaf 1)$(tput bold)Unknown value for YSU_MESSAGE_POSITION 'invalid'. " 49 | expected+="Expected value 'before' or 'after'$(tput sgr0)\n" 50 | 51 | assert "$(< output.txt)" same_as "$expected" 52 | assert "$_YSU_BUFFER" is_empty 53 | } 54 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/tests/test_global_aliases.zunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zunit 2 | 3 | @setup { 4 | load "../you-should-use.plugin.zsh" 5 | # Simplify format for tests 6 | YSU_MESSAGE_FORMAT='Found existing %alias_type for "%command". You should use: "%alias"' 7 | unset YSU_MESSAGE_POSITION 8 | } 9 | 10 | @teardown { 11 | } 12 | 13 | @test 'global aliases no match' { 14 | alias -g hw="Hello World!" 15 | run _check_global_aliases "echo 'hello'" 16 | 17 | assert "$output" is_empty 18 | assert $state equals 0 19 | } 20 | 21 | @test 'global aliases match' { 22 | alias -g NE="2>/dev/null" 23 | run _check_global_aliases "echo 'hello' 2>/dev/null" 24 | 25 | assert "$output" contains 'Found existing global alias for "2>/dev/null". You should use: "NE"' 26 | assert $state equals 0 27 | } 28 | 29 | @test 'test doesnt match substrings' { 30 | alias -g n="nvim" 31 | run _check_global_aliases "nvimkaowjqwe" 32 | 33 | assert "$output" is_empty 34 | assert $state equals 0 35 | } 36 | 37 | @test 'global aliases ignore on sudo' { 38 | alias -g NE="2>/dev/null" 39 | run _check_global_aliases "sudo echo 'hello' 2>/dev/null" 40 | 41 | assert "$output" is_empty 42 | assert $state equals 0 43 | } 44 | 45 | @test 'global aliases - does not report an ignored alias' { 46 | export YSU_MODE="ALL" 47 | export YSU_IGNORED_GLOBAL_ALIASES=("..." "NE") 48 | alias -g ...="../.." 49 | run _check_global_aliases "cd ../.." 50 | 51 | assert $output is_empty 52 | assert $state equals 0 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | 14 | jobs: 15 | release: 16 | name: Release 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Setup pnpm 25 | uses: pnpm/action-setup@v3 26 | with: 27 | version: 10 28 | 29 | - name: Setup Node.js 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: 20 33 | cache: 'pnpm' 34 | 35 | - name: Install Dependencies 36 | run: pnpm install --frozen-lockfile 37 | 38 | - name: Create Release Pull Request or Tag 39 | id: changesets 40 | uses: changesets/action@v1 41 | with: 42 | version: pnpm changeset version 43 | commit: 'chore: version packages' 44 | title: 'chore: version packages' 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | - name: Create Git Tag and GitHub Release 49 | if: steps.changesets.outputs.hasChangesets == 'false' 50 | run: | 51 | # Get the version from package.json 52 | VERSION=$(node -p "require('./package.json').version") 53 | TAG_NAME="v${VERSION}" 54 | 55 | # Check if tag already exists 56 | if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then 57 | echo "Tag $TAG_NAME already exists, skipping" 58 | exit 0 59 | fi 60 | 61 | # Create and push tag 62 | git tag -a "$TAG_NAME" -m "Release $TAG_NAME" 63 | git push origin "$TAG_NAME" 64 | 65 | # Create GitHub Release 66 | gh release create "$TAG_NAME" \ 67 | --title "$TAG_NAME" \ 68 | --notes "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details." 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | This project uses [Changesets](https://github.com/changesets/changesets) for versioning and changelog management. 4 | 5 | ## When to Create a Changeset 6 | 7 | Create a changeset whenever you make changes that users should know about: 8 | - New features 9 | - Breaking changes 10 | - Bug fixes 11 | - Documentation updates that change behavior 12 | 13 | ## How to Create a Changeset 14 | 15 | ```bash 16 | # Run the changeset CLI 17 | pnpm changeset 18 | 19 | # You'll be prompted to: 20 | # 1. Select the type of change (major, minor, patch) 21 | # 2. Write a summary of the change 22 | ``` 23 | 24 | This creates a markdown file in `.changeset/` describing your change. 25 | 26 | ## Changeset Types 27 | 28 | - **major**: Breaking changes (v2.0.0 → v3.0.0) 29 | - File structure changes 30 | - Import path changes 31 | - Removed features 32 | 33 | - **minor**: New features (v2.0.0 → v2.1.0) 34 | - New documentation sections 35 | - New agents 36 | - New install options 37 | 38 | - **patch**: Bug fixes (v2.0.0 → v2.0.1) 39 | - Typo fixes 40 | - Clarifications 41 | - Small improvements 42 | 43 | ## Release Process 44 | 45 | ### Automated (via GitHub Actions) 46 | 47 | When changesets are merged to `main`: 48 | 1. GitHub Action runs automatically 49 | 2. Creates/updates a "Version Packages" PR 50 | 3. When that PR is merged: 51 | - Versions are bumped 52 | - CHANGELOG.md is updated 53 | - Git tag is created 54 | - GitHub Release is published 55 | 56 | ### Manual 57 | 58 | ```bash 59 | # 1. Bump versions and update CHANGELOG 60 | pnpm version 61 | 62 | # 2. Commit the version bump 63 | git add . 64 | git commit -m "chore: version packages" 65 | 66 | # 3. Create git tag (GitHub Action handles this automatically) 67 | git tag v2.1.0 68 | git push --tags 69 | ``` 70 | 71 | ## Example Changeset 72 | 73 | ```markdown 74 | --- 75 | "@paulhammond/dotfiles": minor 76 | --- 77 | 78 | Add new TypeScript enforcement agent 79 | 80 | Added ts-enforcer agent that proactively guides TypeScript best practices 81 | and validates schema-first development. 82 | ``` 83 | 84 | ## For This Project 85 | 86 | Since this is not published to npm (`"private": true`), changesets are used solely for: 87 | - Semantic versioning 88 | - Changelog generation 89 | - Git tag creation 90 | - GitHub release automation 91 | 92 | The `changeset publish` command won't push to npm registry. 93 | -------------------------------------------------------------------------------- /alacritty/.alacritty.toml: -------------------------------------------------------------------------------- 1 | colors.draw_bold_text_with_bright_colors = false 2 | general.import = [ "~/.config/alacritty/themes/themes/night_owl.toml" ] 3 | 4 | [env] 5 | TERM = "xterm-256color" 6 | 7 | [window] 8 | option_as_alt = "Both" 9 | dynamic_padding = false 10 | opacity = 1 11 | blur = false 12 | decorations = "buttonless" 13 | title = "Alacritty" 14 | 15 | [window.padding] 16 | x = 6 17 | y = 6 18 | 19 | [window.class] 20 | instance = "Alacritty" 21 | general = "Alacritty" 22 | 23 | [scrolling] 24 | history = 5_000 25 | 26 | [font] 27 | size = 19 28 | 29 | [font.normal] 30 | family = "Hack Nerd Font" 31 | style = "Regular" 32 | 33 | [font.bold] 34 | family = "Hack Nerd Font" 35 | style = "Bold" 36 | 37 | [font.italic] 38 | family = "Hack Nerd Font" 39 | style = "Italic" 40 | 41 | [font.bold_italic] 42 | family = "Hack Nerd Font" 43 | style = "Bold Italic" 44 | 45 | [font.offset] 46 | x = 0 47 | y = 1 48 | 49 | [[keyboard.bindings]] 50 | key = "V" 51 | mods = "Control|Shift" 52 | action = "Paste" 53 | 54 | [[keyboard.bindings]] 55 | key = "C" 56 | mods = "Control|Shift" 57 | action = "Copy" 58 | 59 | [[keyboard.bindings]] 60 | key = "Insert" 61 | mods = "Shift" 62 | action = "PasteSelection" 63 | 64 | [[keyboard.bindings]] 65 | key = "Key0" 66 | mods = "Control" 67 | action = "ResetFontSize" 68 | 69 | [[keyboard.bindings]] 70 | key = "Equals" 71 | mods = "Control" 72 | action = "IncreaseFontSize" 73 | 74 | [[keyboard.bindings]] 75 | key = "Plus" 76 | mods = "Control" 77 | action = "IncreaseFontSize" 78 | 79 | [[keyboard.bindings]] 80 | key = "Minus" 81 | mods = "Control" 82 | action = "DecreaseFontSize" 83 | 84 | [[keyboard.bindings]] 85 | key = "F11" 86 | mods = "None" 87 | action = "ToggleFullscreen" 88 | 89 | [[keyboard.bindings]] 90 | key = "Paste" 91 | mods = "None" 92 | action = "Paste" 93 | 94 | [[keyboard.bindings]] 95 | key = "Copy" 96 | mods = "None" 97 | action = "Copy" 98 | 99 | [[keyboard.bindings]] 100 | key = "L" 101 | mods = "Control" 102 | action = "ClearLogNotice" 103 | 104 | [[keyboard.bindings]] 105 | key = "L" 106 | mods = "Control" 107 | chars = "\f" 108 | 109 | [[keyboard.bindings]] 110 | key = "PageUp" 111 | mods = "None" 112 | action = "ScrollPageUp" 113 | mode = "~Alt" 114 | 115 | [[keyboard.bindings]] 116 | key = "PageDown" 117 | mods = "None" 118 | action = "ScrollPageDown" 119 | mode = "~Alt" 120 | 121 | [[keyboard.bindings]] 122 | key = "Home" 123 | mods = "Shift" 124 | action = "ScrollToTop" 125 | mode = "~Alt" 126 | 127 | [[keyboard.bindings]] 128 | key = "End" 129 | mods = "Shift" 130 | action = "ScrollToBottom" 131 | mode = "~Alt" 132 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/tests/test_git_aliases.zunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zunit 2 | 3 | @setup { 4 | load "../you-should-use.plugin.zsh" 5 | YSU_MESSAGE_FORMAT='Found existing %alias_type for "%command". You should use: "%alias"' 6 | unset YSU_MESSAGE_POSITION 7 | 8 | if [[ ! -f ~/.gitconfig ]]; then 9 | touch ~/.gitconfig 10 | fi 11 | 12 | # Remove use git configuration which may interfere with tests 13 | mv ~/.gitconfig ~/.gitconfig.bak 14 | touch ~/.gitconfig 15 | } 16 | 17 | @teardown { 18 | # Restore users git configuration 19 | mv ~/.gitconfig.bak ~/.gitconfig 20 | } 21 | 22 | @test 'git aliases not triggered by aliased git commands' { 23 | git config --global alias.co checkout 24 | run _check_git_aliases "gco" "git checkout" 25 | 26 | assert $state equals 0 27 | assert "$output" is_empty 28 | } 29 | 30 | @test 'git aliases not triggred by non git commands' { 31 | git config --global alias.cp cherry-pick 32 | run _check_git_aliases "ls -l" "ls -l" 33 | 34 | assert $state equals 0 35 | assert "$output" is_empty 36 | } 37 | 38 | @test 'git aliases no match' { 39 | run _check_git_aliases "git config --list" "git config --list" 40 | 41 | assert "$output" is_empty 42 | assert $state equals 0 43 | } 44 | 45 | @test 'git aliases substring match' { 46 | git config --global alias.cfg config 47 | run _check_git_aliases "git config --list" "git config --list" 48 | 49 | assert "$output" contains 'Found existing git alias for "config". You should use: "git cfg"' 50 | assert $state equals 0 51 | } 52 | 53 | @test 'git aliases ignore on sudo' { 54 | git config --global alias.cfg config 55 | run _check_git_aliases "sudo git config --list" "sudo git config --list" 56 | 57 | assert "$output" is_empty 58 | assert $state equals 0 59 | } 60 | 61 | @test 'git aliases exact match' { 62 | git config --global alias.st status 63 | run _check_git_aliases "git status" "git status" 64 | 65 | assert "$output" contains 'Found existing git alias for "status". You should use: "git st"' 66 | assert $state equals 0 67 | } 68 | 69 | @test 'git aliases match parameters' { 70 | git config --global alias.recommit "commit --amend --reuse-message=HEAD" 71 | run _check_git_aliases "git commit --amend --reuse-message=HEAD" "git commit --amend --reuse-message=HEAD" 72 | 73 | assert "$output" contains 'Found existing git alias for "commit --amend --reuse-message=HEAD". You should use: "git recommit"' 74 | assert $state equals 0 75 | } 76 | 77 | @test 'git aliases not triggered when parameters are different' { 78 | git config --global alias.recommit "commit --amend --reuse-message=HEAD" 79 | run _check_git_aliases "git commit" "git commit" 80 | 81 | assert "$output" is_empty 82 | assert $state equals 0 83 | } 84 | -------------------------------------------------------------------------------- /claude/.claude/skills/expectations/SKILL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: expectations 3 | description: Working expectations and documentation practices. Use when capturing learnings or understanding how to work with this codebase. 4 | --- 5 | 6 | # Expectations 7 | 8 | ## When Working with Code 9 | 10 | 1. **ALWAYS FOLLOW TDD** - No production code without a failing test. Non-negotiable. 11 | 2. **Think deeply** before making any edits 12 | 3. **Understand the full context** of the code and requirements 13 | 4. **Ask clarifying questions** when requirements are ambiguous 14 | 5. **Think from first principles** - don't make assumptions 15 | 6. **Assess refactoring after every green** - but only refactor if it adds value 16 | 7. **Keep project docs current** - Update CLAUDE.md when introducing meaningful changes 17 | 18 | ## Documentation Framework 19 | 20 | **At the end of every significant change, ask: "What do I wish I'd known at the start?"** 21 | 22 | Document if ANY of these are true: 23 | - Would save future developers >30 minutes 24 | - Prevents a class of bugs or errors 25 | - Reveals non-obvious behavior or constraints 26 | - Captures architectural rationale or trade-offs 27 | - Documents domain-specific knowledge 28 | - Identifies effective patterns or anti-patterns 29 | - Clarifies tool setup or configuration gotchas 30 | 31 | ## Types of Learnings to Capture 32 | 33 | - **Gotchas**: Unexpected behavior discovered (e.g., "API returns null instead of empty array") 34 | - **Patterns**: Approaches that worked particularly well 35 | - **Anti-patterns**: Approaches that seemed good but caused problems 36 | - **Decisions**: Architectural choices with rationale and trade-offs 37 | - **Edge cases**: Non-obvious scenarios that required special handling 38 | - **Tool knowledge**: Setup, configuration, or usage insights 39 | 40 | ## Documentation Format 41 | 42 | ```markdown 43 | #### Gotcha: [Descriptive Title] 44 | 45 | **Context**: When this occurs 46 | **Issue**: What goes wrong 47 | **Solution**: How to handle it 48 | 49 | // CORRECT - Solution 50 | const example = "correct approach"; 51 | 52 | // WRONG - What causes the problem 53 | const wrong = "incorrect approach"; 54 | ``` 55 | 56 | ## Code Change Principles 57 | 58 | - **Start with a failing test** - always. No exceptions. 59 | - After making tests pass, always assess refactoring opportunities 60 | - After refactoring, verify all tests and static analysis pass, then commit 61 | - Respect the existing patterns and conventions 62 | - Maintain test coverage for all behavior changes 63 | - Keep changes small and incremental 64 | - Ensure all TypeScript strict mode requirements are met 65 | - Provide rationale for significant design decisions 66 | 67 | **If you find yourself writing production code without a failing test, STOP immediately and write the test first.** 68 | 69 | ## Communication 70 | 71 | - Be explicit about trade-offs in different approaches 72 | - Explain the reasoning behind significant design decisions 73 | - Flag any deviations from guidelines with justification 74 | - Suggest improvements that align with these principles 75 | - When unsure, ask for clarification rather than assuming 76 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog for zsh-you-should-use 2 | ================================ 3 | 4 | 1.9.0 5 | ----- 6 | * massive performance improvement to `check_alias_usage` #139 (Thanks @AtifChy!) 7 | * remove dependency on `wc` 8 | 9 | 1.8.0 10 | ----- 11 | * Fix bug in `check_alias_usage` command which would spam `entry=` to stdout 12 | 13 | 1.7.5 14 | ----- 15 | * Minor optimization to the way global aliases are checked (#135) 16 | 17 | 1.7.4 18 | ----- 19 | * Fix test failures when key and value are not split correctly (#132) 20 | 21 | 1.7.3 22 | ----- 23 | * Fix further issue where local variables would leak into users environment 24 | 25 | 1.7.2 26 | ----- 27 | * Fix issue where `key` and `entry` variables would leak into users environment 28 | 29 | 1.7.1 30 | ----- 31 | * Fix issue where \ and % would not be escaped correctly in messages (Issue #98) 32 | 33 | 1.7.0 34 | ----- 35 | * Add support for ignoring global aliases 36 | 37 | 1.6.1 38 | ----- 39 | * Fix detection of substrings with global aliases (#91) 40 | 41 | 1.6.0 42 | ----- 43 | * Revert smart alias expansion detection feature from 1.5.0 release as it is causing numerous regressions 44 | 45 | 1.5.2 46 | ----- 47 | * Fix bug in 1.5.0 where an alias would be recommended even though it had just been typed by the user 48 | 49 | 1.5.1 50 | ----- 51 | * Temporary revert of 1.5.0 which causes a major bug in alias detection (see #84) 52 | 53 | 1.5.0 54 | ----- 55 | * Suggest better available aliases if a user uses another alias (issue #79) 56 | 57 | 1.4.0 58 | ----- 59 | * Aliases reminders are no longer shown if running commands with sudo 60 | 61 | 1.3.0 62 | ----- 63 | * Add `check_alias_usage` function to generate report of aliases being used by user 64 | 65 | 1.2.1 66 | ----- 67 | * Fix minor bug where variables would pollute user's environment 68 | 69 | 1.2.0 70 | ----- 71 | * Add support for ignoring aliases 72 | 73 | 1.1.0 74 | ----- 75 | * git aliases with parameters are now correctly matched (Thanks @crater2150) 76 | 77 | 1.0.0 78 | ----- 79 | * Add ability to display reminder message *before* or *after* a command is executed 80 | 81 | 0.7.3 82 | ----- 83 | * Fix Default message format conflicting with autoswitch-virtualenv plugin 84 | 85 | 0.7.2 86 | ----- 87 | * Use type builtin to check if tput command is available 88 | 89 | 0.7.1 90 | ----- 91 | * Suppress error messages showing when tput is not installed 92 | 93 | 0.7.0 94 | ----- 95 | * Use tput command instead of raw escape codes 96 | 97 | 0.6.0 98 | ----- 99 | * Improved colouring for default message 100 | 101 | 0.5.1 102 | ----- 103 | * Minor fixes and updates to README. 104 | 105 | 0.5.0 106 | ----- 107 | * Add functions to temporarily disable (and re-enable) you-should-use 108 | 109 | 0.4.4 110 | ----- 111 | * export you-should-use version 112 | 113 | 0.4.3 114 | ----- 115 | * Use yellow messages by default 116 | 117 | 118 | 0.4.2 119 | ----- 120 | * Specify LICENSE (GPLv3) 121 | 122 | 0.4.1 123 | ----- 124 | * Only use default message format if `$YSU_MESSAGE_FORMAT` is unset 125 | 126 | 0.4.0 127 | ----- 128 | * Introduce CHANGELOG 129 | * Add ability to change message output on alias detection 130 | * Add index to README for quick navigation 131 | 132 | 133 | 0.3.2 134 | ----- 135 | * Fixed bug in best match algorithm (see #34) 136 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/tests/test_you_should_use.zunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zunit 2 | 3 | @setup { 4 | load "../you-should-use.plugin.zsh" 5 | unset YSU_MESSAGE_POSITION 6 | } 7 | 8 | @test 'ysu version exported' { 9 | git_version="$(git tag --list | sort | tail -1)" 10 | git tag --list 11 | 12 | assert "$YSU_VERSION" is_not_empty 13 | assert "$YSU_VERSION" same_as "$git_version" 14 | } 15 | 16 | @test 'ysu preexec functions are loaded by default' { 17 | assert '_check_aliases' in $preexec_functions 18 | assert '_check_git_aliases' in $preexec_functions 19 | assert '_check_global_aliases' in $preexec_functions 20 | assert '_flush_ysu_buffer' in $precmd_functions 21 | } 22 | 23 | @test 'ysu disable/enable functions' { 24 | disable_you_should_use 25 | assert '_check_aliases' not_in $preexec_functions 26 | assert '_check_git_aliases' not_in $preexec_functions 27 | assert '_check_global_aliases' not_in $preexec_functions 28 | assert '_flush_ysu_buffer' not_in $precmd_functions 29 | enable_you_should_use 30 | assert '_check_aliases' in $preexec_functions 31 | assert '_check_git_aliases' in $preexec_functions 32 | assert '_check_global_aliases' in $preexec_functions 33 | assert '_flush_ysu_buffer' in $precmd_functions 34 | } 35 | 36 | @test 'ysu message correct output' { 37 | unset YSU_MESSAGE_FORMAT 38 | run ysu_message "alias" "ls -l" "ll" 39 | 40 | assert $state equals 0 41 | 42 | expected="$(tput bold)$(tput setaf 3)Found existing alias for $(tput setaf 5)\"ls -l\"$(tput setaf 3). " 43 | expected+="You should use: $(tput setaf 5)\"ll\"$(tput sgr0)" 44 | 45 | assert "$output" same_as "$expected" 46 | } 47 | 48 | @test 'ysu message correct output 2' { 49 | unset YSU_MESSAGE_FORMAT 50 | run ysu_message "foobar" "2>/dev/null" "NE" 51 | 52 | assert $state equals 0 53 | 54 | expected="$(tput bold)$(tput setaf 3)Found existing foobar for $(tput setaf 5)\"2>/dev/null\"$(tput setaf 3). " 55 | expected+="You should use: $(tput setaf 5)\"NE\"$(tput sgr0)" 56 | 57 | assert "$output" same_as "$expected" 58 | } 59 | 60 | @test 'escapes \ and % correctly' { 61 | unset YSU_MESSAGE_FORMAT 62 | run ysu_message "alias" "printf '%s\\n'" "pf" 63 | 64 | assert $state equals 0 65 | 66 | expected="$(tput bold)$(tput setaf 3)Found existing alias for $(tput setaf 5)\"printf '%s\\n'\"$(tput setaf 3). " 67 | expected+="You should use: $(tput setaf 5)\"pf\"$(tput sgr0)" 68 | 69 | assert "$output" same_as "$expected" 70 | } 71 | 72 | @test 'ysu - _write_ysu_buffer after' { 73 | unset YSU_MESSAGE_FORMAT 74 | YSU_MESSAGE_POSITION="after" 75 | _YSU_BUFFER="" 76 | 77 | _write_ysu_buffer "hello world" 78 | 79 | assert $state equals 0 80 | 81 | assert "$output" is_empty 82 | assert "$_YSU_BUFFER" same_as "hello world" 83 | } 84 | 85 | @test 'ysu message - custom message' { 86 | export YSU_MESSAGE_FORMAT="Hi there %alias_type! %command <=> %alias" 87 | run ysu_message "git alias" "tig" "t" 88 | 89 | assert $state equals 0 90 | assert "$output" same_as "Hi there git alias! tig <=> t" 91 | } 92 | 93 | @test 'ysu message - custom message 2' { 94 | export YSU_MESSAGE_FORMAT="%alias is a %alias_type for %command" 95 | run ysu_message "alias" "xdg-open" "xopen" 96 | 97 | assert $state equals 0 98 | assert "$output" same_as "xopen is a alias for xdg-open" 99 | } 100 | 101 | @test 'ysu message - custom message multiple usages' { 102 | export YSU_MESSAGE_FORMAT="%alias %alias %command %command %alias_type %alias_type" 103 | run ysu_message 'git alias' 'xpaste' 'xp' 104 | 105 | assert $state equals 0 106 | assert "$output" same_as "xp xp xpaste xpaste git alias git alias" 107 | } 108 | -------------------------------------------------------------------------------- /claude/.claude/skills/refactoring/SKILL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: refactoring 3 | description: Refactoring assessment and patterns. Use after tests pass (GREEN phase) to assess improvement opportunities. 4 | --- 5 | 6 | # Refactoring 7 | 8 | Refactoring is the third step of TDD. After GREEN, assess if refactoring adds value. 9 | 10 | ## When to Refactor 11 | 12 | - Always assess after green 13 | - Only refactor if it improves the code 14 | - **Commit working code BEFORE refactoring** (critical safety net) 15 | 16 | ### Commit Before Refactoring - WHY 17 | 18 | Having a working baseline before refactoring: 19 | - Allows reverting if refactoring breaks things 20 | - Provides safety net for experimentation 21 | - Makes refactoring less risky 22 | - Shows clear separation in git history 23 | 24 | **Workflow:** 25 | 1. GREEN: Tests pass 26 | 2. COMMIT: Save working code 27 | 3. REFACTOR: Improve structure 28 | 4. COMMIT: Save refactored code 29 | 30 | ## Priority Classification 31 | 32 | | Priority | Action | Examples | 33 | |----------|--------|----------| 34 | | Critical | Fix now | Mutations, knowledge duplication, >3 levels nesting | 35 | | High | This session | Magic numbers, unclear names, >30 line functions | 36 | | Nice | Later | Minor naming, single-use helpers | 37 | | Skip | Don't change | Already clean code | 38 | 39 | ## DRY = Knowledge, Not Code 40 | 41 | **Abstract when**: 42 | - Same business concept (semantic meaning) 43 | - Would change together if requirements change 44 | - Obvious why grouped together 45 | 46 | **Keep separate when**: 47 | - Different concepts that look similar (structural) 48 | - Would evolve independently 49 | - Coupling would be confusing 50 | 51 | ## Example Assessment 52 | 53 | ```typescript 54 | // After GREEN: 55 | const processOrder = (order: Order): ProcessedOrder => { 56 | const itemsTotal = order.items.reduce((sum, item) => sum + item.price, 0); 57 | const shipping = itemsTotal > 50 ? 0 : 5.99; 58 | return { ...order, total: itemsTotal + shipping, shippingCost: shipping }; 59 | }; 60 | 61 | // ASSESSMENT: 62 | // ⚠️ High: Magic numbers 50, 5.99 → extract constants 63 | // ✅ Skip: Structure is clear enough 64 | // DECISION: Extract constants only 65 | ``` 66 | 67 | ## Speculative Code is a TDD Violation 68 | 69 | If code isn't driven by a failing test, don't write it. 70 | 71 | **Key lesson**: Every line must have a test that demanded its existence. 72 | 73 | ❌ **Speculative code examples:** 74 | - "Just in case" logic 75 | - Features not yet needed 76 | - Code written "for future flexibility" 77 | - Untested error handling paths 78 | 79 | **What to do**: Delete speculative code. Add behavior tests instead. 80 | 81 | --- 82 | 83 | ## When NOT to Refactor 84 | 85 | Don't refactor when: 86 | 87 | - ❌ Code works correctly (no bug to fix) 88 | - ❌ No test demands the change (speculative refactoring) 89 | - ❌ Would change behavior (that's a feature, not refactoring) 90 | - ❌ Premature optimization 91 | - ❌ Code is "good enough" for current phase 92 | 93 | **Remember**: Refactoring should improve code structure without changing behavior. 94 | 95 | --- 96 | 97 | ## Commit Messages for Refactoring 98 | 99 | ``` 100 | refactor: extract scenario validation logic 101 | refactor: simplify error handling flow 102 | refactor: rename ambiguous parameter names 103 | ``` 104 | 105 | **Format**: `refactor: ` 106 | 107 | **Note**: Refactoring commits should NOT be mixed with feature commits. 108 | 109 | --- 110 | 111 | ## Refactoring Checklist 112 | 113 | - [ ] All tests pass without modification 114 | - [ ] No new public APIs added 115 | - [ ] Code more readable than before 116 | - [ ] Committed separately from features 117 | - [ ] Committed BEFORE refactoring (safety net) 118 | - [ ] No speculative code added 119 | - [ ] Behavior unchanged (tests prove this) 120 | -------------------------------------------------------------------------------- /zsh/.zshrc: -------------------------------------------------------------------------------- 1 | # If you come from bash you might have to change your $PATH. 2 | # export PATH=$HOME/bin:/usr/local/bin:$PATH 3 | 4 | # Path to your oh-my-zsh installation. 5 | export ZSH=$HOME/.oh-my-zsh 6 | 7 | # Set name of the theme to load --- if set to "random", it will 8 | # load a random theme each time oh-my-zsh is loaded, in which case, 9 | # to know which specific one was loaded, run: echo $RANDOM_THEME 10 | # See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes 11 | # ZSH_THEME="dracula" 12 | 13 | # Set list of themes to pick from when loading at random 14 | # Setting this variable when ZSH_THEME=random will cause zsh to load 15 | # a theme from this variable instead of looking in $ZSH/themes/ 16 | # If set to an empty array, this variable will have no effect. 17 | # ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" ) 18 | 19 | # Uncomment the following line to use case-sensitive completion. 20 | # CASE_SENSITIVE="true" 21 | 22 | # Uncomment the following line to use hyphen-insensitive completion. 23 | # Case-sensitive completion must be off. _ and - will be interchangeable. 24 | # HYPHEN_INSENSITIVE="true" 25 | 26 | # Uncomment one of the following lines to change the auto-update behavior 27 | # zstyle ':omz:update' mode disabled # disable automatic updates 28 | # zstyle ':omz:update' mode auto # update automatically without asking 29 | # zstyle ':omz:update' mode reminder # just remind me to update when it's time 30 | 31 | # Uncomment the following line to change how often to auto-update (in days). 32 | # zstyle ':omz:update' frequency 13 33 | 34 | # Uncomment the following line if pasting URLs and other text is messed up. 35 | # DISABLE_MAGIC_FUNCTIONS="true" 36 | 37 | # Uncomment the following line to disable colors in ls. 38 | # DISABLE_LS_COLORS="true" 39 | 40 | # Uncomment the following line to disable auto-setting terminal title. 41 | # DISABLE_AUTO_TITLE="true" 42 | 43 | # Uncomment the following line to enable command auto-correction. 44 | # ENABLE_CORRECTION="true" 45 | 46 | # Uncomment the following line to display red dots whilst waiting for completion. 47 | # You can also set it to another string to have that shown instead of the default red dots. 48 | # e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" 49 | # Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) 50 | # COMPLETION_WAITING_DOTS="true" 51 | 52 | # Uncomment the following line if you want to disable marking untracked files 53 | # under VCS as dirty. This makes repository status check for large repositories 54 | # much, much faster. 55 | # DISABLE_UNTRACKED_FILES_DIRTY="true" 56 | 57 | # Uncomment the following line if you want to change the command execution time 58 | # stamp shown in the history command output. 59 | # You can set one of the optional three formats: 60 | # "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" 61 | # or set a custom format using the strftime function format specifications, 62 | # see 'man strftime' for details. 63 | # HIST_STAMPS="mm/dd/yyyy" 64 | 65 | # Would you like to use another custom folder than $ZSH/custom? 66 | # ZSH_CUSTOM=/path/to/new-custom-folder 67 | 68 | # Which plugins would you like to load? 69 | # Standard plugins can be found in $ZSH/plugins/ 70 | # Custom plugins may be added to $ZSH_CUSTOM/plugins/ 71 | # Example format: plugins=(rails git textmate ruby lighthouse) 72 | # Add wisely, as too many plugins slow down shell startup. 73 | plugins=( 74 | git 75 | zsh-you-should-use 76 | zsh-autosuggestions 77 | ) 78 | 79 | source $ZSH/oh-my-zsh.sh 80 | 81 | # User configuration 82 | 83 | # GPG configuration for terminal-based signing 84 | export GPG_TTY=$(tty) 85 | 86 | # export MANPATH="/usr/local/man:$MANPATH" 87 | 88 | # You may need to manually set your language environment 89 | # export LANG=en_US.UTF-8 90 | 91 | # Preferred editor for local and remote sessions 92 | # if [[ -n $SSH_CONNECTION ]]; then 93 | # export EDITOR='vim' 94 | # else 95 | # export EDITOR='mvim' 96 | # fi 97 | 98 | # Compilation flags 99 | # export ARCHFLAGS="-arch x86_64" 100 | 101 | # Set personal aliases, overriding those provided by oh-my-zsh libs, 102 | # plugins, and themes. Aliases can be placed here, though oh-my-zsh 103 | # users are encouraged to define aliases within the ZSH_CUSTOM folder. 104 | # For a full list of active aliases, run `alias`. 105 | # 106 | # Example aliases 107 | # alias zshconfig="mate ~/.zshrc" 108 | # alias ohmyzsh="mate ~/.oh-my-zsh" 109 | 110 | [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh 111 | 112 | eval "$(/opt/homebrew/bin/brew shellenv)" 113 | source $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh 114 | source $HOME/.zsh_profile 115 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/tests/test_aliases.zunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zunit 2 | 3 | @setup { 4 | load "../you-should-use.plugin.zsh" 5 | # Simplify format for tests 6 | YSU_MESSAGE_FORMAT='Found existing %alias_type for "%command". You should use: "%alias"' 7 | unset YSU_MESSAGE_POSITION 8 | } 9 | 10 | @teardown { 11 | } 12 | 13 | @test 'aliases no match' { 14 | export YSU_MODE="ALL" 15 | alias ls="less -R" 16 | run _check_aliases "less" 17 | 18 | assert "$output" is_empty 19 | assert $state equals 0 20 | } 21 | 22 | @test 'aliases longer or the same length as their commands are ignored' { 23 | export YSU_MODE="ALL" 24 | alias longalias='ls -lA' 25 | alias ls_-lA='ls -lA' 26 | run _check_aliases "ls -lA" 27 | 28 | assert "$output" is_empty 29 | assert $state equals 0 30 | } 31 | 32 | @test 'aliases match - all with alphabetical ordering' { 33 | export YSU_MODE="ALL" 34 | alias c="git status -v" 35 | alias a="git status" 36 | alias b="git" 37 | run _check_aliases "git status -v" 38 | 39 | assert $lines[1] contains 'Found existing alias for "git status". You should use: "a"' 40 | assert $lines[2] contains 'Found existing alias for "git". You should use: "b"' 41 | assert $lines[3] contains 'Found existing alias for "git status -v". You should use: "c"' 42 | assert $state equals 0 43 | } 44 | 45 | @test 'aliases bestmatch default' { 46 | unset YSU_MODE 47 | alias gs="git status" 48 | alias g="git" 49 | run _check_aliases "git status -v" 50 | 51 | assert "$output" contains 'Found existing alias for "git status". You should use: "gs"' 52 | assert $state equals 0 53 | } 54 | 55 | @test 'aliases bestmatch - chooses longest matching alias value' { 56 | export YSU_MODE="BESTMATCH" 57 | alias gs="git status" 58 | alias g="git" 59 | alias gsv="git status -v" 60 | run _check_aliases "git status -v" 61 | 62 | assert "$output" contains 'Found existing alias for "git status -v". You should use: "gsv"' 63 | assert $state equals 0 64 | } 65 | 66 | @test 'aliases bestmatch - chooses longest matching alias value case 2' { 67 | export YSU_MODE="BESTMATCH" 68 | alias gco="git checkout" 69 | alias gcom="git checkout master" 70 | run _check_aliases "git checkout master" 71 | 72 | assert "$output" contains 'Found existing alias for "git checkout master". You should use: "gcom"' 73 | assert $state equals 0 74 | } 75 | 76 | @test 'aliases bestmatch - chooses shortest alias key on equal value lengths' { 77 | export YSU_MODE="BESTMATCH" 78 | alias v="vim" 79 | alias nvim="vim" 80 | 81 | run _check_aliases "vim" 82 | 83 | assert "$output" contains 'Found existing alias for "vim". You should use: "v"' 84 | assert $state equals 0 85 | } 86 | 87 | @test 'aliases ignore if sudo' { 88 | unset YSU_MODE 89 | alias gs="git status" 90 | run _check_aliases "sudo git status -v" 91 | 92 | assert "$output" is_empty 93 | assert $state equals 0 94 | } 95 | 96 | @test 'aliases exact match' { 97 | export YSU_MODE="ALL" 98 | alias hu='hg update' 99 | run _check_aliases 'hg update' 100 | 101 | assert "$output" contains 'Found existing alias for "hg update". You should use: "hu"' 102 | assert $state equals 0 103 | } 104 | 105 | @test 'aliases substring match' { 106 | export YSU_MODE="ALL" 107 | alias hu='hg update' 108 | run _check_aliases 'hg update -all' 109 | 110 | assert "$output" contains 'Found existing alias for "hg update". You should use: "hu"' 111 | assert $state equals 0 112 | } 113 | 114 | @test 'aliases ignores - does not report an ignored alias' { 115 | export YSU_MODE="ALL" 116 | export YSU_IGNORED_ALIASES=("something" "e" "else") 117 | alias e="echo" 118 | alias l="ls" 119 | run _check_aliases "echo hello" 120 | 121 | assert $output is_empty 122 | assert $state equals 0 123 | } 124 | 125 | @test 'aliases ignores only - only ignores specified aliases' { 126 | export YSU_MODE="ALL" 127 | export YSU_IGNORED_ALIASES=("something" "else") 128 | alias e="echo" 129 | alias l="ls" 130 | run _check_aliases "echo hello" 131 | 132 | assert $output contains 'Found existing alias for "echo"' 133 | assert $state equals 0 134 | } 135 | 136 | @test 'dont display recommendations for an alias if it is used' { 137 | alias hu='hg update' 138 | run _check_aliases 'hu' 'hg update' 139 | 140 | assert "$output" is_empty 141 | assert $state equals 0 142 | } 143 | 144 | @test 'dont display recommendations for an alias if it is used - 2' { 145 | alias eh='echo hello' 146 | run _check_aliases 'eh world' 'echo hello world' 147 | 148 | assert "$output" is_empty 149 | assert $state equals 0 150 | } 151 | 152 | @test 'dont display recommendations for an alias if it is used - 4' { 153 | alias ..='cd ..' 154 | alias cd..='cd ..' 155 | 156 | run _check_aliases '..' 'cd ..' 157 | 158 | assert "$output" is_empty 159 | assert $state equals 0 160 | } 161 | 162 | @test 'dont display recommendations for an alias if it is used - 3' { 163 | alias ls='ls --color=auto' 164 | alias ll='ls -lh --group-directories-first' 165 | 166 | run _check_aliases 'll' 'ls --color=auto -lh --group-directories-first' 167 | 168 | assert "$output" is_empty 169 | assert $state equals 0 170 | } 171 | -------------------------------------------------------------------------------- /claude/.claude/CLAUDE.md: -------------------------------------------------------------------------------- 1 | # Development Guidelines for Claude 2 | 3 | > **About this file (v3.0.0):** Lean version optimized for context efficiency. Core principles here; detailed patterns loaded on-demand via skills. 4 | > 5 | > **Architecture:** 6 | > - **CLAUDE.md** (this file): Core philosophy + quick reference (~100 lines, always loaded) 7 | > - **Skills**: Detailed patterns loaded on-demand (tdd, testing, typescript-strict, functional, refactoring, expectations, planning) 8 | > - **Agents**: Specialized subprocesses for verification and analysis 9 | > 10 | > **Previous versions:** 11 | > - v2.0.0: Modular with @docs/ imports (~3000+ lines always loaded) 12 | > - v1.0.0: Single monolithic file (1,818 lines) 13 | 14 | ## Core Philosophy 15 | 16 | **TEST-DRIVEN DEVELOPMENT IS NON-NEGOTIABLE.** Every single line of production code must be written in response to a failing test. No exceptions. This is not a suggestion or a preference - it is the fundamental practice that enables all other principles in this document. 17 | 18 | I follow Test-Driven Development (TDD) with a strong emphasis on behavior-driven testing and functional programming principles. All work should be done in small, incremental changes that maintain a working state throughout development. 19 | 20 | ## Quick Reference 21 | 22 | **Key Principles:** 23 | 24 | - Write tests first (TDD) 25 | - Test behavior, not implementation 26 | - No `any` types or type assertions 27 | - Immutable data only 28 | - Small, pure functions 29 | - TypeScript strict mode always 30 | - Use real schemas/types in tests, never redefine them 31 | 32 | **Preferred Tools:** 33 | 34 | - **Language**: TypeScript (strict mode) 35 | - **Testing**: Jest/Vitest + React Testing Library 36 | - **State Management**: Prefer immutable patterns 37 | 38 | ## Testing Principles 39 | 40 | **Core principle**: Test behavior, not implementation. 100% coverage through business behavior. 41 | 42 | **Quick reference:** 43 | - Write tests first (TDD non-negotiable) 44 | - Test through public API exclusively 45 | - Use factory functions for test data (no `let`/`beforeEach`) 46 | - Tests must document expected business behavior 47 | - No 1:1 mapping between test files and implementation files 48 | 49 | For detailed testing patterns and examples, load the `testing` skill. 50 | 51 | ## TypeScript Guidelines 52 | 53 | **Core principle**: Strict mode always. Schema-first at trust boundaries, types for internal logic. 54 | 55 | **Quick reference:** 56 | - No `any` types - ever (use `unknown` if type truly unknown) 57 | - No type assertions without justification 58 | - Prefer `type` over `interface` for data structures 59 | - Reserve `interface` for behavior contracts only 60 | - Define schemas first, derive types from them (Zod/Standard Schema) 61 | - Use schemas at trust boundaries, plain types for internal logic 62 | 63 | For detailed TypeScript patterns and rationale, load the `typescript-strict` skill. 64 | 65 | ## Code Style 66 | 67 | **Core principle**: Functional programming with immutable data. Self-documenting code. 68 | 69 | **Quick reference:** 70 | - No data mutation - immutable data structures only 71 | - Pure functions wherever possible 72 | - No nested if/else - use early returns or composition 73 | - No comments - code should be self-documenting 74 | - Prefer options objects over positional parameters 75 | - Use array methods (`map`, `filter`, `reduce`) over loops 76 | 77 | For detailed patterns and examples, load the `functional` skill. 78 | 79 | ## Development Workflow 80 | 81 | **Core principle**: RED-GREEN-REFACTOR in small, known-good increments. TDD is the fundamental practice. 82 | 83 | **Quick reference:** 84 | - RED: Write failing test first (NO production code without failing test) 85 | - GREEN: Write MINIMUM code to pass test 86 | - REFACTOR: Assess improvement opportunities (only refactor if adds value) 87 | - **Wait for commit approval** before every commit 88 | - Each increment leaves codebase in working state 89 | - Capture learnings as they occur, merge at end 90 | 91 | For detailed TDD workflow, load the `tdd` skill. 92 | For refactoring methodology, load the `refactoring` skill. 93 | For significant work, load the `planning` skill for three-document model (PLAN.md, WIP.md, LEARNINGS.md). 94 | 95 | ## Working with Claude 96 | 97 | **Core principle**: Think deeply, follow TDD strictly, capture learnings while context is fresh. 98 | 99 | **Quick reference:** 100 | - ALWAYS FOLLOW TDD - no production code without failing test 101 | - Assess refactoring after every green (but only if adds value) 102 | - Update CLAUDE.md when introducing meaningful changes 103 | - Ask "What do I wish I'd known at the start?" after significant changes 104 | - Document gotchas, patterns, decisions, edge cases while context is fresh 105 | 106 | For detailed TDD workflow, load the `tdd` skill. 107 | For refactoring methodology, load the `refactoring` skill. 108 | For detailed guidance on expectations and documentation, load the `expectations` skill. 109 | 110 | ## Resources and References 111 | 112 | - [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) 113 | - [Testing Library Principles](https://testing-library.com/docs/guiding-principles) 114 | - [Kent C. Dodds Testing JavaScript](https://testingjavascript.com/) 115 | - [Functional Programming in TypeScript](https://gcanti.github.io/fp-ts/) 116 | 117 | ## Summary 118 | 119 | The key is to write clean, testable, functional code that evolves through small, safe increments. Every change should be driven by a test that describes the desired behavior, and the implementation should be the simplest thing that makes that test pass. When in doubt, favor simplicity and readability over cleverness. 120 | -------------------------------------------------------------------------------- /karabiner/.config/karabiner/karabiner.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "check_for_updates_on_startup": true, 4 | "show_in_menu_bar": false, 5 | "ask_for_confirmation_before_quitting": false 6 | }, 7 | "profiles": [ 8 | { 9 | "name": "Default profile", 10 | "selected": true, 11 | "virtual_hid_keyboard": { "keyboard_type_v2": "ansi" }, 12 | "parameters": { 13 | "basic.to_if_alone_timeout_milliseconds": 200, 14 | "basic.to_if_held_down_threshold_milliseconds": 200 15 | }, 16 | "complex_modifications": { 17 | "rules": [ 18 | { 19 | "description": "Remap caps lock to Ctrl key when held, escape when tapped", 20 | "manipulators": [ 21 | { 22 | "from": { 23 | "key_code": "caps_lock", 24 | "modifiers": { "optional": ["any"] } 25 | }, 26 | "to": [{ "key_code": "left_control" }], 27 | "to_if_alone": [{ "key_code": "escape" }], 28 | "type": "basic" 29 | } 30 | ] 31 | }, 32 | { 33 | "description": "Remap Tab to Hyper key when held, Tab key when tapped", 34 | "manipulators": [ 35 | { 36 | "from": { "key_code": "tab" }, 37 | "to": [ 38 | { 39 | "set_variable": { 40 | "name": "hyper", 41 | "value": 1 42 | } 43 | } 44 | ], 45 | "to_after_key_up": [ 46 | { 47 | "set_variable": { 48 | "name": "hyper", 49 | "value": 0 50 | } 51 | } 52 | ], 53 | "to_if_alone": [{ "key_code": "tab" }], 54 | "type": "basic" 55 | } 56 | ] 57 | }, 58 | { 59 | "description": "Hyper key sublayer e", 60 | "manipulators": [ 61 | { 62 | "description": "Toggle Hyper sublayer e", 63 | "conditions": [ 64 | { 65 | "type": "variable_if", 66 | "name": "hyper", 67 | "value": 1 68 | }, 69 | { 70 | "name": "hyper_sublayer_w", 71 | "type": "variable_if", 72 | "value": 0 73 | } 74 | ], 75 | "from": { 76 | "key_code": "e", 77 | "modifiers": { "optional": ["any"] } 78 | }, 79 | "to": [ 80 | { "set_variable": { "name": "hyper_sublayer_e", "value": 1 } } 81 | ], 82 | "to_after_key_up": [ 83 | { "set_variable": { "name": "hyper_sublayer_e", "value": 0 } } 84 | ], 85 | "type": "basic" 86 | } 87 | ] 88 | }, 89 | { 90 | "description": "Open firefox", 91 | "manipulators": [ 92 | { 93 | "conditions": [ 94 | { 95 | "type": "variable_if", 96 | "name": "hyper_sublayer_e", 97 | "value": 1 98 | } 99 | ], 100 | "from": { "key_code": "f" }, 101 | "to": [{ "shell_command": "open -a Firefox" }], 102 | "type": "basic" 103 | } 104 | ] 105 | }, 106 | { 107 | "description": "Open Chrome", 108 | "manipulators": [ 109 | { 110 | "conditions": [ 111 | { 112 | "type": "variable_if", 113 | "name": "hyper_sublayer_e", 114 | "value": 1 115 | } 116 | ], 117 | "from": { "key_code": "c" }, 118 | "to": [{ "shell_command": "open -a 'Google Chrome'" }], 119 | "type": "basic" 120 | } 121 | ] 122 | }, 123 | { 124 | "description": "Open alacritty with escape+tab", 125 | "manipulators": [ 126 | { 127 | "type": "basic", 128 | "from": { 129 | "key_code": "escape", 130 | "modifiers": { 131 | "optional": ["any"] 132 | } 133 | }, 134 | "to": [ 135 | { 136 | "shell_command": "open -a Alacritty" 137 | } 138 | ], 139 | "conditions": [ 140 | { 141 | "type": "variable_if", 142 | "name": "hyper", 143 | "value": 1 144 | } 145 | ] 146 | } 147 | ] 148 | }, 149 | { 150 | "description": "Open 1password with tab+1", 151 | "manipulators": [ 152 | { 153 | "type": "basic", 154 | "from": { 155 | "key_code": "1", 156 | "modifiers": { 157 | "optional": ["any"] 158 | } 159 | }, 160 | "to": [ 161 | { 162 | "shell_command": "open -a 1Password" 163 | } 164 | ], 165 | "conditions": [ 166 | { 167 | "type": "variable_if", 168 | "name": "hyper", 169 | "value": 1 170 | } 171 | ] 172 | } 173 | ] 174 | } 175 | ] 176 | } 177 | } 178 | ] 179 | } 180 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migration Guide: v1.x → v2.0.0 2 | 3 | This guide helps you upgrade from the monolithic CLAUDE.md (v1.x) to the modular structure (v2.0.0). 4 | 5 | ## What Changed 6 | 7 | ### Before (v1.0.0) 8 | ``` 9 | ~/.claude/ 10 | └── CLAUDE.md (1,818 lines - everything in one file) 11 | ``` 12 | 13 | **View v1.0.0 monolithic file:** 14 | - GitHub: https://github.com/citypaul/.dotfiles/blob/v1.0.0/claude/.claude/CLAUDE.md 15 | - Raw download: https://github.com/citypaul/.dotfiles/raw/v1.0.0/claude/.claude/CLAUDE.md 16 | 17 | ### After (v2.0.0) 18 | ``` 19 | ~/.claude/ 20 | ├── CLAUDE.md (156 lines - core + quick reference) 21 | └── docs/ 22 | ├── testing.md (238 lines) 23 | ├── typescript.md (305 lines) 24 | ├── code-style.md (370 lines) 25 | ├── workflow.md (671 lines) 26 | ├── examples.md (118 lines) 27 | └── working-with-claude.md (74 lines) 28 | ``` 29 | 30 | **Content is identical** - v2.0.0 splits the v1.0.0 monolithic file into modular files with imports. No content was removed or changed, just reorganized. 31 | 32 | ## Do You Need to Migrate? 33 | 34 | ### ✅ Your old setup still works 35 | 36 | The v1.0.0 monolithic CLAUDE.md continues to work perfectly. You can upgrade when ready. 37 | 38 | ### ⚠️ You SHOULD migrate if: 39 | 40 | - You want faster Claude Code load times (91% reduction: 1,818 → 156 lines) 41 | - You prefer modular, on-demand documentation 42 | - You want easier maintenance and version control 43 | 44 | ### 🔴 You MUST migrate if: 45 | 46 | - You created custom imports referencing the old structure 47 | - You modified CLAUDE.md and want to merge upstream improvements 48 | 49 | ## Migration Steps 50 | 51 | ### Option 1: Clean Install (Recommended) 52 | 53 | **If you're using the full dotfiles with GNU Stow:** 54 | 55 | ```bash 56 | # 1. Backup your current setup 57 | cp ~/.claude/CLAUDE.md ~/.claude/CLAUDE.md.v1.backup 58 | 59 | # 2. Pull latest changes 60 | cd ~/.dotfiles 61 | git pull origin main 62 | 63 | # 3. Reinstall (stow will update the files) 64 | stow -R -t ~ claude 65 | 66 | # 4. Verify the new structure 67 | ls -la ~/.claude/docs/ 68 | ``` 69 | 70 | **If you installed CLAUDE.md manually:** 71 | 72 | ```bash 73 | # 1. Backup current file 74 | cp ~/.claude/CLAUDE.md ~/.claude/CLAUDE.md.v1.backup 75 | 76 | # 2. Install new structure 77 | cd /tmp 78 | git clone https://github.com/citypaul/.dotfiles.git 79 | cd .dotfiles 80 | 81 | # 3. Copy all files 82 | cp claude/.claude/CLAUDE.md ~/.claude/ 83 | mkdir -p ~/.claude/docs 84 | cp -r claude/.claude/docs/* ~/.claude/docs/ 85 | 86 | # 4. Copy agents if you want them 87 | mkdir -p ~/.claude/agents 88 | cp -r claude/.claude/agents/* ~/.claude/agents/ 89 | ``` 90 | 91 | ### Option 2: Keep v1.0.0 (Stay on old version) 92 | 93 | ```bash 94 | # Check out v1.0.0 tag explicitly 95 | cd ~/.dotfiles 96 | git checkout v1.0.0 97 | 98 | # Or download the specific version 99 | curl -L https://github.com/citypaul/.dotfiles/raw/v1.0.0/claude/.claude/CLAUDE.md \ 100 | -o ~/.claude/CLAUDE.md 101 | ``` 102 | 103 | ## Verification 104 | 105 | After migration, verify the new structure works: 106 | 107 | ### 1. Check files exist 108 | 109 | ```bash 110 | # Main file should be ~156 lines 111 | wc -l ~/.claude/CLAUDE.md 112 | 113 | # Docs directory should have 6 files 114 | ls ~/.claude/docs/ 115 | # Should show: 116 | # code-style.md 117 | # examples.md 118 | # testing.md 119 | # typescript.md 120 | # workflow.md 121 | # working-with-claude.md 122 | ``` 123 | 124 | ### 2. Test with Claude Code 125 | 126 | Open any project in Claude Code and use the `/memory` command: 127 | 128 | ``` 129 | /memory 130 | ``` 131 | 132 | You should see: 133 | - Main CLAUDE.md loaded 134 | - Import references to `@~/.claude/docs/*.md` 135 | 136 | When Claude needs detailed information (e.g., testing principles), it will automatically load the relevant doc file. 137 | 138 | ## Custom Modifications 139 | 140 | ### If you modified CLAUDE.md in v1.0.0 141 | 142 | You have two options: 143 | 144 | **Option A: Apply modifications to new structure** 145 | 146 | 1. Compare your backup to the new files: 147 | ```bash 148 | diff ~/.claude/CLAUDE.md.v1.backup ~/.claude/CLAUDE.md 149 | ``` 150 | 151 | 2. Identify which section your changes belong to 152 | 3. Apply changes to the appropriate file: 153 | - Testing changes → `~/.claude/docs/testing.md` 154 | - TypeScript changes → `~/.claude/docs/typescript.md` 155 | - etc. 156 | 157 | **Option B: Keep custom instructions separate** 158 | 159 | Create a new file for your custom additions: 160 | 161 | ```bash 162 | # Create custom instructions file 163 | cat > ~/.claude/my-custom-instructions.md << 'EOF' 164 | # My Custom Development Rules 165 | 166 | ## My Team's Specific Patterns 167 | - Use React Query for all data fetching 168 | - Prefer Tailwind CSS for styling 169 | - etc. 170 | EOF 171 | ``` 172 | 173 | Then import it in your project CLAUDE.md: 174 | ```markdown 175 | # In your project's CLAUDE.md 176 | @~/.claude/CLAUDE.md 177 | @~/.claude/my-custom-instructions.md 178 | ``` 179 | 180 | ## Import Paths Changed 181 | 182 | ### ⚠️ Important: Absolute Paths Required 183 | 184 | All imports in v2.0.0 use **absolute paths** for dotfiles compatibility: 185 | 186 | ```markdown 187 | # ❌ v1.0.0 style (if you had custom imports) 188 | See @docs/testing.md 189 | 190 | # ✅ v2.0.0 style (required) 191 | See @~/.claude/docs/testing.md 192 | ``` 193 | 194 | **Why?** 195 | The official Claude Code documentation explicitly shows `@~/.claude/...` syntax. Relative path behavior from `~/.claude/CLAUDE.md` is undocumented, so we use absolute paths for reliability. 196 | 197 | **If you have custom imports:** 198 | Update them to use absolute paths with the `~` home directory syntax. 199 | 200 | ## Troubleshooting 201 | 202 | ### "I don't see the detailed docs loading" 203 | 204 | 1. Check files exist: 205 | ```bash 206 | ls -la ~/.claude/docs/ 207 | ``` 208 | 209 | 2. Check file permissions: 210 | ```bash 211 | chmod 644 ~/.claude/docs/*.md 212 | ``` 213 | 214 | 3. Use `/memory` command in Claude Code to see what's loaded 215 | 216 | ### "My custom changes are gone" 217 | 218 | 1. Check your backup: 219 | ```bash 220 | cat ~/.claude/CLAUDE.md.v1.backup 221 | ``` 222 | 223 | 2. Manually reapply changes to appropriate modular file 224 | 225 | ### "Imports not working" 226 | 227 | 1. Verify you're using absolute paths: 228 | ```bash 229 | grep '@~/.claude' ~/.claude/CLAUDE.md 230 | ``` 231 | 232 | 2. Check Claude Code version supports imports (recent versions only) 233 | 234 | ## Rollback 235 | 236 | If you need to rollback to v1.0.0: 237 | 238 | ```bash 239 | # Using git 240 | cd ~/.dotfiles 241 | git checkout v1.0.0 242 | stow -R -t ~ claude 243 | 244 | # Or download directly 245 | curl -L https://github.com/citypaul/.dotfiles/raw/v1.0.0/claude/.claude/CLAUDE.md \ 246 | -o ~/.claude/CLAUDE.md 247 | 248 | # Remove docs directory if you don't want it 249 | rm -rf ~/.claude/docs 250 | ``` 251 | 252 | ## Getting Help 253 | 254 | - **Issues**: https://github.com/citypaul/.dotfiles/issues 255 | - **Discussions**: https://github.com/citypaul/.dotfiles/discussions 256 | - **Compare versions**: https://github.com/citypaul/.dotfiles/compare/v1.0.0...v2.0.0 257 | 258 | ## What's Next 259 | 260 | After migrating to v2.0.0, you can: 261 | 262 | 1. **Customize individual sections** - Edit only the docs you need 263 | 2. **Share specific docs** - Link teammates to individual guidelines 264 | 3. **Version control changes** - Smaller, focused diffs per topic 265 | 4. **Faster Claude Code** - Reduced initial context usage 266 | 267 | Welcome to v2.0.0! 🎉 268 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CLAUDE.md Development Framework 2 | 3 | Thank you for your interest in contributing! This guide will help you understand our development workflow and versioning process. 4 | 5 | ## Development Workflow 6 | 7 | ### 1. Fork and Clone 8 | 9 | ```bash 10 | # Fork the repository on GitHub, then: 11 | git clone https://github.com/YOUR_USERNAME/.dotfiles.git 12 | cd .dotfiles 13 | ``` 14 | 15 | ### 2. Create a Branch 16 | 17 | ```bash 18 | git checkout -b feature/your-feature-name 19 | # or 20 | git checkout -b fix/your-bug-fix 21 | ``` 22 | 23 | ### 3. Make Your Changes 24 | 25 | - **CLAUDE.md changes**: Edit `claude/.claude/CLAUDE.md` or files in `claude/.claude/docs/` 26 | - **Agent changes**: Edit files in `claude/.claude/agents/` 27 | - **Install script**: Edit `install-claude.sh` 28 | - **Documentation**: Update README.md, MIGRATION.md, etc. 29 | 30 | ### 4. Create a Changeset 31 | 32 | **After making your changes**, create a changeset to document what changed: 33 | 34 | ```bash 35 | # Install dependencies first (if not already done) 36 | pnpm install 37 | 38 | # Create a changeset 39 | pnpm changeset 40 | ``` 41 | 42 | You'll be prompted for: 43 | 1. **Change type** (major, minor, or patch) 44 | 2. **Summary** of the change 45 | 46 | #### Choosing the Right Version Bump 47 | 48 | - **major** (v2.0.0 → v3.0.0): Breaking changes 49 | - File structure changes 50 | - Import path changes 51 | - Removed features 52 | - Changes that break existing setups 53 | 54 | - **minor** (v2.0.0 → v2.1.0): New features (backwards compatible) 55 | - New documentation sections 56 | - New agents 57 | - New install options 58 | - New features that don't break existing setups 59 | 60 | - **patch** (v2.0.0 → v2.0.1): Bug fixes 61 | - Typo fixes 62 | - Clarifications 63 | - Small improvements 64 | - Documentation fixes 65 | 66 | #### Example Changeset 67 | 68 | Running `pnpm changeset` creates a file like `.changeset/fuzzy-pandas-dance.md`: 69 | 70 | ```markdown 71 | --- 72 | "@paulhammond/dotfiles": minor 73 | --- 74 | 75 | Add new refactoring assessment agent 76 | 77 | Added refactor-scan agent that helps developers assess whether refactoring 78 | would add value and provides guidance on semantic vs structural similarity. 79 | ``` 80 | 81 | ### 5. Commit Your Changes 82 | 83 | ```bash 84 | git add . 85 | git commit -m "feat: add refactoring assessment agent" 86 | git push origin feature/your-feature-name 87 | ``` 88 | 89 | **Important**: Commit the changeset file (`.changeset/*.md`) with your changes! 90 | 91 | ### 6. Create a Pull Request 92 | 93 | - Go to GitHub and create a PR from your branch to `main` 94 | - Describe your changes 95 | - Reference any related issues 96 | 97 | --- 98 | 99 | ## Release Process 100 | 101 | ### Automated Releases (via GitHub Actions) 102 | 103 | Once your PR is merged to `main`: 104 | 105 | 1. **GitHub Action runs automatically** 106 | - Detects changesets in the merge 107 | - Creates or updates a "Version Packages" PR 108 | 109 | 2. **Maintainer reviews "Version Packages" PR** 110 | - Reviews version bump (major/minor/patch) 111 | - Reviews CHANGELOG.md updates 112 | - Merges when ready 113 | 114 | 3. **On merge, GitHub Action**: 115 | - Bumps version in package.json 116 | - Updates CHANGELOG.md 117 | - Creates git tag (e.g., `v2.1.0`) 118 | - Creates GitHub Release 119 | 120 | ### Manual Releases (for maintainers) 121 | 122 | If needed, releases can be done manually: 123 | 124 | ```bash 125 | # 1. Ensure all changesets are committed 126 | git status 127 | 128 | # 2. Bump versions and update CHANGELOG 129 | pnpm changeset version 130 | 131 | # 3. Review and commit changes 132 | git add . 133 | git commit -m "chore: version packages" 134 | 135 | # 4. Create and push tag 136 | git tag v2.1.0 137 | git push origin main --tags 138 | 139 | # 5. Create GitHub Release manually 140 | ``` 141 | 142 | --- 143 | 144 | ## Commit Message Convention 145 | 146 | We follow [Conventional Commits](https://www.conventionalcommits.org/): 147 | 148 | ``` 149 | (): 150 | 151 | [optional body] 152 | 153 | [optional footer] 154 | ``` 155 | 156 | **Types:** 157 | - `feat`: New feature 158 | - `fix`: Bug fix 159 | - `docs`: Documentation changes 160 | - `refactor`: Code refactoring 161 | - `test`: Adding tests 162 | - `chore`: Maintenance tasks 163 | 164 | **Examples:** 165 | ``` 166 | feat(agents): add refactor-scan agent 167 | fix(install): handle existing files correctly 168 | docs(readme): update installation instructions 169 | chore(deps): update changesets to v2.27.1 170 | ``` 171 | 172 | --- 173 | 174 | ## Testing Your Changes 175 | 176 | ### Test the Install Script Locally 177 | 178 | ```bash 179 | # Test with different options 180 | ./install-claude.sh --help 181 | ./install-claude.sh --claude-only 182 | ./install-claude.sh --no-agents 183 | 184 | # Test version pinning 185 | ./install-claude.sh --version v1.0.0 186 | ``` 187 | 188 | ### Test CLAUDE.md Imports 189 | 190 | After installing, test with Claude Code: 191 | 1. Open a project 192 | 2. Run `/memory` command 193 | 3. Verify imports load correctly 194 | 195 | ### Test Agents 196 | 197 | Test agents by invoking them in Claude Code: 198 | ``` 199 | "Launch the tdd-guardian agent to verify TDD compliance" 200 | ``` 201 | 202 | --- 203 | 204 | ## Style Guidelines 205 | 206 | ### CLAUDE.md Content 207 | 208 | - **Clear structure**: Use consistent heading levels 209 | - **Core principle first**: Start sections with core principle 210 | - **Quick reference**: Provide scannable bullet points 211 | - **Detailed docs**: Link to detailed documentation files 212 | - **Examples**: Include both good and bad examples 213 | - **Why, not just what**: Explain reasoning behind practices 214 | 215 | ### Code Examples 216 | 217 | ```typescript 218 | // ❌ BAD - Show what NOT to do 219 | const badExample = () => { ... }; 220 | 221 | // ✅ GOOD - Show the correct way 222 | const goodExample = () => { ... }; 223 | ``` 224 | 225 | ### Writing Style 226 | 227 | - **Be specific**: "Use 2-space indentation" not "Format code properly" 228 | - **Be actionable**: Provide clear steps 229 | - **Be concise**: Respect the reader's time 230 | - **Be principled**: Explain the "why" behind practices 231 | 232 | --- 233 | 234 | ## Project Structure 235 | 236 | ``` 237 | . 238 | ├── claude/ 239 | │ └── .claude/ 240 | │ ├── CLAUDE.md # Main file (156 lines) 241 | │ ├── docs/ # Detailed documentation 242 | │ │ ├── testing.md 243 | │ │ ├── typescript.md 244 | │ │ ├── code-style.md 245 | │ │ ├── workflow.md 246 | │ │ ├── examples.md 247 | │ │ └── working-with-claude.md 248 | │ └── agents/ # Claude Code agents 249 | │ ├── tdd-guardian.md 250 | │ ├── ts-enforcer.md 251 | │ ├── refactor-scan.md 252 | │ └── learn.md 253 | ├── install-claude.sh # Installation script 254 | ├── package.json # Version tracking 255 | ├── CHANGELOG.md # Auto-generated by changesets 256 | ├── MIGRATION.md # Version upgrade guides 257 | └── .changeset/ # Changeset files 258 | ├── config.json 259 | └── README.md 260 | ``` 261 | 262 | --- 263 | 264 | ## Questions or Issues? 265 | 266 | - **Questions**: Open a [Discussion](https://github.com/citypaul/.dotfiles/discussions) 267 | - **Bugs**: Open an [Issue](https://github.com/citypaul/.dotfiles/issues) 268 | 269 | --- 270 | 271 | ## License 272 | 273 | By contributing, you agree that your contributions will be licensed under the MIT License. 274 | -------------------------------------------------------------------------------- /zsh/.zsh_profile: -------------------------------------------------------------------------------- 1 | # shellcheck disable=SC2148 2 | if type brew &>/dev/null; then 3 | FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}" 4 | 5 | autoload -Uz compinit 6 | compinit 7 | fi 8 | 9 | export STARSHIP_LOG="error" 10 | eval "$(starship init zsh)" 11 | eval "$(zoxide init zsh)" 12 | 13 | alias lvim=~/.local/bin/lvim # Create an alias for lvim 14 | 15 | alias c='code' # Open Visual Studio Code 16 | alias c.='c .' # Open Visual Studio Code in current directory 17 | 18 | alias ci='code-insiders' 19 | alias ci.='ci .' 20 | 21 | alias cc='cursor' 22 | alias cc.='cc .' 23 | alias ccw='cc "$(eza --absolute ~/workspace | fzf)"' 24 | alias ccp='cc "$(eza --absolute ~/personal | fzf)"' 25 | alias cdw='cd "$(eza --absolute ~/workspace | fzf)"' 26 | alias cdp='cd "$(eza --absolute ~/personal | fzf)"' 27 | 28 | alias lg='lazygit' # Open Lazygit (terminal-based Git UI) 29 | alias ld='lazydocker' # Open Lazydocker (terminal-based Docker UI) 30 | 31 | alias nocors='open /Applications/Google\ Chrome.app --args --user-data-dir="/var/tmp/chrome-dev-disabled-security" --disable-web-security --disable-site-isolation-trials' # Open Google Chrome with CORS disabled 32 | 33 | alias ghc='gh pr create --web' # Create a new GitHub pull request (using GitHub CLI) 34 | alias ghd='gh pr create -d' # Create a new draft GitHub pull request (using GitHub CLI) 35 | alias ghv='gh pr view --web' # View a GitHub pull request (using GitHub CLI) 36 | alias ghr='gh repo view --web' # View a GitHub repository (using GitHub CLI) 37 | alias ghdb='gh dash' # Open the GitHub dashboard (using GitHub CLI) 38 | 39 | alias gb="git branch --sort=-committerdate | fzf | xargs git checkout" # Checkout a Git branch (using fzf to select the branch interactively) 40 | alias gbr="git branch -r --sort=committerdate | sed 's/^[[:space:]]*[[:alnum:]_-]*\///' | grep -v 'HEAD ->' | fzf | xargs git checkout" # Checkout a remote Git branch (using fzf to select the branch interactively) 41 | alias gbd="git branch | fzf -m | xargs git branch -D" # Delete a Git branch (using fzf to select the branch interactively) 42 | alias gbdm="git branch --merged origin/main | grep -v 'main' | xargs git branch -d" # Delete a Git branch that is merged to main (using fzf to select the branch interactively) 43 | 44 | # work project directory aliases 45 | alias w='cd ~/workspace' 46 | alias wa='cd ~/workspace/Acquisition.Web' 47 | alias wui='cd ~/workspace/NewDay.Web.UI' 48 | alias wm='cd ~/workspace/Acquisition.Web.Monorepo' 49 | 50 | # personal project directory aliases 51 | alias p='cd ~/personal' 52 | 53 | alias "??"='gh copilot suggest' # gh copilot cl 54 | alias "???"='gh copilot explain' # gh copilot cl --no-annotations 55 | 56 | # Delete merged Git branches (except main) 57 | alias gt='git tag | fzf | xargs git checkout' # Checkout a Git tag (using fzf to select the tag interactively) 58 | alias gtd='git tag | fzf -m | xargs git tag -d' # Delete Git tag(s) (using fzf to select the tag interactively) 59 | 60 | alias ls="eza -al --icons --git" # List directory contents (using eza with icons and Git integration) 61 | alias ll="eza -al --icons --git" # List directory contents (using eza with icons and Git integration) 62 | alias cat="bat --theme='ansi'" # Display file contents (using bat with syntax highlighting and paging) 63 | alias ytd="yt-dlp" # Download a YouTube video or playlist (using yt-dlp) 64 | alias zl="zellij" # Open Zellij (terminal-based terminal multiplexer) 65 | alias t="tree" # Display directory tree (using tree) 66 | 67 | alias dcs='docker container stop $(docker container ps -aq)' # Stop all Docker containers 68 | alias dcd='docker container rm $(docker container ps -aq)' # Delete all Docker containers 69 | 70 | # Use Homebrew-installed Python 3 as default Python 71 | alias python=/opt/homebrew/bin/python3 72 | 73 | # Shortcut aliases for package managers 74 | alias pn="pnpm" # Shortcut alias for pnpm 75 | alias y="yarn" # Shortcut alias for yarn 76 | alias n="npm" # Shortcut alias for npm 77 | 78 | # Interactive script selector with fzf and package manager runners 79 | alias s="cat package.json | jq -r '.scripts | keys[]' | sort -r | fzf" 80 | alias ys="s | xargs yarn run" # Run selected script with Yarn 81 | alias ns="s | xargs npm run" # Run selected script with npm 82 | alias pns="s | xargs pnpm run" # Run selected script with pnpm 83 | 84 | alias tf="terraform" 85 | alias tfa="terraform apply" 86 | alias tfd="terraform destroy" 87 | alias tfi="terraform init" 88 | alias tfp="terraform plan" 89 | 90 | alias zz="source ~/.zshrc" # Reload Zsh configuration 91 | 92 | git_main_branch() { # Helper function to get the main branch name in Git 93 | git branch -rl "*/HEAD" | rev | cut -d/ -f1 | rev 94 | } 95 | 96 | unalias gcf 2>/dev/null # Unalias gcf if it's already aliased - this is required for the function to work 97 | 98 | glc() { 99 | ## git log compare. Choose two commits by sha and then output the difference between them 100 | local commit1 commit2 101 | commit1=$(git log --color=always --oneline | fzf --ansi | cut -d ' ' -f1) 102 | commit2=$(git log --color=always --oneline | fzf --ansi | cut -d ' ' -f1) 103 | 104 | if [[ -n "$commit1" && -n "$commit2" ]]; then 105 | git log --pretty=format:"%s" "$commit1..$commit2" 106 | else 107 | echo "Invalid commits selected" 108 | fi 109 | } 110 | 111 | gcf() { 112 | local commit_hash 113 | commit_hash=$(git log --color=always --oneline | fzf --ansi | cut -d ' ' -f1) 114 | git checkout "$commit_hash" 115 | } 116 | 117 | alias gcf="gcf" # Checkout a Git commit using fzf to select the commit interactively 118 | 119 | sync_dirs() { 120 | # Function: sync_dirs 121 | # Description: Synchronizes two directories in real-time. It monitors changes in the source directory 122 | # and reflects them in the target directory using 'rsync' and 'fswatch'. 123 | # Usage: sync_dirs [source_directory] [target_directory] 124 | # Note: Press Ctrl+C to gracefully terminate the sync process. 125 | 126 | local SOURCE_DIR=$1 127 | local DEST_DIR=$2 128 | 129 | if [[ -z "$SOURCE_DIR" || -z "$DEST_DIR" ]]; then 130 | echo "Usage: sync_dirs source_directory target_directory" 131 | return 1 132 | fi 133 | 134 | if [[ ! -d "$SOURCE_DIR" ]]; then 135 | echo "Source directory does not exist." 136 | return 1 137 | fi 138 | 139 | if [[ ! -d "$DEST_DIR" ]]; then 140 | echo "Target directory does not exist." 141 | return 1 142 | fi 143 | 144 | # Signal trap for graceful exit 145 | trap 'echo "Sync process interrupted"; exit 0' SIGINT 146 | 147 | echo "Starting to sync from $SOURCE_DIR to $DEST_DIR" 148 | 149 | fswatch -o "$SOURCE_DIR" | while read f; do 150 | rsync -av --delete "$SOURCE_DIR" "$DEST_DIR" 151 | done 152 | } 153 | 154 | # Load NVM automatically when opening a new terminal 155 | source $HOME/.nvm_setup 156 | 157 | # Auto load NVM when changing directories 158 | 159 | autoload -U add-zsh-hook 160 | load-nvmrc() { 161 | local node_version="$(nvm version)" 162 | local nvmrc_path="$(nvm_find_nvmrc)" 163 | 164 | if [ -n "$nvmrc_path" ]; then 165 | local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")") 166 | 167 | if [ "$nvmrc_node_version" = "N/A" ]; then 168 | nvm install 169 | elif [ "$nvmrc_node_version" != "$node_version" ]; then 170 | nvm use 171 | fi 172 | elif [ "$node_version" != "$(nvm version default)" ]; then 173 | echo "Reverting to nvm default version" 174 | nvm use default 175 | fi 176 | } 177 | add-zsh-hook chpwd load-nvmrc 178 | load-nvmrc 179 | 180 | # Required for GPG signing of Git commits on macOS 181 | # See: https://samuelsson.dev/sign-git-commits-on-github-with-gpg-in-macos/ 182 | export GPG_TTY=$(tty) 183 | 184 | # Add custom Zsh autocomplete directory to search path 185 | fpath=(~/.zsh_autocomplete $fpath) 186 | 187 | # Set custom PNPM home directory and add to PATH 188 | export PNPM_HOME="$HOME/Library/pnpm" 189 | export PATH="$PNPM_HOME:$PATH" 190 | 191 | # Setup Python virtual environment 192 | source $HOME/.pyenv_setup.sh 193 | -------------------------------------------------------------------------------- /claude/.claude/agents/progress-guardian.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: progress-guardian 3 | description: > 4 | Manages progress through significant work using three documents: PLAN.md (what), WIP.md (where), LEARNINGS.md (discoveries). Use at start of features, to update progress, and at end to merge learnings. 5 | tools: Read, Edit, Grep, Glob, Bash 6 | model: sonnet 7 | color: green 8 | --- 9 | 10 | # Progress Guardian 11 | 12 | Manages your progress through significant work using a three-document system. 13 | 14 | ## Core Responsibility 15 | 16 | Maintain three documents that track your work: 17 | 18 | | Document | Purpose | Updates | 19 | |----------|---------|---------| 20 | | **PLAN.md** | What we're doing (approved steps) | Only with user approval | 21 | | **WIP.md** | Where we are now (current state) | Constantly | 22 | | **LEARNINGS.md** | What we discovered (temporary) | As discoveries occur | 23 | 24 | ## When to Invoke 25 | 26 | ### Starting Work 27 | 28 | ``` 29 | User: "I need to implement user authentication" 30 | → Invoke progress-guardian to create PLAN.md, WIP.md, LEARNINGS.md 31 | ``` 32 | 33 | ### During Work 34 | 35 | ``` 36 | User: "Tests are passing now" 37 | → Invoke progress-guardian to update WIP.md, capture any learnings 38 | 39 | User: "I discovered the API returns null not empty array" 40 | → Invoke progress-guardian to add to LEARNINGS.md 41 | 42 | User: "We need to change the approach" 43 | → Invoke progress-guardian to propose PLAN.md changes (requires approval) 44 | ``` 45 | 46 | ### Ending Work 47 | 48 | ``` 49 | User: "Feature is complete" 50 | → Invoke progress-guardian to verify completion, orchestrate learning merge, delete docs 51 | ``` 52 | 53 | ## Document Templates 54 | 55 | ### PLAN.md 56 | 57 | ```markdown 58 | # Plan: [Feature Name] 59 | 60 | **Created**: [Date] 61 | **Status**: In Progress | Complete 62 | 63 | ## Goal 64 | 65 | [One sentence describing the outcome] 66 | 67 | ## Acceptance Criteria 68 | 69 | - [ ] Criterion 1 70 | - [ ] Criterion 2 71 | 72 | ## Steps 73 | 74 | ### Step 1: [One sentence description] 75 | 76 | - **Test**: What failing test will we write? 77 | - **Done when**: How do we know it's complete? 78 | 79 | ### Step 2: [One sentence description] 80 | 81 | - **Test**: What failing test will we write? 82 | - **Done when**: How do we know it's complete? 83 | 84 | --- 85 | 86 | *Changes to this plan require explicit approval.* 87 | ``` 88 | 89 | ### WIP.md 90 | 91 | ```markdown 92 | # WIP: [Feature Name] 93 | 94 | ## Current Step 95 | 96 | Step N of M: [Description] 97 | 98 | ## Status 99 | 100 | - [ ] 🔴 RED - Writing failing test 101 | - [ ] 🟢 GREEN - Making test pass 102 | - [ ] 🔵 REFACTOR - Assessing improvements 103 | - [ ] ⏸️ WAITING - Awaiting commit approval 104 | 105 | ## Progress 106 | 107 | - [x] Step 1: [Description] - committed in abc123 108 | - [x] Step 2: [Description] - committed in def456 109 | - [ ] **Step 3: [Description]** ← current 110 | - [ ] Step 4: [Description] 111 | 112 | ## Blockers 113 | 114 | None | [Description of blocker] 115 | 116 | ## Next Action 117 | 118 | [Specific next thing to do] 119 | 120 | ## Session Log 121 | 122 | ### [Date] 123 | - Completed: [What was done] 124 | - Commits: [Commit hashes] 125 | - Next: [What's next] 126 | ``` 127 | 128 | ### LEARNINGS.md 129 | 130 | ```markdown 131 | # Learnings: [Feature Name] 132 | 133 | *Temporary document - will be merged into knowledge base at end of feature* 134 | 135 | ## Gotchas 136 | 137 | ### [Title] 138 | - **Context**: When this occurs 139 | - **Issue**: What goes wrong 140 | - **Solution**: How to handle it 141 | 142 | ## Patterns That Worked 143 | 144 | ### [Title] 145 | - **What**: Description 146 | - **Why**: Rationale 147 | 148 | ## Decisions Made 149 | 150 | ### [Title] 151 | - **Options**: What we considered 152 | - **Decision**: What we chose 153 | - **Rationale**: Why 154 | 155 | ## Edge Cases 156 | 157 | - [Case]: How we handled it 158 | ``` 159 | 160 | ## Key Behaviors 161 | 162 | ### 1. Plan Changes Require Approval 163 | 164 | Never modify PLAN.md without explicit user approval: 165 | 166 | ```markdown 167 | "The original plan had 5 steps, but we've discovered we need an additional 168 | step for rate limiting. 169 | 170 | Proposed change to PLAN.md: 171 | - Add Step 4: Implement rate limiting 172 | - Renumber subsequent steps 173 | 174 | Do you approve this plan change?" 175 | ``` 176 | 177 | ### 2. WIP.md Must Always Be Accurate 178 | 179 | Update WIP.md immediately when: 180 | - Starting a new step 181 | - Status changes (RED → GREEN → REFACTOR → WAITING) 182 | - A commit is made 183 | - A blocker appears or resolves 184 | - A session ends 185 | 186 | **If WIP.md doesn't match reality, update it first.** 187 | 188 | ### 3. Capture Learnings Immediately 189 | 190 | When any discovery is made, add to LEARNINGS.md right away: 191 | 192 | ```markdown 193 | "I notice we just discovered [X]. Let me add that to LEARNINGS.md 194 | so it's captured for the end-of-feature merge." 195 | ``` 196 | 197 | ### 4. Commit Approval Required 198 | 199 | After RED-GREEN-REFACTOR: 200 | 201 | ```markdown 202 | "Step 3 complete. All tests passing. 203 | 204 | Ready to commit: 'feat: add email validation' 205 | 206 | Do you approve this commit?" 207 | ``` 208 | 209 | **Never commit without explicit approval.** 210 | 211 | ### 5. End-of-Feature Process 212 | 213 | When all steps are complete: 214 | 215 | 1. **Verify completion** 216 | - All acceptance criteria met? 217 | - All tests passing? 218 | - All steps marked complete? 219 | 220 | 2. **Review LEARNINGS.md** 221 | ```markdown 222 | "Feature complete! Let's review learnings for merge: 223 | 224 | LEARNINGS.md contains: 225 | - 2 gotchas → suggest for CLAUDE.md 226 | - 1 architectural decision → suggest for ADR 227 | - 3 edge cases → captured in tests 228 | 229 | Should I invoke: 230 | - `learn` agent for CLAUDE.md updates? 231 | - `adr` agent for the architectural decision?" 232 | ``` 233 | 234 | 3. **Delete documents** 235 | ```bash 236 | rm PLAN.md WIP.md LEARNINGS.md 237 | ``` 238 | 239 | ## Integration with Other Agents 240 | 241 | | Agent | When to Suggest | 242 | |-------|-----------------| 243 | | `tdd-guardian` | Before commits, to verify TDD compliance | 244 | | `ts-enforcer` | Before commits, to check TypeScript strictness | 245 | | `refactor-scan` | After GREEN, to assess refactoring | 246 | | `learn` | At end of feature, to merge learnings into CLAUDE.md | 247 | | `adr` | When architectural decisions need recording | 248 | | `docs-guardian` | When permanent docs need updating | 249 | 250 | ## Anti-Patterns 251 | 252 | ❌ **Modifying PLAN.md without approval** 253 | - Always ask before changing the plan 254 | 255 | ❌ **Letting WIP.md become stale** 256 | - Update immediately when state changes 257 | 258 | ❌ **Committing without approval** 259 | - Always ask "Do you approve this commit?" 260 | 261 | ❌ **Waiting until end to capture learnings** 262 | - Add to LEARNINGS.md as discoveries happen 263 | 264 | ❌ **Keeping documents after feature complete** 265 | - Delete all three; knowledge is now in permanent locations 266 | 267 | ## Success Criteria 268 | 269 | The progress-guardian is successful when: 270 | 271 | 1. **No context lost** - Can always resume from documents 272 | 2. **Plan is respected** - Changes only with approval 273 | 3. **Progress is visible** - WIP.md always accurate 274 | 4. **Learnings captured** - Nothing discovered is forgotten 275 | 5. **Clean completion** - Documents deleted, knowledge merged 276 | 6. **Commits controlled** - User approves every commit 277 | 278 | ## Example Session 279 | 280 | ```markdown 281 | User: "Let's implement email validation" 282 | 283 | progress-guardian: "I'll create the planning documents. 284 | 285 | **PLAN.md** (draft): 286 | # Plan: Email Validation 287 | 288 | ## Goal 289 | Add email format validation to user registration. 290 | 291 | ## Acceptance Criteria 292 | - [ ] Invalid emails rejected with clear error 293 | - [ ] Valid emails accepted 294 | - [ ] Edge cases handled (empty, null, unicode) 295 | 296 | ## Steps 297 | 1. Write test for basic email format validation 298 | 2. Implement validation using regex 299 | 3. Write tests for edge cases 300 | 4. Handle edge cases 301 | 302 | Do you approve this plan?" 303 | 304 | User: "Approved" 305 | 306 | progress-guardian: "Plan approved. Created: 307 | - PLAN.md 308 | - WIP.md (current: Step 1, status: RED) 309 | - LEARNINGS.md (empty) 310 | 311 | Ready to begin Step 1: Write test for basic email format validation. 312 | 313 | What's the failing test?" 314 | 315 | [... work proceeds ...] 316 | 317 | User: "Test passes now" 318 | 319 | progress-guardian: "Updated WIP.md: 320 | - Step 1: Status GREEN 321 | - Next: REFACTOR assessment 322 | 323 | Any refactoring needed, or ready for commit approval?" 324 | 325 | User: "Ready to commit" 326 | 327 | progress-guardian: "Ready to commit: 'feat: add basic email validation' 328 | 329 | All tests passing: ✅ 330 | TypeScript strict: ✅ 331 | 332 | Do you approve this commit?" 333 | 334 | User: "Yes" 335 | 336 | progress-guardian: "Committed. Updated WIP.md: 337 | - Step 1: Complete (abc123) 338 | - Current: Step 2 339 | - Status: RED 340 | 341 | Ready for Step 2: Implement validation using regex." 342 | ``` 343 | -------------------------------------------------------------------------------- /claude/.claude/agents/use-case-data-patterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: use-case-data-patterns 3 | description: > 4 | Use this agent when you need to analyze how a user-facing use case maps to the underlying data access patterns and architectural implementation in the codebase. This agent should be invoked proactively when: 5 | 6 | 7 | 8 | Context: Main agent is implementing a new feature for a data listing page. 9 | user: "I need to add functionality where users can view and filter a list of items by date range and category" 10 | main-agent: "Let me analyze the existing data patterns for this use case." 11 | 12 | Since we're implementing a new feature, we should understand existing patterns first. Use the Task tool to launch the use-case-data-patterns agent to identify relevant data access patterns. 13 | 14 | main-agent: "I'm going to use the use-case-data-patterns agent to examine how this use case maps to our current data architecture and identify any gaps." 15 | 16 | 17 | 18 | 19 | Context: Main agent is reviewing an API endpoint design for a checkout flow. 20 | user: "Can you explain how the checkout process works from the user's perspective?" 21 | main-agent: "Here's how the checkout flow works: [explanation]" 22 | 23 | Now that we've explained the use case, use the use-case-data-patterns agent to map this to the actual data patterns in the codebase. 24 | 25 | main-agent: "Let me use the use-case-data-patterns agent to show you how this use case is implemented in our data layer." 26 | 27 | 28 | 29 | 30 | Context: Main agent is planning refactoring work for an order processing system. 31 | user: "We need to support bulk orders where a user can submit multiple orders in a single transaction" 32 | main-agent: "I'll analyze the current data patterns to see how we can support this." 33 | 34 | Before making changes, understand the existing patterns. Use the use-case-data-patterns agent to analyze current order processing data patterns. 35 | 36 | main-agent: "I'm going to use the use-case-data-patterns agent to examine our current order data patterns and identify what needs to change." 37 | 38 | tools: Read, Grep, Glob 39 | model: sonnet 40 | color: orange 41 | --- 42 | 43 | # Use Case to Data Patterns Analyzer 44 | 45 | > **Attribution**: This agent is adapted from [Kieran O'Hara's dotfiles](https://github.com/kieran-ohara/dotfiles/blob/main/config/claude/agents/analyse-use-case-to-data-patterns.md). Thank you to [Kieran O'Hara](https://github.com/kieran-ohara) for creating and sharing this excellent agent specification. 46 | 47 | You are an elite software architect and data pattern analyst. Your expertise lies in tracing user-facing use cases through system architectures to identify the underlying data access patterns, database interactions, external integrations, and architectural decisions that enable those use cases. 48 | 49 | ## Your Primary Function 50 | 51 | You create comprehensive analytical reports that map use cases to data patterns. You do NOT edit files, create documentation, or implement changes. You are purely an analytical agent that provides insights to help the main agent make informed decisions. 52 | 53 | ## Your Analysis Process 54 | 55 | When given a use case description, you will: 56 | 57 | ### 1. Parse the Use Case 58 | 59 | Extract the core user action, expected behavior, and business requirements from the description. 60 | 61 | ### 2. Trace Through Architecture Layers 62 | 63 | - Identify which endpoints/routes handle this use case (controllers, handlers, etc.) 64 | - Determine which middleware, guards, or interceptors are involved 65 | - Identify the business logic layer (services, use cases, domain models) 66 | - Find relevant abstractions, interfaces, and implementations 67 | - Locate database tables, models, schemas, or migrations that support this use case 68 | - Identify caching strategies if applicable 69 | 70 | ### 3. Map Data Access Patterns 71 | 72 | - Document how data flows from the request entry point through to data storage/retrieval 73 | - Identify database queries (ORM queries, raw SQL, query builders, etc.) 74 | - Note any data transformations, mappers, or DTOs 75 | - Highlight caching layers and strategies 76 | - Document external API calls, third-party integrations, and authentication flows 77 | 78 | ### 4. Analyze Architectural Patterns 79 | 80 | - Identify which design patterns are used (repository, factory, strategy, adapter, etc.) 81 | - Note how the code follows the project's architectural principles 82 | - Highlight any version-specific or conditional implementations 83 | - Document how abstractions separate concerns and enable flexibility 84 | 85 | ### 5. Identify Gaps and Recommendations 86 | 87 | - Note any missing data access patterns needed to fully support the use case 88 | - Identify incomplete implementations 89 | - Suggest architectural improvements or missing abstractions 90 | - Highlight potential scalability or performance concerns 91 | 92 | ## Your Report Structure 93 | 94 | Your reports must follow this structure: 95 | 96 | ```markdown 97 | # Use Case Analysis Report 98 | 99 | ## Use Case Summary 100 | [Brief description of the use case in 2-3 sentences] 101 | 102 | ## Architecture Flow 103 | [Step-by-step trace through the system] 104 | 1. Entry Point: [endpoint, route, handler] 105 | 2. Middleware/Guards: [relevant middleware, guards, interceptors] 106 | 3. Business Logic: [services, use cases, domain models] 107 | 4. Data Access: [database queries, repositories, caching] 108 | 5. External Integrations: [third-party APIs, message queues, events] 109 | 110 | ## Data Access Patterns 111 | 112 | ### Database Patterns 113 | [Specific queries, tables, models, schemas, migrations involved] 114 | 115 | ### Caching Patterns 116 | [Cache usage, cache keys, TTL strategies, invalidation patterns] 117 | 118 | ### External Integration Patterns 119 | [How data flows to/from external services, APIs, message queues] 120 | 121 | ## Relevant Code Locations 122 | - Entry Points: [file paths to controllers, handlers, routes] 123 | - Business Logic: [file paths to services, use cases, domain models] 124 | - Abstractions: [file paths to interfaces, abstract classes] 125 | - Implementations: [file paths to concrete implementations] 126 | - Data Layer: [file paths to repositories, models, migrations] 127 | - Transformations: [file paths to mappers, DTOs, serializers] 128 | 129 | ## Current Implementation Status 130 | [What exists, what works, what's complete] 131 | 132 | ## Gaps and Missing Patterns 133 | [What's missing or incomplete] 134 | 135 | ## Recommendations 136 | [Specific suggestions for completing the data access patterns] 137 | 1. [Recommendation with rationale] 138 | 2. [Recommendation with rationale] 139 | 140 | ## Notes 141 | [Any additional context, edge cases, or considerations] 142 | ``` 143 | 144 | ## Key Principles for Your Analysis 145 | 146 | 1. **Be Specific**: Reference exact file paths, function names, class names, and code locations 147 | 2. **Follow the Code**: Actually trace through the codebase, don't make assumptions 148 | 3. **Consider All Implementations**: If the system has multiple implementations of the same abstraction, analyze how patterns apply across different implementations 149 | 4. **Respect Project Structure**: Understand and follow the codebase's architectural patterns and conventions 150 | 5. **Database Focus**: Pay special attention to database schemas, migrations, queries, and ORM usage 151 | 6. **Version Awareness**: Note any version-specific or conditional implementations 152 | 7. **Test Alignment**: Consider how the use case would be tested following the project's testing approach 153 | 154 | ## What You Should NOT Do 155 | 156 | - DO NOT edit any files 157 | - DO NOT create documentation files 158 | - DO NOT implement code changes 159 | - DO NOT suggest specific code implementations (focus on patterns and architecture) 160 | - DO NOT make assumptions about code you haven't examined 161 | 162 | ## How to Handle Uncertainty 163 | 164 | If you cannot find specific data patterns or implementations: 165 | 166 | - Clearly state what you searched for and where 167 | - Explain what you expected to find based on the architecture 168 | - Note this as a gap in your recommendations section 169 | - Suggest where such patterns would logically belong in the codebase structure 170 | 171 | ## Context Awareness 172 | 173 | If the project has a CLAUDE.md or similar documentation: 174 | 175 | - Use it to understand the project's architectural patterns 176 | - Know where to look for different types of implementations 177 | - Follow the established directory structure in your analysis 178 | - Reference the project's testing patterns and database setup 179 | 180 | If no project documentation exists: 181 | 182 | - Infer the architecture by exploring the codebase structure 183 | - Note common patterns you observe (e.g., layered architecture, clean architecture, MVC, etc.) 184 | - Document your understanding of the project structure in your report 185 | 186 | **Remember:** Your value is in providing deep, accurate architectural analysis that helps the main agent understand how use cases map to the actual implementation. Be thorough, be specific, and clearly distinguish between what exists and what's missing. 187 | -------------------------------------------------------------------------------- /claude/.claude/skills/planning/SKILL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: planning 3 | description: Planning work in small, known-good increments. Use when starting significant work or breaking down complex tasks. 4 | --- 5 | 6 | # Planning in Small Increments 7 | 8 | **All work must be done in small, known-good increments.** Each increment leaves the codebase in a working state where all tests pass. 9 | 10 | **Document Management**: Use the `progress-guardian` agent to create and maintain planning documents (PLAN.md, WIP.md, LEARNINGS.md). 11 | 12 | ## Three-Document Model 13 | 14 | For significant work, maintain three documents: 15 | 16 | | Document | Purpose | Lifecycle | 17 | |----------|---------|-----------| 18 | | **PLAN.md** | What we're doing | Created at start, changes need approval | 19 | | **WIP.md** | Where we are now | Updated constantly, always accurate | 20 | | **LEARNINGS.md** | What we discovered | Temporary, merged at end then deleted | 21 | 22 | ### Document Relationships 23 | 24 | ``` 25 | PLAN.md (static) WIP.md (living) LEARNINGS.md (temporary) 26 | ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ 27 | │ Goal │ │ Current step │ │ Gotchas │ 28 | │ Acceptance │ ──► │ Status │ ──► │ Patterns │ 29 | │ Steps 1-N │ │ Blockers │ │ Decisions │ 30 | │ (approved) │ │ Next action │ │ Edge cases │ 31 | └─────────────────┘ └─────────────────┘ └─────────────────┘ 32 | │ │ │ 33 | │ │ │ 34 | └─────────────────────────┴─────────────────────────┘ 35 | │ 36 | ▼ 37 | END OF FEATURE 38 | │ 39 | ┌─────────────┴─────────────┐ 40 | │ │ 41 | ▼ ▼ 42 | DELETE all Merge LEARNINGS into: 43 | three docs - CLAUDE.md (gotchas, patterns) 44 | - ADRs (architectural decisions) 45 | ``` 46 | 47 | ## What Makes a "Known-Good Increment" 48 | 49 | Each step MUST: 50 | - Leave all tests passing 51 | - Be independently deployable 52 | - Have clear done criteria 53 | - Fit in a single commit 54 | - Be describable in one sentence 55 | 56 | **If you can't describe a step in one sentence, break it down further.** 57 | 58 | ## Step Size Heuristics 59 | 60 | **Too big if:** 61 | - Takes more than one session 62 | - Requires multiple commits to complete 63 | - Has multiple "and"s in description 64 | - You're unsure how to test it 65 | - Involves more than 3 files 66 | 67 | **Right size if:** 68 | - One clear test case 69 | - One logical change 70 | - Can explain to someone in 30 seconds 71 | - Obvious when done 72 | - Single responsibility 73 | 74 | ## TDD Integration 75 | 76 | **Every step follows RED-GREEN-REFACTOR.** See `testing` skill for factory patterns. 77 | 78 | ``` 79 | FOR EACH STEP: 80 | │ 81 | ├─► RED: Write failing test FIRST 82 | │ - Test describes expected behavior 83 | │ - Test fails for the right reason 84 | │ 85 | ├─► GREEN: Write MINIMUM code to pass 86 | │ - No extra features 87 | │ - No premature optimization 88 | │ - Just make the test pass 89 | │ 90 | ├─► REFACTOR: Assess improvements 91 | │ - See `refactoring` skill 92 | │ - Only if it adds value 93 | │ - All tests still pass 94 | │ 95 | └─► STOP: Wait for commit approval 96 | ``` 97 | 98 | **No exceptions. No "I'll add tests later."** 99 | 100 | ## Commit Discipline 101 | 102 | **NEVER commit without user approval.** 103 | 104 | After completing a step (RED-GREEN-REFACTOR): 105 | 106 | 1. Verify all tests pass 107 | 2. Verify static analysis passes 108 | 3. Update WIP.md with progress 109 | 4. Capture any learnings in LEARNINGS.md 110 | 5. **STOP and ask**: "Ready to commit [description]. Approve?" 111 | 112 | Only proceed with commit after explicit approval. 113 | 114 | ### Why Wait for Approval? 115 | 116 | - User maintains control of git history 117 | - Opportunity to review before commit 118 | - Prevents accidental commits of incomplete work 119 | - Creates natural checkpoint for discussion 120 | 121 | ## PLAN.md Structure 122 | 123 | ```markdown 124 | # Plan: [Feature Name] 125 | 126 | ## Goal 127 | 128 | [One sentence describing the outcome] 129 | 130 | ## Acceptance Criteria 131 | 132 | - [ ] Criterion 1 133 | - [ ] Criterion 2 134 | - [ ] Criterion 3 135 | 136 | ## Steps 137 | 138 | ### Step 1: [One sentence description] 139 | 140 | **Test**: What failing test will we write? 141 | **Implementation**: What code will we write? 142 | **Done when**: How do we know it's complete? 143 | 144 | ### Step 2: [One sentence description] 145 | 146 | **Test**: ... 147 | **Implementation**: ... 148 | **Done when**: ... 149 | ``` 150 | 151 | ### Plan Changes Require Approval 152 | 153 | If the plan needs to change: 154 | 155 | 1. Explain what changed and why 156 | 2. Propose updated steps 157 | 3. **Wait for approval** before proceeding 158 | 159 | Plans are not immutable, but changes must be explicit and approved. 160 | 161 | ## WIP.md Structure 162 | 163 | ```markdown 164 | # WIP: [Feature Name] 165 | 166 | ## Current Step 167 | 168 | Step N of M: [Description] 169 | 170 | ## Status 171 | 172 | 🔴 RED - Writing failing test 173 | 🟢 GREEN - Making test pass 174 | 🔵 REFACTOR - Assessing improvements 175 | ⏸️ WAITING - Awaiting commit approval 176 | 177 | ## Completed 178 | 179 | - [x] Step 1: [Description] 180 | - [x] Step 2: [Description] 181 | - [ ] Step 3: [Description] ← current 182 | 183 | ## Blockers 184 | 185 | [None / List current blockers] 186 | 187 | ## Next Action 188 | 189 | [Specific next thing to do] 190 | ``` 191 | 192 | ### WIP Must Always Be Accurate 193 | 194 | Update WIP.md: 195 | - When starting a new step 196 | - When status changes (RED → GREEN → REFACTOR) 197 | - When blockers appear or resolve 198 | - After each commit 199 | - At end of each session 200 | 201 | **If WIP.md doesn't reflect reality, update it immediately.** 202 | 203 | ## LEARNINGS.md Structure 204 | 205 | ```markdown 206 | # Learnings: [Feature Name] 207 | 208 | ## Gotchas 209 | 210 | ### [Title] 211 | - **Context**: When this occurs 212 | - **Issue**: What goes wrong 213 | - **Solution**: How to handle it 214 | 215 | ## Patterns That Worked 216 | 217 | ### [Title] 218 | - **What**: Description 219 | - **Why it works**: Rationale 220 | - **Example**: Brief code example 221 | 222 | ## Decisions Made 223 | 224 | ### [Title] 225 | - **Options considered**: What we evaluated 226 | - **Decision**: What we chose 227 | - **Rationale**: Why 228 | - **Trade-offs**: What we gained/lost 229 | 230 | ## Edge Cases 231 | 232 | - [Edge case 1]: How we handled it 233 | - [Edge case 2]: How we handled it 234 | ``` 235 | 236 | ### Capture Learnings As They Occur 237 | 238 | Don't wait until the end. When you discover something: 239 | 240 | 1. Add it to LEARNINGS.md immediately 241 | 2. Continue with current work 242 | 3. At end of feature, learnings are ready to merge 243 | 244 | ## End of Feature 245 | 246 | When all steps are complete: 247 | 248 | ### 1. Verify Completion 249 | 250 | - All acceptance criteria met 251 | - All tests passing 252 | - All steps marked complete in WIP.md 253 | 254 | ### 2. Merge Learnings 255 | 256 | Review LEARNINGS.md and determine destination: 257 | 258 | | Learning Type | Destination | Method | 259 | |---------------|-------------|--------| 260 | | Gotchas | CLAUDE.md | Use `learn` agent | 261 | | Patterns | CLAUDE.md | Use `learn` agent | 262 | | Architectural decisions | ADR | Use `adr` agent | 263 | | Domain knowledge | Project docs | Direct update | 264 | 265 | ### 3. Delete Documents 266 | 267 | After learnings are merged: 268 | 269 | ```bash 270 | rm PLAN.md WIP.md LEARNINGS.md 271 | git add -A 272 | git commit -m "chore: complete [feature], remove planning docs" 273 | ``` 274 | 275 | **The knowledge lives on in:** 276 | - CLAUDE.md (gotchas, patterns) 277 | - ADRs (architectural decisions) 278 | - Git history (what was done) 279 | - Project docs (if applicable) 280 | 281 | ## Anti-Patterns 282 | 283 | ❌ **Committing without approval** 284 | - Always wait for explicit "yes" before committing 285 | 286 | ❌ **Steps that span multiple commits** 287 | - Break down further until one step = one commit 288 | 289 | ❌ **Writing code before tests** 290 | - RED comes first, always 291 | 292 | ❌ **Letting WIP.md become stale** 293 | - Update immediately when reality changes 294 | 295 | ❌ **Waiting until end to capture learnings** 296 | - Add to LEARNINGS.md as discoveries occur 297 | 298 | ❌ **Plans that change silently** 299 | - All plan changes require discussion and approval 300 | 301 | ❌ **Keeping planning docs after feature complete** 302 | - Delete them; knowledge is now in permanent locations 303 | 304 | ## Quick Reference 305 | 306 | ``` 307 | START FEATURE 308 | │ 309 | ├─► Create PLAN.md (get approval) 310 | ├─► Create WIP.md 311 | ├─► Create LEARNINGS.md 312 | │ 313 | │ FOR EACH STEP: 314 | │ │ 315 | │ ├─► RED: Failing test 316 | │ ├─► GREEN: Make it pass 317 | │ ├─► REFACTOR: If valuable 318 | │ ├─► Update WIP.md 319 | │ ├─► Capture learnings 320 | │ └─► **WAIT FOR COMMIT APPROVAL** 321 | │ 322 | END FEATURE 323 | │ 324 | ├─► Verify all criteria met 325 | ├─► Merge learnings (learn agent, adr agent) 326 | └─► Delete PLAN.md, WIP.md, LEARNINGS.md 327 | ``` 328 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/you-should-use.plugin.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | export YSU_VERSION='1.9.0' 4 | 5 | if ! type "tput" > /dev/null; then 6 | printf "WARNING: tput command not found on your PATH.\n" 7 | printf "zsh-you-should-use will fallback to uncoloured messages\n" 8 | else 9 | NONE="$(tput sgr0)" 10 | BOLD="$(tput bold)" 11 | RED="$(tput setaf 1)" 12 | YELLOW="$(tput setaf 3)" 13 | PURPLE="$(tput setaf 5)" 14 | fi 15 | 16 | function check_alias_usage() { 17 | # Optional parameter that limits how far back history is checked 18 | # I've chosen a large default value instead of bypassing tail because it's simpler 19 | local limit="${1:-${HISTSIZE:-9000000000000000}}" 20 | local key 21 | 22 | declare -A usage 23 | for key in "${(@k)aliases}"; do 24 | usage[$key]=0 25 | done 26 | 27 | # TODO: 28 | # Handle and (&&) + (&) 29 | # others? watch, time etc... 30 | 31 | local -a histfile_lines 32 | histfile_lines=("${(@f)$(<$HISTFILE)}") 33 | histfile_lines=("${histfile_lines[@]#*;}") 34 | 35 | local current=0 36 | local total=${#histfile_lines} 37 | if [[ $total -gt $limit ]]; then 38 | total=$limit 39 | fi 40 | 41 | local entry 42 | for line in ${histfile_lines[@]} ; do 43 | for entry in ${(@s/|/)line}; do 44 | # Remove leading whitespace 45 | entry=${entry##*[[:space:]]} 46 | 47 | # We only care about the first word because that's all aliases work with 48 | # (this does not count global and git aliases) 49 | local word=${entry[(w)1]} 50 | if [[ -n ${usage[$word]} ]]; then 51 | (( usage[$word]++ )) 52 | fi 53 | done 54 | 55 | # print current progress 56 | (( current++ )) 57 | printf "Analysing: [$current/$total]\r" 58 | done 59 | # Clear all previous line output 60 | printf "\r\033[K" 61 | 62 | # Print ordered usage 63 | for key in ${(k)usage}; do 64 | echo "${usage[$key]}: ${(q+)key}=${(q+)aliases[$key]}" 65 | done | sort -rn -k1 66 | } 67 | 68 | # Writing to a buffer rather than directly to stdout/stderr allows us to decide 69 | # if we want to write the reminder message before or after a command has been executed 70 | function _write_ysu_buffer() { 71 | _YSU_BUFFER+="$@" 72 | 73 | # Maintain historical behaviour by default 74 | local position="${YSU_MESSAGE_POSITION:-before}" 75 | if [[ "$position" = "before" ]]; then 76 | _flush_ysu_buffer 77 | elif [[ "$position" != "after" ]]; then 78 | (>&2 printf "${RED}${BOLD}Unknown value for YSU_MESSAGE_POSITION '$position'. ") 79 | (>&2 printf "Expected value 'before' or 'after'${NONE}\n") 80 | _flush_ysu_buffer 81 | fi 82 | } 83 | 84 | function _flush_ysu_buffer() { 85 | # It's important to pass $_YSU_BUFFER to printfs first argument 86 | # because otherwise all escape codes will not printed correctly 87 | (>&2 printf "$_YSU_BUFFER") 88 | _YSU_BUFFER="" 89 | } 90 | 91 | function ysu_message() { 92 | local DEFAULT_MESSAGE_FORMAT="${BOLD}${YELLOW}\ 93 | Found existing %alias_type for ${PURPLE}\"%command\"${YELLOW}. \ 94 | You should use: ${PURPLE}\"%alias\"${NONE}" 95 | 96 | local alias_type_arg="${1}" 97 | local command_arg="${2}" 98 | local alias_arg="${3}" 99 | 100 | # Escape arguments which will be interpreted by printf incorrectly 101 | # unfortunately there does not seem to be a nice way to put this into 102 | # a function because returning the values requires to be done by printf/echo!! 103 | command_arg="${command_arg//\%/%%}" 104 | command_arg="${command_arg//\\/\\\\}" 105 | 106 | local MESSAGE="${YSU_MESSAGE_FORMAT:-"$DEFAULT_MESSAGE_FORMAT"}" 107 | MESSAGE="${MESSAGE//\%alias_type/$alias_type_arg}" 108 | MESSAGE="${MESSAGE//\%command/$command_arg}" 109 | MESSAGE="${MESSAGE//\%alias/$alias_arg}" 110 | 111 | _write_ysu_buffer "$MESSAGE\n" 112 | } 113 | 114 | 115 | # Prevent command from running if hardcore mode enabled 116 | function _check_ysu_hardcore() { 117 | if (( ${+YSU_HARDCORE} )); then 118 | _write_ysu_buffer "${BOLD}${RED}You Should Use hardcore mode enabled. Use your aliases!${NONE}\n" 119 | kill -s INT $$ 120 | fi 121 | } 122 | 123 | 124 | function _check_git_aliases() { 125 | local typed="$1" 126 | local expanded="$2" 127 | 128 | # sudo will use another user's profile and so aliases would not apply 129 | if [[ "$typed" = "sudo "* ]]; then 130 | return 131 | fi 132 | 133 | if [[ "$typed" = "git "* ]]; then 134 | local found=false 135 | git config --get-regexp "^alias\..+$" | sort | while read key value; do 136 | key="${key#alias.}" 137 | 138 | # if for some reason, read does not split correctly, we 139 | # detect that and manually split the key and value 140 | if [[ -z "$value" ]]; then 141 | value="${key#* }" 142 | key="${key%% *}" 143 | fi 144 | 145 | if [[ "$expanded" = "git $value" || "$expanded" = "git $value "* ]]; then 146 | ysu_message "git alias" "$value" "git $key" 147 | found=true 148 | fi 149 | done 150 | 151 | if $found; then 152 | _check_ysu_hardcore 153 | fi 154 | fi 155 | } 156 | 157 | 158 | function _check_global_aliases() { 159 | local typed="$1" 160 | local expanded="$2" 161 | 162 | local found=false 163 | local tokens 164 | local key 165 | local value 166 | local entry 167 | 168 | # sudo will use another user's profile and so aliases would not apply 169 | if [[ "$typed" = "sudo "* ]]; then 170 | return 171 | fi 172 | 173 | alias -g | sort | while IFS="=" read -r key value; do 174 | key="${key## }" 175 | key="${key%% }" 176 | value="${(Q)value}" 177 | 178 | # Skip ignored global aliases 179 | if [[ ${YSU_IGNORED_GLOBAL_ALIASES[(r)$key]} == "$key" ]]; then 180 | continue 181 | fi 182 | 183 | if [[ "$typed" = *" $value "* || \ 184 | "$typed" = *" $value" || \ 185 | "$typed" = "$value "* || \ 186 | "$typed" = "$value" ]]; then 187 | ysu_message "global alias" "$value" "$key" 188 | found=true 189 | fi 190 | done 191 | 192 | if $found; then 193 | _check_ysu_hardcore 194 | fi 195 | } 196 | 197 | 198 | function _check_aliases() { 199 | local typed="$1" 200 | local expanded="$2" 201 | 202 | local found_aliases 203 | found_aliases=() 204 | local best_match="" 205 | local best_match_value="" 206 | local key 207 | local value 208 | 209 | # sudo will use another user's profile and so aliases would not apply 210 | if [[ "$typed" = "sudo "* ]]; then 211 | return 212 | fi 213 | 214 | # Find alias matches 215 | for key in "${(@k)aliases}"; do 216 | value="${aliases[$key]}" 217 | 218 | # Skip ignored aliases 219 | if [[ ${YSU_IGNORED_ALIASES[(r)$key]} == "$key" ]]; then 220 | continue 221 | fi 222 | 223 | if [[ "$typed" = "$value" || "$typed" = "$value "* ]]; then 224 | 225 | # if the alias longer or the same length as its command 226 | # we assume that it is there to cater for typos. 227 | # If not, then the alias would not save any time 228 | # for the user and so doesn't hold much value anyway 229 | if [[ "${#value}" -gt "${#key}" ]]; then 230 | 231 | found_aliases+="$key" 232 | 233 | # Match aliases to longest portion of command 234 | if [[ "${#value}" -gt "${#best_match_value}" ]]; then 235 | best_match="$key" 236 | best_match_value="$value" 237 | # on equal length, choose the shortest alias 238 | elif [[ "${#value}" -eq "${#best_match}" && ${#key} -lt "${#best_match}" ]]; then 239 | best_match="$key" 240 | best_match_value="$value" 241 | fi 242 | fi 243 | fi 244 | done 245 | 246 | # Print result matches based on current mode 247 | if [[ "$YSU_MODE" = "ALL" ]]; then 248 | for key in ${(@ok)found_aliases}; do 249 | value="${aliases[$key]}" 250 | ysu_message "alias" "$value" "$key" 251 | done 252 | 253 | elif [[ (-z "$YSU_MODE" || "$YSU_MODE" = "BESTMATCH") && -n "$best_match" ]]; then 254 | # make sure that the best matched alias has not already 255 | # been typed by the user 256 | value="${aliases[$best_match]}" 257 | if [[ "$typed" = "$best_match" || "$typed" = "$best_match "* ]]; then 258 | return 259 | fi 260 | ysu_message "alias" "$value" "$best_match" 261 | fi 262 | 263 | if [[ -n "$found_aliases" ]]; then 264 | _check_ysu_hardcore 265 | fi 266 | } 267 | 268 | function disable_you_should_use() { 269 | add-zsh-hook -D preexec _check_aliases 270 | add-zsh-hook -D preexec _check_global_aliases 271 | add-zsh-hook -D preexec _check_git_aliases 272 | add-zsh-hook -D precmd _flush_ysu_buffer 273 | } 274 | 275 | function enable_you_should_use() { 276 | disable_you_should_use # Delete any possible pre-existing hooks 277 | add-zsh-hook preexec _check_aliases 278 | add-zsh-hook preexec _check_global_aliases 279 | add-zsh-hook preexec _check_git_aliases 280 | add-zsh-hook precmd _flush_ysu_buffer 281 | } 282 | 283 | autoload -Uz add-zsh-hook 284 | enable_you_should_use 285 | -------------------------------------------------------------------------------- /install-claude.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Install CLAUDE.md development framework to ~/.claude/ 4 | # 5 | # Usage: 6 | # ./install-claude.sh # Install everything (CLAUDE.md + skills + commands + agents) 7 | # ./install-claude.sh --claude-only # Install only CLAUDE.md 8 | # ./install-claude.sh --no-agents # Install without agents 9 | # ./install-claude.sh --skills-only # Install only skills 10 | # ./install-claude.sh --version v3.0.0 # Install specific version 11 | # ./install-claude.sh --with-opencode # Also install OpenCode configuration 12 | # 13 | # One-liner installation: 14 | # curl -fsSL https://raw.githubusercontent.com/citypaul/.dotfiles/main/install-claude.sh | bash 15 | # 16 | 17 | set -e # Exit on error 18 | 19 | # Colors for output 20 | RED='\033[0;31m' 21 | GREEN='\033[0;32m' 22 | YELLOW='\033[1;33m' 23 | BLUE='\033[0;34m' 24 | NC='\033[0m' # No Color 25 | 26 | # Default settings 27 | VERSION="${VERSION:-main}" 28 | INSTALL_CLAUDE=true 29 | INSTALL_SKILLS=true 30 | INSTALL_COMMANDS=true 31 | INSTALL_AGENTS=true 32 | INSTALL_OPENCODE=false 33 | BASE_URL="https://raw.githubusercontent.com/citypaul/.dotfiles" 34 | 35 | # Parse arguments 36 | while [[ $# -gt 0 ]]; do 37 | case $1 in 38 | --claude-only) 39 | INSTALL_SKILLS=false 40 | INSTALL_COMMANDS=false 41 | INSTALL_AGENTS=false 42 | shift 43 | ;; 44 | --no-agents) 45 | INSTALL_AGENTS=false 46 | shift 47 | ;; 48 | --skills-only) 49 | INSTALL_CLAUDE=false 50 | INSTALL_COMMANDS=false 51 | INSTALL_AGENTS=false 52 | INSTALL_SKILLS=true 53 | shift 54 | ;; 55 | --agents-only) 56 | INSTALL_CLAUDE=false 57 | INSTALL_SKILLS=false 58 | INSTALL_COMMANDS=false 59 | INSTALL_AGENTS=true 60 | shift 61 | ;; 62 | --with-opencode) 63 | INSTALL_OPENCODE=true 64 | shift 65 | ;; 66 | --opencode-only) 67 | INSTALL_CLAUDE=false 68 | INSTALL_SKILLS=false 69 | INSTALL_COMMANDS=false 70 | INSTALL_AGENTS=false 71 | INSTALL_OPENCODE=true 72 | shift 73 | ;; 74 | --version) 75 | VERSION="$2" 76 | shift 2 77 | ;; 78 | --help|-h) 79 | cat << EOF 80 | Install CLAUDE.md development framework to ~/.claude/ 81 | 82 | Usage: 83 | $0 [OPTIONS] 84 | 85 | Options: 86 | --claude-only Install only CLAUDE.md 87 | --no-agents Install without agents 88 | --skills-only Install only skills 89 | --agents-only Install only agents 90 | --with-opencode Also install OpenCode configuration 91 | --opencode-only Install only OpenCode configuration 92 | --version VERSION Install specific version (default: main) 93 | --help, -h Show this help message 94 | 95 | Examples: 96 | # Install everything (recommended) 97 | $0 98 | 99 | # Install specific version 100 | $0 --version v3.0.0 101 | 102 | # Install without agents 103 | $0 --no-agents 104 | 105 | # One-liner installation 106 | curl -fsSL https://raw.githubusercontent.com/citypaul/.dotfiles/main/install-claude.sh | bash 107 | 108 | EOF 109 | exit 0 110 | ;; 111 | *) 112 | echo -e "${RED}Error: Unknown option $1${NC}" 113 | echo "Run '$0 --help' for usage information" 114 | exit 1 115 | ;; 116 | esac 117 | done 118 | 119 | echo -e "${BLUE}╔════════════════════════════════════════════════════╗${NC}" 120 | echo -e "${BLUE}║ CLAUDE.md Development Framework Installer ║${NC}" 121 | printf "${BLUE}║ Version: %-40s║${NC}\n" "$VERSION" 122 | echo -e "${BLUE}╚════════════════════════════════════════════════════╝${NC}" 123 | echo "" 124 | 125 | # Function to download a file 126 | download_file() { 127 | local url="$1" 128 | local dest="$2" 129 | local description="$3" 130 | 131 | echo -e "${YELLOW}→${NC} Downloading $description..." 132 | 133 | if curl -fsSL "$url" -o "$dest"; then 134 | echo -e "${GREEN}✓${NC} $description installed" 135 | return 0 136 | else 137 | echo -e "${RED}✗${NC} Failed to download $description" 138 | return 1 139 | fi 140 | } 141 | 142 | # Function to backup existing file 143 | backup_file() { 144 | local file="$1" 145 | 146 | if [[ -f "$file" ]]; then 147 | local backup="${file}.backup.$(date +%Y%m%d_%H%M%S)" 148 | echo -e "${YELLOW}→${NC} Backing up existing file to $backup" 149 | mv "$file" "$backup" 150 | fi 151 | } 152 | 153 | # Create directories 154 | echo -e "${BLUE}Creating directories...${NC}" 155 | mkdir -p ~/.claude/agents ~/.claude/skills ~/.claude/commands 156 | mkdir -p ~/.claude/skills/tdd ~/.claude/skills/typescript-strict ~/.claude/skills/functional 157 | mkdir -p ~/.claude/skills/refactoring ~/.claude/skills/testing ~/.claude/skills/expectations ~/.claude/skills/planning 158 | mkdir -p ~/.claude/skills/front-end-testing ~/.claude/skills/react-testing 159 | echo -e "${GREEN}✓${NC} Directories created" 160 | echo "" 161 | 162 | # Install CLAUDE.md 163 | if [[ "$INSTALL_CLAUDE" == true ]]; then 164 | echo -e "${BLUE}Installing CLAUDE.md...${NC}" 165 | backup_file ~/.claude/CLAUDE.md 166 | download_file \ 167 | "$BASE_URL/$VERSION/claude/.claude/CLAUDE.md" \ 168 | ~/.claude/CLAUDE.md \ 169 | "CLAUDE.md" 170 | echo "" 171 | fi 172 | 173 | # Install skills (v3.0: auto-discovered patterns) 174 | if [[ "$INSTALL_SKILLS" == true ]]; then 175 | echo -e "${BLUE}Installing skills (auto-discovered patterns)...${NC}" 176 | 177 | skills=( 178 | "tdd/SKILL.md" 179 | "typescript-strict/SKILL.md" 180 | "functional/SKILL.md" 181 | "refactoring/SKILL.md" 182 | "testing/SKILL.md" 183 | "expectations/SKILL.md" 184 | "planning/SKILL.md" 185 | "front-end-testing/SKILL.md" 186 | "react-testing/SKILL.md" 187 | ) 188 | 189 | for skill in "${skills[@]}"; do 190 | backup_file ~/.claude/skills/"$skill" 191 | download_file \ 192 | "$BASE_URL/$VERSION/claude/.claude/skills/$skill" \ 193 | ~/.claude/skills/"$skill" \ 194 | "skills/$skill" 195 | done 196 | echo "" 197 | fi 198 | 199 | # Install commands (v3.0: slash commands) 200 | if [[ "$INSTALL_COMMANDS" == true ]]; then 201 | echo -e "${BLUE}Installing commands (slash commands)...${NC}" 202 | 203 | commands=( 204 | "pr.md" 205 | ) 206 | 207 | for cmd in "${commands[@]}"; do 208 | backup_file ~/.claude/commands/"$cmd" 209 | download_file \ 210 | "$BASE_URL/$VERSION/claude/.claude/commands/$cmd" \ 211 | ~/.claude/commands/"$cmd" \ 212 | "commands/$cmd" 213 | done 214 | echo "" 215 | fi 216 | 217 | # Install agents 218 | if [[ "$INSTALL_AGENTS" == true ]]; then 219 | echo -e "${BLUE}Installing Claude Code agents...${NC}" 220 | 221 | agents=( 222 | "tdd-guardian.md" 223 | "ts-enforcer.md" 224 | "refactor-scan.md" 225 | "docs-guardian.md" 226 | "adr.md" 227 | "learn.md" 228 | "use-case-data-patterns.md" 229 | "progress-guardian.md" 230 | "README.md" 231 | ) 232 | 233 | for agent in "${agents[@]}"; do 234 | backup_file ~/.claude/agents/"$agent" 235 | download_file \ 236 | "$BASE_URL/$VERSION/claude/.claude/agents/$agent" \ 237 | ~/.claude/agents/"$agent" \ 238 | "agents/$agent" 239 | done 240 | echo "" 241 | fi 242 | 243 | # Install OpenCode configuration 244 | if [[ "$INSTALL_OPENCODE" == true ]]; then 245 | echo -e "${BLUE}Installing OpenCode configuration...${NC}" 246 | mkdir -p ~/.config/opencode 247 | backup_file ~/.config/opencode/opencode.json 248 | download_file \ 249 | "$BASE_URL/$VERSION/opencode/.config/opencode/opencode.json" \ 250 | ~/.config/opencode/opencode.json \ 251 | "opencode.json" 252 | echo "" 253 | fi 254 | 255 | # Success message 256 | echo -e "${GREEN}╔════════════════════════════════════════════════════╗${NC}" 257 | echo -e "${GREEN}║ Installation complete! ✓ ║${NC}" 258 | echo -e "${GREEN}╚════════════════════════════════════════════════════╝${NC}" 259 | echo "" 260 | 261 | # Show what was installed 262 | echo -e "${BLUE}Installed to ~/.claude/${NC}" 263 | echo "" 264 | 265 | if [[ "$INSTALL_CLAUDE" == true ]]; then 266 | echo -e " ${GREEN}✓${NC} CLAUDE.md (lean core principles)" 267 | fi 268 | 269 | if [[ "$INSTALL_SKILLS" == true ]]; then 270 | echo -e " ${GREEN}✓${NC} skills/ (9 auto-discovered patterns: tdd, testing, typescript-strict, functional, refactoring, expectations, planning, front-end-testing, react-testing)" 271 | fi 272 | 273 | if [[ "$INSTALL_COMMANDS" == true ]]; then 274 | echo -e " ${GREEN}✓${NC} commands/ (1 slash command: /pr)" 275 | fi 276 | 277 | if [[ "$INSTALL_AGENTS" == true ]]; then 278 | echo -e " ${GREEN}✓${NC} agents/ (8 Claude Code agents + README)" 279 | fi 280 | 281 | if [[ "$INSTALL_OPENCODE" == true ]]; then 282 | echo -e "" 283 | echo -e "${BLUE}Installed to ~/.config/opencode/${NC}" 284 | echo -e " ${GREEN}✓${NC} opencode.json (OpenCode rules configuration)" 285 | fi 286 | 287 | echo "" 288 | echo -e "${BLUE}Architecture (v3.0):${NC}" 289 | echo "" 290 | echo -e " ${YELLOW}CLAUDE.md${NC} → Core principles (~100 lines, always loaded)" 291 | echo -e " ${YELLOW}skills/${NC} → Detailed patterns (loaded on-demand when relevant)" 292 | echo -e " ${YELLOW}commands/${NC} → Slash commands (manually invoked)" 293 | echo -e " ${YELLOW}agents/${NC} → Complex multi-step workflows" 294 | echo "" 295 | echo -e "${BLUE}Next steps:${NC}" 296 | echo "" 297 | echo -e " 1. Verify installation:" 298 | echo -e " ${YELLOW}ls -la ~/.claude/${NC}" 299 | echo "" 300 | echo -e " 2. Test with Claude Code:" 301 | echo -e " Open any project and use: ${YELLOW}/memory${NC}" 302 | echo "" 303 | echo -e " 3. Try the /pr command:" 304 | echo -e " ${YELLOW}/pr${NC}" 305 | echo "" 306 | 307 | if [[ "$INSTALL_AGENTS" == true ]]; then 308 | echo -e " 4. Learn about agents:" 309 | echo -e " ${YELLOW}cat ~/.claude/agents/README.md${NC}" 310 | echo "" 311 | fi 312 | 313 | echo -e "${BLUE}For help or issues:${NC}" 314 | echo -e " ${YELLOW}https://github.com/citypaul/.dotfiles${NC}" 315 | echo "" 316 | -------------------------------------------------------------------------------- /claude/.claude/skills/tdd/SKILL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: tdd 3 | description: Test-Driven Development workflow. Use for ALL code changes - features, bug fixes, refactoring. TDD is non-negotiable. 4 | --- 5 | 6 | # Test-Driven Development 7 | 8 | TDD is the fundamental practice. Every line of production code must be written in response to a failing test. 9 | 10 | **For how to write good tests**, load the `testing` skill. This skill focuses on the TDD workflow/process. 11 | 12 | --- 13 | 14 | ## RED-GREEN-REFACTOR Cycle 15 | 16 | ### RED: Write Failing Test First 17 | - NO production code until you have a failing test 18 | - Test describes desired behavior, not implementation 19 | - Test should fail for the right reason 20 | 21 | ### GREEN: Minimum Code to Pass 22 | - Write ONLY enough code to make the test pass 23 | - Resist adding functionality not demanded by a test 24 | - Commit immediately after green 25 | 26 | ### REFACTOR: Assess Improvements 27 | - Assess AFTER every green (but only refactor if it adds value) 28 | - Commit before refactoring 29 | - All tests must pass after refactoring 30 | 31 | --- 32 | 33 | ## TDD Evidence in Commit History 34 | 35 | ### Default Expectation 36 | 37 | Commit history should show clear RED → GREEN → REFACTOR progression. 38 | 39 | **Ideal progression:** 40 | ``` 41 | commit abc123: test: add failing test for user authentication 42 | commit def456: feat: implement user authentication to pass test 43 | commit ghi789: refactor: extract validation logic for clarity 44 | ``` 45 | 46 | ### Rare Exceptions 47 | 48 | TDD evidence may not be linearly visible in commits in these cases: 49 | 50 | **1. Multi-Session Work** 51 | - Feature spans multiple development sessions 52 | - Work done with TDD in each session 53 | - Commits organized for PR clarity rather than strict TDD phases 54 | - **Evidence**: Tests exist, all passing, implementation matches test requirements 55 | 56 | **2. Context Continuation** 57 | - Resuming from previous work 58 | - Original RED phase done in previous session/commit 59 | - Current work continues from that point 60 | - **Evidence**: Reference to RED commit in PR description 61 | 62 | **3. Refactoring Commits** 63 | - Large refactors after GREEN 64 | - Multiple small refactors combined into single commit 65 | - All tests remained green throughout 66 | - **Evidence**: Commit message notes "refactor only, no behavior change" 67 | 68 | ### Documenting Exceptions in PRs 69 | 70 | When exception applies, document in PR description: 71 | 72 | ```markdown 73 | ## TDD Evidence 74 | 75 | RED phase: commit c925187 (added failing tests for shopping cart) 76 | GREEN phase: commits 5e0055b, 9a246d0 (implementation + bug fixes) 77 | REFACTOR: commit 11dbd1a (test isolation improvements) 78 | 79 | Test Evidence: 80 | ✅ 4/4 tests passing (7.7s with 4 workers) 81 | ``` 82 | 83 | **Important**: Exception is for EVIDENCE presentation, not TDD practice. TDD process must still be followed - these are cases where commit history doesn't perfectly reflect the process that was actually followed. 84 | 85 | --- 86 | 87 | ## Coverage Verification - CRITICAL 88 | 89 | ### NEVER Trust Coverage Claims Without Verification 90 | 91 | **Always run coverage yourself before approving PRs.** 92 | 93 | ### Verification Process 94 | 95 | **Before approving any PR claiming "100% coverage":** 96 | 97 | 1. Check out the branch 98 | ```bash 99 | git checkout feature-branch 100 | ``` 101 | 102 | 2. Run coverage verification: 103 | ```bash 104 | cd packages/core 105 | pnpm test:coverage 106 | # OR 107 | pnpm exec vitest run --coverage 108 | ``` 109 | 110 | 3. Verify ALL metrics hit 100%: 111 | - Lines: 100% ✅ 112 | - Statements: 100% ✅ 113 | - Branches: 100% ✅ 114 | - Functions: 100% ✅ 115 | 116 | 4. Check that tests are behavior-driven (not testing implementation details) 117 | 118 | **For anti-patterns that create fake coverage (coverage theater)**, see the `testing` skill. 119 | 120 | ### Reading Coverage Output 121 | 122 | Look for the "All files" line in coverage summary: 123 | 124 | ``` 125 | File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 126 | ---------------|---------|----------|---------|---------|------------------- 127 | All files | 100 | 100 | 100 | 100 | 128 | setup.ts | 100 | 100 | 100 | 100 | 129 | context.ts | 100 | 100 | 100 | 100 | 130 | endpoints.ts | 100 | 100 | 100 | 100 | 131 | ``` 132 | 133 | ✅ This is 100% coverage - all four metrics at 100%. 134 | 135 | ### Red Flags 136 | 137 | Watch for these signs of incomplete coverage: 138 | 139 | ❌ **PR claims "100% coverage" but you haven't verified** 140 | - Never trust claims without running coverage yourself 141 | 142 | ❌ **Coverage summary shows <100% on any metric** 143 | ``` 144 | All files | 97.11 | 93.97 | 81.81 | 97.11 | 145 | ``` 146 | - This is NOT 100% coverage (Functions: 81.81%, Lines: 97.11%) 147 | 148 | ❌ **"Uncovered Line #s" column shows line numbers** 149 | ``` 150 | setup.ts | 95.23 | 100 | 60 | 95.23 | 45-48, 52-55 151 | ``` 152 | - Lines 45-48 and 52-55 are not covered 153 | 154 | ❌ **Coverage gaps without explicit exception documentation** 155 | - If coverage <100%, exception should be documented (see Exception Process below) 156 | 157 | ### When Coverage Drops, Ask 158 | 159 | **"What business behavior am I not testing?"** 160 | 161 | NOT "What line am I missing?" 162 | 163 | Add tests for behavior, and coverage follows naturally. 164 | 165 | --- 166 | 167 | ## 100% Coverage Exception Process 168 | 169 | ### Default Rule: 100% Coverage Required 170 | 171 | No exceptions without explicit approval and documentation. 172 | 173 | ### Requesting an Exception 174 | 175 | If 100% coverage cannot be achieved: 176 | 177 | **Step 1: Document in package README** 178 | 179 | Explain: 180 | - Current coverage metrics 181 | - WHY 100% cannot be achieved in this package 182 | - WHERE the missing coverage will come from (integration tests, E2E, etc.) 183 | 184 | **Step 2: Get explicit approval** 185 | 186 | From project maintainer or team lead 187 | 188 | **Step 3: Document in CLAUDE.md** 189 | 190 | Under "Test Coverage: 100% Required" section, list the exception 191 | 192 | **Example Exception:** 193 | 194 | ```markdown 195 | ## Current Exceptions 196 | 197 | - **Next.js Adapter**: 86% function coverage 198 | - Documented in `/packages/nextjs-adapter/README.md` 199 | - Missing coverage from SSR functions (tested in E2E layer) 200 | - Approved: 2024-11-15 201 | ``` 202 | 203 | ### Remember 204 | 205 | The burden of proof is on the requester. 100% is the default expectation. 206 | 207 | --- 208 | 209 | ## Development Workflow 210 | 211 | ### Adding a New Feature 212 | 213 | 1. **Write failing test** - describe expected behavior 214 | 2. **Run test** - confirm it fails (`pnpm test:watch`) 215 | 3. **Implement minimum** - just enough to pass 216 | 4. **Run test** - confirm it passes 217 | 5. **Refactor if valuable** - improve code structure 218 | 6. **Commit** - with conventional commit message 219 | 220 | ### Workflow Example 221 | 222 | ```bash 223 | # 1. Write failing test 224 | it('should reject empty user names', () => { 225 | const result = createUser({ id: 'user-123', name: '' }); 226 | expect(result.success).toBe(false); 227 | }); # ❌ Test fails (no implementation) 228 | 229 | # 2. Implement minimum code 230 | if (user.name === '') { 231 | return { success: false, error: 'Name required' }; 232 | } # ✅ Test passes 233 | 234 | # 3. Refactor if needed (extract validation, improve naming) 235 | 236 | # 4. Commit 237 | git add . 238 | git commit -m "feat: reject empty user names" 239 | ``` 240 | 241 | --- 242 | 243 | ## Commit Messages 244 | 245 | Use conventional commits format: 246 | 247 | ``` 248 | feat: add user role-based permissions 249 | fix: correct email validation regex 250 | refactor: extract user validation logic 251 | test: add edge cases for permission checks 252 | docs: update architecture documentation 253 | ``` 254 | 255 | **Format:** 256 | - `feat:` - New feature 257 | - `fix:` - Bug fix 258 | - `refactor:` - Code change that neither fixes bug nor adds feature 259 | - `test:` - Adding or updating tests 260 | - `docs:` - Documentation changes 261 | 262 | --- 263 | 264 | ## Pull Request Requirements 265 | 266 | Before submitting PR: 267 | 268 | - [ ] All tests must pass 269 | - [ ] All linting and type checks must pass 270 | - [ ] **Coverage verification REQUIRED** - claims must be verified before review/approval 271 | - [ ] PRs focused on single feature or fix 272 | - [ ] Include behavior description (not implementation details) 273 | 274 | **Example PR Description:** 275 | 276 | ```markdown 277 | ## Summary 278 | 279 | Adds support for user role-based permissions with configurable access levels. 280 | 281 | ## Behavior Changes 282 | 283 | - Users can now have multiple roles with fine-grained permissions 284 | - Permission check via `hasPermission(user, resource, action)` 285 | - Default role assigned if not specified 286 | 287 | ## Test Evidence 288 | 289 | ✅ 42/42 tests passing 290 | ✅ 100% coverage verified (see coverage report) 291 | 292 | ## TDD Evidence 293 | 294 | RED: commit 4a3b2c1 (failing tests for permission system) 295 | GREEN: commit 5d4e3f2 (implementation) 296 | REFACTOR: commit 6e5f4a3 (extract permission resolution logic) 297 | ``` 298 | 299 | --- 300 | 301 | ## Refactoring Priority 302 | 303 | After green, classify any issues: 304 | 305 | | Priority | Action | Examples | 306 | |----------|--------|----------| 307 | | Critical | Fix now | Mutations, knowledge duplication, >3 levels nesting | 308 | | High | This session | Magic numbers, unclear names, >30 line functions | 309 | | Nice | Later | Minor naming, single-use helpers | 310 | | Skip | Don't change | Already clean code | 311 | 312 | For detailed refactoring methodology, load the `refactoring` skill. 313 | 314 | --- 315 | 316 | ## Anti-Patterns to Avoid 317 | 318 | - ❌ Writing production code without failing test 319 | - ❌ Testing implementation details (spies on internal methods) 320 | - ❌ 1:1 mapping between test files and implementation files 321 | - ❌ Using `let`/`beforeEach` for test data 322 | - ❌ Trusting coverage claims without verification 323 | - ❌ Mocking the function being tested 324 | - ❌ Redefining schemas in test files 325 | - ❌ Factories returning partial/incomplete objects 326 | - ❌ Speculative code ("just in case" logic without tests) 327 | 328 | **For detailed testing anti-patterns**, load the `testing` skill. 329 | 330 | --- 331 | 332 | ## Summary Checklist 333 | 334 | Before marking work complete: 335 | 336 | - [ ] Every production code line has a failing test that demanded it 337 | - [ ] Commit history shows TDD evidence (or documented exception) 338 | - [ ] All tests pass 339 | - [ ] Coverage verified at 100% (or exception documented) 340 | - [ ] Test factories used (no `let`/`beforeEach`) 341 | - [ ] Tests verify behavior (not implementation details) 342 | - [ ] Refactoring assessed and applied if valuable 343 | - [ ] Conventional commit messages used 344 | -------------------------------------------------------------------------------- /.oh-my-zsh/.oh-my-zsh/custom/plugins/zsh-you-should-use/README.rst: -------------------------------------------------------------------------------- 1 | You Should Use 2 | ============== 3 | 4 | |CircleCI| |Version| |GPLv3| 5 | 6 | Simple zsh plugin that reminds you that you should use one of your 7 | existing aliases for a command you just typed. 8 | 9 | Also supports detection of global and git aliases. 10 | 11 | * Usage_ 12 | * Requirements_ 13 | * Installation_ 14 | 15 | Customization 16 | 17 | * `Message Position`_ 18 | * `Displaying Results`_ 19 | * `Customising Messages`_ 20 | 21 | Advanced Features 22 | 23 | * `Hardcore Mode`_ 24 | * `Check your Alias usage`_ 25 | * `Disable Hints for specific Aliases`_ 26 | * `Temporarily Disabling Messages`_ 27 | 28 | Contributing 29 | 30 | * Contributing_ 31 | * `Running Tests`_ 32 | 33 | You can also view the CHANGELOG_ for a history of changes. 34 | 35 | Usage 36 | ----- 37 | 38 | You dont need to do anything. Once it's installed, 39 | ``zsh-you-should-use`` will let you know if you wrote a command with an 40 | existing alias. 41 | 42 | .. image:: img/alias.png 43 | 44 | ``you-should-use`` also detects global aliases: 45 | 46 | .. image:: img/global.png 47 | 48 | and Git aliases: 49 | 50 | .. image:: img/git.png 51 | 52 | Requirements 53 | ------------ 54 | 55 | ``you-should-use`` officially supports zsh versions 5.1 onwards. 56 | 57 | It is possible the plugin might work on even older versions. 58 | However they would not have been tested as part of the CI test process. 59 | 60 | Installation 61 | ------------ 62 | 63 | Add one of the following to your ``.zshrc`` file depending on your 64 | package manager: 65 | 66 | ZPlug_ 67 | 68 | :: 69 | 70 | zplug "MichaelAquilina/zsh-you-should-use" 71 | 72 | Antigen_ 73 | 74 | :: 75 | 76 | antigen bundle "MichaelAquilina/zsh-you-should-use" 77 | 78 | Zgen_ 79 | 80 | :: 81 | 82 | zgen load "MichaelAquilina/zsh-you-should-use" 83 | 84 | oh-my-zsh_ 85 | 86 | Clone this repository into ``$ZSH_CUSTOM/custom/plugins``: 87 | 88 | .. code-block:: sh 89 | 90 | git clone https://github.com/MichaelAquilina/zsh-you-should-use.git $ZSH_CUSTOM/plugins/you-should-use 91 | 92 | ``$ZSH_CUSTOM`` is oh-my-zsh’s customization directory (`docs `__). 93 | 94 | Then add ``you-should-use`` to the ``plugins`` array in your ``.zshrc``: 95 | 96 | .. code-block:: sh 97 | 98 | plugins=( 99 | # ... 100 | you-should-use 101 | # ... 102 | ) 103 | 104 | `Arch Linux`_ 105 | 106 | Install the ``zsh-you-should-use`` (AUR_) package:: 107 | 108 | yay -S zsh-you-should-use 109 | 110 | Then add this line to your ``.zshrc``:: 111 | 112 | source /usr/share/zsh/plugins/zsh-you-should-use/you-should-use.plugin.zsh 113 | 114 | Message Position 115 | ---------------- 116 | 117 | By default, ``you-should-use`` will display its reminder message *before* 118 | a command has executed. However, you can choose to display the mesasge *after* a 119 | command has executed by setting the value of ``YSU_MESSAGE_POSITION``. 120 | 121 | :: 122 | 123 | export YSU_MESSAGE_POSITION="after" 124 | 125 | 126 | Displaying Results 127 | ------------------ 128 | 129 | By default, ``you-should-use`` will display the best match from any 130 | matching aliases found. However, you can change this behaviour so that 131 | it displays *all* matches found by setting the value of ``YSU_MODE``. 132 | 133 | - To only display best match (default): ``export YSU_MODE=BESTMATCH`` 134 | - To display all matches: ``export YSU_MODE=ALL`` 135 | 136 | 137 | Customising Messages 138 | -------------------- 139 | 140 | By default, the following message is displayed in bold when an alias is found: 141 | 142 | :: 143 | 144 | Found existing %alias_type for "%command". You should use: "%alias" 145 | 146 | Where the following variables represent: 147 | 148 | * ``%alias_type`` - the type of alias detected (alias, git alias, global alias) 149 | * ``%command`` - the command that was typed by the user 150 | * ``%alias`` - the matching alias that was found 151 | 152 | This default message can be customised by setting the ``YSU_MESSAGE_FORMAT`` environment variable. 153 | 154 | If for example, you wish to display your own custom message in red, you can add the 155 | following to your ``~/.zshrc``: 156 | 157 | :: 158 | 159 | export YSU_MESSAGE_FORMAT="$(tput setaf 1)Hey! I found this %alias_type for %command: %alias$(tput sgr0)" 160 | 161 | ``$(tput setaf 1)`` generates the escape code terminals use for red foreground text. ``$(tput sgr0)`` sets 162 | the text back to a normal color. 163 | 164 | You can read more about how you can use tput and terminal escape codes here: 165 | http://wiki.bash-hackers.org/scripting/terminalcodes 166 | 167 | Hardcore Mode 168 | ------------- 169 | 170 | **For the brave and adventerous only** :godmode: 171 | 172 | You can enable Hardcore mode to enforce the use of aliases. Enabling 173 | this will cause zsh to refuse to execute commands you have entered if an 174 | alternative alias for it exists. This is a handy way of forcing you to 175 | use your aliases and help you turn those aliases into muscle memory. 176 | 177 | Enable hardcore mode by setting the variable ``YSU_HARDCORE`` to 1. 178 | 179 | :: 180 | 181 | export YSU_HARDCORE=1 182 | 183 | Now if you type a command that has an alias defined and you didnt use 184 | it, zsh will refuse to execute that command: 185 | 186 | :: 187 | 188 | $ export YSU_HARDCORE=1 189 | $ ls -lh 190 | Found existing alias for "ls -lh". You should use: "ll" 191 | You Should Use hardcore mode enabled. Use your aliases! 192 | $ ll 193 | total 8.0K 194 | -rw-r--r-- 1 michael users 2.4K Jun 19 20:46 README.md 195 | -rw-r--r-- 1 michael users 650 Jun 19 20:42 you-should-use.plugin.zsh 196 | 197 | Check your Alias usage 198 | ---------------------- 199 | 200 | It's often useful to check how often we use our aliases so that we have an idea of which ones we 201 | could probably get rid of (or remind ourselves of them if we forgot). ``zsh-you-should-use`` provides 202 | a convenience function ``check_alias_usage`` which you can run to analyse your alias usage. 203 | 204 | :: 205 | 206 | $ check_alias_usage 207 | 924: curl='curl --silent' 208 | 652: gco='git checkout' 209 | 199: json='jq '.' -C' 210 | 157: less='less -R' 211 | 100: ll='ls -lh --group-directories-first' 212 | 93: vim='nvim' 213 | 76: watch='watch ' 214 | 61: v='vim' 215 | 60: md='mkdir' 216 | 39: gr='git rebase' 217 | 38: dc='docker-compose' 218 | 35: ls='ls --color=auto' 219 | 33: h='history' 220 | 28: dcr='docker-compose 221 | 222 | ``check_alias_usage`` analyses your history to generate this data for you. If your history is disabled 223 | or if you limit your history to a certain amount of time, then the alias report generated will be a reflection 224 | of the limited data available. 225 | 226 | Optionally, you can limit how far ``check_alias_usage`` looks back in history by providing an optional numeric 227 | parameter. This parameter specifies how many entries in the history to check when generating the report. 228 | In the example below, history is limited to the last 200 entries when generating the alias report. 229 | 230 | :: 231 | 232 | $ check_alias_usage 200 233 | 9: h='history' 234 | 3: gpoh='git push -u origin HEAD' 235 | 3: gco='git checkout' 236 | 2: v='vim' 237 | 2: ll='ls -lh --group-directories-first' 238 | 2: gpohw='gpoh && git web --pull-request' 239 | 2: gc='git commit' 240 | 2: gap='git add -p' 241 | 2: ap='ansible-playbook' 242 | 1: xopen='GDK_BACKEND=wayland xdg-open' 243 | 1: t='tig' 244 | 1: gw='git web' 245 | 1: gs='git status' 246 | 247 | 248 | Disable Hints for specific Aliases 249 | ---------------------------------- 250 | 251 | You can tell ``you-should-use`` to permanently ignore certain aliases by including them in the ``YSU_IGNORED_ALIASES`` variable (which is an array): 252 | 253 | :: 254 | 255 | $ ls -l 256 | Found existing alias for "ls -l". You should use: "ll" 257 | 258 | $ export YSU_IGNORED_ALIASES=("g" "ll") 259 | $ ls -l 260 | 261 | If you want to ignore global aliases, use the ``YSU_IGNORED_GLOBAL_ALIASES`` environment variable. 262 | 263 | :: 264 | 265 | $ cd ../.. 266 | Found existing global alias for "../..". You should use: "..." 267 | 268 | $ export YSU_IGNORED_GLOBAL_ALIASES=("...") 269 | $ cd ../.. 270 | 271 | 272 | Temporarily Disabling Messages 273 | ------------------------------ 274 | 275 | You can temporarily disable you should use by running the command ``disable_you_should_use``. 276 | 277 | When you want to re-enable messages, run the command ``enable_you_should_use``. 278 | 279 | Contributing 280 | ------------ 281 | 282 | Pull requests and Feedback are welcome! :tada: 283 | 284 | I have tried to cater for as many use cases that I can think of. 285 | However, they are naturally tailored to to my own workflow and I could 286 | be missing many others. 287 | 288 | Because of this if there is a specific use case that does not work as 289 | you would expect or if you have any suggestions to how the plugin should 290 | behave, feel free to `open an 291 | issue `__ 292 | 293 | Running Tests 294 | ------------- 295 | 296 | Install `zunit `__. Run ``zunit`` in the root 297 | directory of the repo. 298 | 299 | :: 300 | 301 | $ zunit 302 | Launching ZUnit 303 | ZUnit: 0.8.2 304 | ZSH: zsh 5.3.1 (x86_64-suse-linux-gnu) 305 | 306 | ✔ ysu message correct output 307 | ✔ ysu global message correct output 308 | ✔ ysu git message correct output 309 | 310 | NOTE: It is required that you use a minimum zunit version of 0.8.2 311 | 312 | NOTE: The tests that run move your git configuration to a temporary 313 | location during the test process so that your user configuration does 314 | not interfere with tests. This will be restored at the end of each test 315 | so there is no risk in losing your settings. If for some strange reason 316 | zunit crashes or does not restore your git configuration automatically, 317 | all you need to do is run ``mv ~/.gitconfig.bak ~/.gitconfig`` 318 | 319 | .. _Zplug: https://github.com/zplug/zplug 320 | 321 | .. _Antigen: https://github.com/zsh-users/antigen 322 | 323 | .. _ZGen: https://github.com/tarjoilija/zgen 324 | 325 | .. _Fig: https://fig.io 326 | 327 | .. _oh-my-zsh: https://github.com/robbyrussell/oh-my-zsh 328 | 329 | .. _Arch Linux: https://www.archlinux.org/ 330 | 331 | .. _AUR: https://aur.archlinux.org/packages/zsh-you-should-use/ 332 | 333 | .. _CHANGELOG: CHANGELOG.md 334 | 335 | .. |GPLv3| image:: https://img.shields.io/badge/License-GPL%20v3-blue.svg 336 | :target: https://www.gnu.org/licenses/gpl-3.0 337 | 338 | .. |CircleCI| image:: https://circleci.com/gh/MichaelAquilina/zsh-you-should-use.svg?style=svg 339 | :target: https://circleci.com/gh/MichaelAquilina/zsh-you-should-use 340 | 341 | .. |Version| image:: https://badge.fury.io/gh/MichaelAquilina%2Fzsh-you-should-use.svg 342 | :target: https://badge.fury.io/gh/MichaelAquilina%2Fzsh-you-should-use 343 | -------------------------------------------------------------------------------- /claude/.claude/agents/tdd-guardian.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: tdd-guardian 3 | description: > 4 | Use this agent proactively to guide Test-Driven Development throughout the coding process and reactively to verify TDD compliance. Invoke when users plan to write code, have written code, or when tests are green (for refactoring assessment). 5 | tools: Read, Grep, Glob, Bash 6 | model: sonnet 7 | color: red 8 | --- 9 | 10 | # TDD Guardian 11 | 12 | You are the TDD Guardian, an elite Test-Driven Development coach and enforcer. Your mission is dual: 13 | 14 | 1. **PROACTIVE COACHING** - Guide users through proper TDD before violations occur 15 | 2. **REACTIVE ANALYSIS** - Verify TDD compliance after code is written 16 | 17 | **Core Principle:** EVERY SINGLE LINE of production code must be written in response to a failing test. This is non-negotiable. 18 | 19 | ## Sacred Cycle: RED → GREEN → REFACTOR 20 | 21 | 1. **RED**: Write a failing test describing desired behavior 22 | 2. **GREEN**: Write MINIMUM code to make it pass (resist over-engineering) 23 | 3. **REFACTOR**: Assess if improvement adds value (not always needed) 24 | 25 | ## Your Dual Role 26 | 27 | ### When Invoked PROACTIVELY (User Planning Code) 28 | 29 | **Your job:** Guide them through TDD BEFORE they write production code. 30 | 31 | **Process:** 32 | 1. **Identify the simplest behavior** to test first 33 | 2. **Help write the failing test** that describes business behavior 34 | 3. **Ensure test is behavior-focused**, not implementation-focused 35 | 4. **Stop them** if they try to write production code before the test 36 | 5. **Guide minimal implementation** - only enough to pass 37 | 6. **Prompt refactoring assessment** when tests are green 38 | 39 | **Response Pattern:** 40 | ``` 41 | "Let's start with TDD. What's the simplest behavior we can test first? 42 | 43 | We'll: 44 | 1. Write a failing test for that specific behavior 45 | 2. Implement just enough code to make it pass 46 | 3. Assess if refactoring would add value 47 | 48 | What behavior should we test?" 49 | ``` 50 | 51 | ### When Invoked REACTIVELY (Code Already Written) 52 | 53 | **Your job:** Analyze whether TDD was followed properly. 54 | 55 | **Analysis Process:** 56 | 57 | #### 1. Examine Recent Changes 58 | ```bash 59 | git diff 60 | git status 61 | git log --oneline -5 62 | ``` 63 | - Identify modified production files 64 | - Identify modified test files 65 | - Separate new code from changes 66 | 67 | #### 2. Verify Test-First Development 68 | For each production code change: 69 | - Locate the corresponding test 70 | - Check git history: `git log -p ` to see if test came first 71 | - Verify test was failing before implementation 72 | 73 | #### 3. Validate Test Quality 74 | Check that tests follow principles: 75 | - ✅ Tests describe WHAT the code should do (behavior) 76 | - ❌ Tests do NOT describe HOW it does it (implementation) 77 | - ✅ Tests use the public API only 78 | - ❌ Tests do NOT access private methods or internal state 79 | - ✅ Tests have descriptive names documenting business behavior 80 | - ❌ Tests do NOT have names like "should call X method" 81 | - ✅ Tests use factory functions for test data 82 | - ❌ Tests do NOT use `let` declarations or `beforeEach` 83 | 84 | #### 4. Check for TDD Violations 85 | 86 | **Common violations:** 87 | - ❌ Production code without a failing test first 88 | - ❌ Multiple tests written before making first one pass 89 | - ❌ More production code than needed to pass current test 90 | - ❌ Adding features "while you're there" without tests 91 | - ❌ Tests examining implementation details 92 | - ❌ Missing edge case tests 93 | - ❌ Using `any` types or type assertions in tests 94 | - ❌ Using `let` or `beforeEach` (should use factories) 95 | - ❌ Skipping refactoring assessment when green 96 | 97 | #### 5. Generate Structured Report 98 | 99 | Use this format: 100 | 101 | ``` 102 | ## TDD Guardian Analysis 103 | 104 | ### ✅ Passing Checks 105 | - All production code has corresponding tests 106 | - Tests use public APIs only 107 | - Test names describe business behavior 108 | - Factory functions used for test data 109 | 110 | ### ⚠️ Issues Found 111 | 112 | #### 1. Test written after production code 113 | **File**: `src/payment/payment-processor.ts:45-67` 114 | **Issue**: Function `calculateDiscount` was implemented without a failing test first 115 | **Impact**: Violates fundamental TDD principle - no production code without failing test 116 | **Git Evidence**: `git log -p` shows implementation committed before test 117 | **Recommendation**: 118 | 1. Remove or comment out the `calculateDiscount` function 119 | 2. Write a failing test describing the discount behavior 120 | 3. Implement minimal code to pass the test 121 | 4. Refactor if needed 122 | 123 | #### 2. Implementation-focused test 124 | **File**: `src/payment/payment-processor.test.ts:89-95` 125 | **Test**: "should call validatePaymentAmount" 126 | **Issue**: Test checks if internal method is called (implementation detail) 127 | **Impact**: Test is brittle and doesn't verify actual behavior 128 | **Recommendation**: 129 | Replace with behavior-focused tests: 130 | - "should reject payments with negative amounts" 131 | - "should reject payments exceeding maximum amount" 132 | Test the outcome, not the internal call 133 | 134 | #### 3. Missing edge case coverage 135 | **File**: `src/order/order-processor.ts:23-31` 136 | **Issue**: Free shipping logic has no test for exactly £50 boundary 137 | **Impact**: Boundary condition untested - may have off-by-one error 138 | **Recommendation**: Add test case for order total exactly at £50 threshold 139 | 140 | ### 📊 Coverage Assessment 141 | - Production files changed: 3 142 | - Test files changed: 2 143 | - Untested production code: 1 function 144 | - Behavior coverage: ~85% (missing edge cases) 145 | 146 | ### 🎯 Next Steps 147 | 1. Fix the test-first violation in payment-processor.ts 148 | 2. Refactor implementation-focused tests to behavior-focused tests 149 | 3. Add missing edge case tests 150 | 4. Achieve 100% behavior coverage before proceeding 151 | ``` 152 | 153 | ## Coaching Guidance by Phase 154 | 155 | ### RED PHASE (Writing Failing Test) 156 | 157 | **Guide users to:** 158 | - Start with simplest behavior 159 | - Test ONE thing at a time 160 | - Use factory functions for test data (not `let`/`beforeEach`) 161 | - Focus on business behavior, not implementation 162 | - Write descriptive test names 163 | 164 | **Example:** 165 | ```typescript 166 | // ✅ GOOD - Behavior-focused, uses factory 167 | it("should reject payments with negative amounts", () => { 168 | const payment = getMockPayment({ amount: -100 }); 169 | const result = processPayment(payment); 170 | expect(result.success).toBe(false); 171 | expect(result.error.message).toBe("Invalid amount"); 172 | }); 173 | 174 | // ❌ BAD - Implementation-focused, uses let 175 | let payment: Payment; 176 | beforeEach(() => { 177 | payment = { amount: 100 }; 178 | }); 179 | it("should call validateAmount", () => { 180 | const spy = jest.spyOn(validator, 'validateAmount'); 181 | processPayment(payment); 182 | expect(spy).toHaveBeenCalled(); 183 | }); 184 | ``` 185 | 186 | ### GREEN PHASE (Implementing) 187 | 188 | **Ensure users:** 189 | - Write ONLY enough code to pass current test 190 | - Resist adding "just in case" logic 191 | - No speculative features 192 | - If writing more than needed: STOP and question why 193 | 194 | **Challenge over-implementation:** 195 | "I notice you're adding [X feature]. Is there a failing test demanding this code? If not, we should remove it and only implement what the current test requires." 196 | 197 | ### REFACTOR PHASE (Improving) 198 | 199 | **Assessment checklist:** 200 | - Are there magic numbers → Extract constants 201 | - Are names unclear → Improve naming 202 | - Is logic complex → Extract functions 203 | - Is there knowledge duplication → Create single source of truth 204 | - Is structure nested → Use early returns 205 | 206 | **Important:** Not all code needs refactoring. If clean, say so: 207 | "The code is already clean and expressive. No refactoring needed. Let's commit and move to the next test." 208 | 209 | **Refactoring rules:** 210 | - Commit current code FIRST 211 | - External APIs stay unchanged 212 | - All tests must still pass 213 | - Commit refactoring separately 214 | 215 | ## Response Patterns 216 | 217 | ### User Asks to Implement Feature 218 | ``` 219 | "Let's start with TDD. What's the simplest behavior we can test first? 220 | 221 | We'll write a failing test for that behavior, implement just enough to pass, then assess refactoring. 222 | 223 | What's the first behavior to test?" 224 | ``` 225 | 226 | ### User Shows Code Without Mentioning Tests 227 | ``` 228 | "STOP. Before we proceed, I need to see the failing test that drove this implementation. 229 | 230 | TDD is non-negotiable - production code must be written in response to a failing test. 231 | 232 | Can you show me the test that demanded this code?" 233 | ``` 234 | 235 | ### Tests Are Green 236 | ``` 237 | "Tests are green! Now let's assess refactoring opportunities: 238 | 239 | ✅ Already clean: 240 | - Clear function names 241 | - No magic numbers 242 | - Simple structure 243 | 244 | The code doesn't need refactoring. Let's commit and move to the next test." 245 | ``` 246 | 247 | OR if refactoring would help: 248 | 249 | ``` 250 | "Tests are green! I've identified refactoring opportunities: 251 | 252 | 🔴 Critical: 253 | - Magic number 10000 repeated 3 times → Extract MAX_PAYMENT_AMOUNT constant 254 | 255 | ⚠️ Should fix: 256 | - Nested conditionals in validatePayment → Use early returns 257 | 258 | Let's refactor these while tests stay green." 259 | ``` 260 | 261 | ### User Suggests Skipping Tests 262 | ``` 263 | "Absolutely not. TDD is the fundamental practice that enables all other principles. 264 | 265 | If you're typing production code without a failing test, you're not doing TDD. 266 | 267 | Let's write the test first. What behavior are we testing?" 268 | ``` 269 | 270 | ## Quality Gates 271 | 272 | Before allowing any commit, verify: 273 | - ✅ All production code has a test that demanded it 274 | - ✅ Tests verify behavior, not implementation 275 | - ✅ Implementation is minimal (only what's needed) 276 | - ✅ Refactoring assessment completed (if tests green) 277 | - ✅ All tests pass 278 | - ✅ TypeScript strict mode satisfied 279 | - ✅ No `any` types or unjustified assertions 280 | - ✅ Factory functions used (no `let`/`beforeEach`) 281 | 282 | ## Project-Specific Guidelines 283 | 284 | From CLAUDE.md: 285 | 286 | **Type System:** 287 | - Use `type` for data structures (with `readonly`) 288 | - Use `interface` only for behavior contracts/ports 289 | - Prefer options objects over positional parameters 290 | - Schema-first development with Zod 291 | 292 | **Code Style:** 293 | - No comments (code should be self-documenting) 294 | - Pure functions and immutable data 295 | - Early returns over nested conditionals 296 | - Factory functions for test data 297 | 298 | **Test Data Pattern:** 299 | ```typescript 300 | // ✅ CORRECT - Factory with optional overrides 301 | const getMockPayment = ( 302 | overrides?: Partial 303 | ): Payment => { 304 | return { 305 | amount: 100, 306 | currency: "GBP", 307 | cardId: "card_123", 308 | ...overrides, 309 | }; 310 | }; 311 | 312 | // Usage 313 | const payment = getMockPayment({ amount: -100 }); 314 | ``` 315 | 316 | ## Commands to Use 317 | 318 | - `git diff` - See what changed 319 | - `git status` - See current state 320 | - `git log --oneline -n 20` - Recent commits 321 | - `git log -p ` - File history to verify test-first 322 | - `Grep` - Search for test patterns 323 | - `Read` - Examine specific files 324 | - `Glob` - Find test files 325 | 326 | ## Your Mandate 327 | 328 | Be **strict but constructive**. TDD is non-negotiable, but your goal is education, not punishment. 329 | 330 | When violations occur: 331 | 1. Call them out clearly 332 | 2. Explain WHY it matters 333 | 3. Show HOW to fix it 334 | 4. Guide proper practice 335 | 336 | **REMEMBER:** 337 | - You are the guardian of TDD practice 338 | - Every line of production code needs a failing test 339 | - Tests drive design and implementation 340 | - This is the foundation of quality software 341 | 342 | **Your role is to ensure TDD becomes second nature, not a burden.** 343 | -------------------------------------------------------------------------------- /claude/.claude/skills/react-testing/SKILL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: react-testing 3 | description: React Testing Library patterns for testing React components, hooks, and context. Use when testing React applications. 4 | --- 5 | 6 | # React Testing Library 7 | 8 | This skill focuses on React-specific testing patterns. For general DOM testing patterns (queries, userEvent, async, accessibility), load the `front-end-testing` skill. For TDD workflow, load the `tdd` skill. 9 | 10 | --- 11 | 12 | ## Testing React Components 13 | 14 | **React components are just functions that return JSX.** Test them like functions: inputs (props) → output (rendered DOM). 15 | 16 | ### Basic Component Testing 17 | 18 | ```tsx 19 | // ✅ CORRECT - Test component behavior 20 | it('should display user name when provided', () => { 21 | render(); 22 | 23 | expect(screen.getByText(/alice/i)).toBeInTheDocument(); 24 | expect(screen.getByText(/alice@example.com/i)).toBeInTheDocument(); 25 | }); 26 | ``` 27 | 28 | ```tsx 29 | // ❌ WRONG - Testing implementation 30 | it('should set name state', () => { 31 | const wrapper = mount(); 32 | expect(wrapper.state('name')).toBe('Alice'); // Internal state! 33 | }); 34 | ``` 35 | 36 | ### Testing Props 37 | 38 | ```tsx 39 | // ✅ CORRECT - Test how props affect rendered output 40 | it('should call onSubmit when form submitted', async () => { 41 | const handleSubmit = vi.fn(); 42 | const user = userEvent.setup(); 43 | 44 | render(); 45 | 46 | await user.type(screen.getByLabelText(/email/i), 'test@example.com'); 47 | await user.click(screen.getByRole('button', { name: /submit/i })); 48 | 49 | expect(handleSubmit).toHaveBeenCalledWith({ 50 | email: 'test@example.com', 51 | }); 52 | }); 53 | ``` 54 | 55 | ### Testing Conditional Rendering 56 | 57 | ```tsx 58 | // ✅ CORRECT - Test what user sees in different states 59 | it('should show error message when login fails', async () => { 60 | server.use( 61 | http.post('/api/login', () => { 62 | return HttpResponse.json({ error: 'Invalid credentials' }, { status: 401 }); 63 | }) 64 | ); 65 | 66 | const user = userEvent.setup(); 67 | render(); 68 | 69 | await user.type(screen.getByLabelText(/email/i), 'wrong@example.com'); 70 | await user.click(screen.getByRole('button', { name: /submit/i })); 71 | 72 | await screen.findByText(/invalid credentials/i); 73 | }); 74 | ``` 75 | 76 | --- 77 | 78 | ## Testing React Hooks 79 | 80 | ### Custom Hooks with renderHook 81 | 82 | **Built into React Testing Library** (since v13): 83 | 84 | ```tsx 85 | import { renderHook } from '@testing-library/react'; 86 | 87 | it('should toggle value', () => { 88 | const { result } = renderHook(() => useToggle(false)); 89 | 90 | expect(result.current.value).toBe(false); 91 | 92 | act(() => { 93 | result.current.toggle(); 94 | }); 95 | 96 | expect(result.current.value).toBe(true); 97 | }); 98 | ``` 99 | 100 | **Pattern:** 101 | - `result.current` - Current return value of hook 102 | - `act()` - Wrap state updates 103 | - `rerender()` - Re-run hook with new props 104 | 105 | ### Hooks with Props 106 | 107 | ```tsx 108 | it('should accept initial value', () => { 109 | const { result, rerender } = renderHook( 110 | ({ initialValue }) => useCounter(initialValue), 111 | { initialProps: { initialValue: 10 } } 112 | ); 113 | 114 | expect(result.current.count).toBe(10); 115 | 116 | // Test with different initial value 117 | rerender({ initialValue: 20 }); 118 | expect(result.current.count).toBe(20); 119 | }); 120 | ``` 121 | 122 | --- 123 | 124 | ## Testing Context 125 | 126 | ### wrapper Option 127 | 128 | **For hooks that need context providers:** 129 | 130 | ```tsx 131 | const { result } = renderHook(() => useAuth(), { 132 | wrapper: ({ children }) => ( 133 | 134 | {children} 135 | 136 | ), 137 | }); 138 | 139 | expect(result.current.user).toBeNull(); 140 | 141 | act(() => { 142 | result.current.login({ email: 'test@example.com' }); 143 | }); 144 | 145 | expect(result.current.user).toEqual({ email: 'test@example.com' }); 146 | ``` 147 | 148 | ### Multiple Providers 149 | 150 | ```tsx 151 | const AllProviders = ({ children }) => ( 152 | 153 | 154 | 155 | {children} 156 | 157 | 158 | 159 | ); 160 | 161 | const { result } = renderHook(() => useMyHook(), { 162 | wrapper: AllProviders, 163 | }); 164 | ``` 165 | 166 | ### Testing Components with Context 167 | 168 | ```tsx 169 | // ✅ CORRECT - Wrap component in provider 170 | const renderWithAuth = (ui, { user = null, ...options } = {}) => { 171 | return render( 172 | 173 | {ui} 174 | , 175 | options 176 | ); 177 | }; 178 | 179 | it('should show user menu when authenticated', () => { 180 | renderWithAuth(, { 181 | user: { name: 'Alice', role: 'admin' }, 182 | }); 183 | 184 | expect(screen.getByRole('button', { name: /user menu/i })).toBeInTheDocument(); 185 | }); 186 | ``` 187 | 188 | --- 189 | 190 | ## Testing Forms 191 | 192 | ### Controlled Inputs 193 | 194 | ```tsx 195 | it('should update input value as user types', async () => { 196 | const user = userEvent.setup(); 197 | 198 | render(); 199 | 200 | const input = screen.getByLabelText(/search/i); 201 | 202 | await user.type(input, 'react'); 203 | 204 | expect(input).toHaveValue('react'); 205 | }); 206 | ``` 207 | 208 | ### Form Submissions 209 | 210 | ```tsx 211 | it('should submit form with user input', async () => { 212 | const handleSubmit = vi.fn(); 213 | const user = userEvent.setup(); 214 | 215 | render(); 216 | 217 | await user.type(screen.getByLabelText(/name/i), 'Alice'); 218 | await user.type(screen.getByLabelText(/email/i), 'alice@example.com'); 219 | await user.type(screen.getByLabelText(/password/i), 'password123'); 220 | await user.click(screen.getByRole('button', { name: /sign up/i })); 221 | 222 | expect(handleSubmit).toHaveBeenCalledWith({ 223 | name: 'Alice', 224 | email: 'alice@example.com', 225 | password: 'password123', 226 | }); 227 | }); 228 | ``` 229 | 230 | ### Form Validation 231 | 232 | ```tsx 233 | it('should show validation errors for invalid input', async () => { 234 | const user = userEvent.setup(); 235 | 236 | render(); 237 | 238 | // Submit empty form 239 | await user.click(screen.getByRole('button', { name: /sign up/i })); 240 | 241 | // Validation errors appear 242 | expect(screen.getByText(/name is required/i)).toBeInTheDocument(); 243 | expect(screen.getByText(/email is required/i)).toBeInTheDocument(); 244 | expect(screen.getByText(/password is required/i)).toBeInTheDocument(); 245 | }); 246 | ``` 247 | 248 | --- 249 | 250 | ## React-Specific Anti-Patterns 251 | 252 | ### 1. Unnecessary act() wrapping 253 | 254 | ❌ **WRONG - Manual act() everywhere** 255 | ```tsx 256 | act(() => { 257 | render(); 258 | }); 259 | 260 | await act(async () => { 261 | await user.click(button); 262 | }); 263 | ``` 264 | 265 | ✅ **CORRECT - RTL handles it** 266 | ```tsx 267 | render(); 268 | await user.click(button); 269 | ``` 270 | 271 | **Modern RTL auto-wraps:** 272 | - `render()` 273 | - `userEvent` methods 274 | - `fireEvent` 275 | - `waitFor`, `findBy` 276 | 277 | **When you DO need manual `act()`:** 278 | - Custom hook state updates (`renderHook`) 279 | - Direct state mutations (rare, usually bad practice) 280 | 281 | --- 282 | 283 | ### 2. Manual cleanup() calls 284 | 285 | ❌ **WRONG - Manual cleanup** 286 | ```tsx 287 | afterEach(() => { 288 | cleanup(); // Automatic since RTL 9! 289 | }); 290 | ``` 291 | 292 | ✅ **CORRECT - No cleanup needed** 293 | ```tsx 294 | // Cleanup happens automatically after each test 295 | ``` 296 | 297 | --- 298 | 299 | ### 3. beforeEach render pattern 300 | 301 | ❌ **WRONG - Shared render in beforeEach** 302 | ```tsx 303 | let button; 304 | beforeEach(() => { 305 | render(); 306 | button = screen.getByRole('button'); // Shared state across tests 307 | }); 308 | 309 | it('test 1', () => { 310 | // Uses shared button from beforeEach 311 | }); 312 | ``` 313 | 314 | ✅ **CORRECT - Factory function per test** 315 | ```tsx 316 | const renderComponent = () => { 317 | render(); 318 | return { 319 | button: screen.getByRole('button'), 320 | }; 321 | }; 322 | 323 | it('test 1', () => { 324 | const { button } = renderComponent(); // Fresh state 325 | }); 326 | ``` 327 | 328 | For factory patterns, see `testing` skill. 329 | 330 | --- 331 | 332 | ### 4. Testing component internals 333 | 334 | ❌ **WRONG - Accessing component internals** 335 | ```tsx 336 | const wrapper = shallow(); 337 | expect(wrapper.state('isOpen')).toBe(true); // Internal state 338 | expect(wrapper.instance().handleClick).toBeDefined(); // Internal method 339 | ``` 340 | 341 | ✅ **CORRECT - Test rendered output** 342 | ```tsx 343 | render(); 344 | expect(screen.getByRole('dialog')).toBeInTheDocument(); // What user sees 345 | ``` 346 | 347 | --- 348 | 349 | ### 5. Shallow rendering 350 | 351 | ❌ **WRONG - Shallow rendering** 352 | ```tsx 353 | const wrapper = shallow(); 354 | // Child components not rendered - incomplete test 355 | ``` 356 | 357 | ✅ **CORRECT - Full rendering** 358 | ```tsx 359 | render(); 360 | // Full component tree rendered - realistic test 361 | ``` 362 | 363 | **Why:** Shallow rendering hides integration bugs between parent/child components. 364 | 365 | --- 366 | 367 | ## Testing Loading States 368 | 369 | ```tsx 370 | it('should show loading then data', async () => { 371 | render(); 372 | 373 | // Initially loading 374 | expect(screen.getByText(/loading/i)).toBeInTheDocument(); 375 | 376 | // Wait for data 377 | await screen.findByText(/alice/i); 378 | 379 | // Loading gone 380 | expect(screen.queryByText(/loading/i)).not.toBeInTheDocument(); 381 | }); 382 | ``` 383 | 384 | --- 385 | 386 | ## Testing Error Boundaries 387 | 388 | ```tsx 389 | it('should catch errors with error boundary', () => { 390 | // Suppress console.error for this test 391 | const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); 392 | 393 | render( 394 | Something went wrong}> 395 | 396 | 397 | ); 398 | 399 | expect(screen.getByText(/something went wrong/i)).toBeInTheDocument(); 400 | 401 | spy.mockRestore(); 402 | }); 403 | ``` 404 | 405 | --- 406 | 407 | ## Testing Portals 408 | 409 | ```tsx 410 | it('should render modal in portal', () => { 411 | render(Modal content); 412 | 413 | // Portal renders outside root, but Testing Library finds it 414 | expect(screen.getByText(/modal content/i)).toBeInTheDocument(); 415 | }); 416 | ``` 417 | 418 | **Testing Library queries the entire document,** so portals work automatically. 419 | 420 | --- 421 | 422 | ## Testing Suspense 423 | 424 | ```tsx 425 | it('should show fallback then content', async () => { 426 | render( 427 | Loading...}> 428 | 429 | 430 | ); 431 | 432 | // Initially fallback 433 | expect(screen.getByText(/loading/i)).toBeInTheDocument(); 434 | 435 | // Wait for component 436 | await screen.findByText(/lazy content/i); 437 | }); 438 | ``` 439 | 440 | --- 441 | 442 | ## Summary Checklist 443 | 444 | React-specific checks: 445 | 446 | - [ ] Using `render()` from @testing-library/react (not enzyme's shallow/mount) 447 | - [ ] Using `renderHook()` for custom hooks 448 | - [ ] Using `wrapper` option for context providers 449 | - [ ] No manual `act()` calls (RTL handles it) 450 | - [ ] No manual `cleanup()` calls (automatic) 451 | - [ ] Testing component output, not internal state 452 | - [ ] Using factory functions, not `beforeEach` render 453 | - [ ] Following TDD workflow (see `tdd` skill) 454 | - [ ] Using general DOM testing patterns (see `front-end-testing` skill) 455 | - [ ] Using test factories for data (see `testing` skill) 456 | -------------------------------------------------------------------------------- /zellij/.config/zellij/config.kdl: -------------------------------------------------------------------------------- 1 | // If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true" 2 | keybinds { 3 | normal { 4 | // uncomment this and adjust key if using copy_on_select=false 5 | // bind "Alt c" { Copy; } 6 | } 7 | locked { 8 | bind "Ctrl g" { SwitchToMode "Normal"; } 9 | } 10 | resize { 11 | bind "Ctrl n" { SwitchToMode "Normal"; } 12 | bind "h" "Left" { Resize "Increase Left"; } 13 | bind "j" "Down" { Resize "Increase Down"; } 14 | bind "k" "Up" { Resize "Increase Up"; } 15 | bind "l" "Right" { Resize "Increase Right"; } 16 | bind "H" { Resize "Decrease Left"; } 17 | bind "J" { Resize "Decrease Down"; } 18 | bind "K" { Resize "Decrease Up"; } 19 | bind "L" { Resize "Decrease Right"; } 20 | bind "=" "+" { Resize "Increase"; } 21 | bind "-" { Resize "Decrease"; } 22 | } 23 | pane { 24 | bind "Ctrl p" { SwitchToMode "Normal"; } 25 | bind "h" "Left" { MoveFocus "Left"; } 26 | bind "l" "Right" { MoveFocus "Right"; } 27 | bind "j" "Down" { MoveFocus "Down"; } 28 | bind "k" "Up" { MoveFocus "Up"; } 29 | bind "p" { SwitchFocus; } 30 | bind "n" { NewPane; SwitchToMode "Normal"; } 31 | bind "d" { NewPane "Down"; SwitchToMode "Normal"; } 32 | bind "r" { NewPane "Right"; SwitchToMode "Normal"; } 33 | bind "x" { CloseFocus; SwitchToMode "Normal"; } 34 | bind "f" { ToggleFocusFullscreen; SwitchToMode "Normal"; } 35 | bind "z" { TogglePaneFrames; SwitchToMode "Normal"; } 36 | bind "w" { ToggleFloatingPanes; SwitchToMode "Normal"; } 37 | bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "Normal"; } 38 | bind "c" { SwitchToMode "RenamePane"; PaneNameInput 0;} 39 | } 40 | move { 41 | bind "Ctrl h" { SwitchToMode "Normal"; } 42 | bind "n" "Tab" { MovePane; } 43 | bind "p" { MovePaneBackwards; } 44 | bind "h" "Left" { MovePane "Left"; } 45 | bind "j" "Down" { MovePane "Down"; } 46 | bind "k" "Up" { MovePane "Up"; } 47 | bind "l" "Right" { MovePane "Right"; } 48 | } 49 | tab { 50 | bind "Ctrl t" { SwitchToMode "Normal"; } 51 | bind "r" { SwitchToMode "RenameTab"; TabNameInput 0; } 52 | bind "h" "Left" "Up" "k" { GoToPreviousTab; } 53 | bind "l" "Right" "Down" "j" { GoToNextTab; } 54 | bind "n" { NewTab; SwitchToMode "Normal"; } 55 | bind "x" { CloseTab; SwitchToMode "Normal"; } 56 | bind "s" { ToggleActiveSyncTab; SwitchToMode "Normal"; } 57 | bind "b" { BreakPane; SwitchToMode "Normal"; } 58 | bind "]" { BreakPaneRight; SwitchToMode "Normal"; } 59 | bind "[" { BreakPaneLeft; SwitchToMode "Normal"; } 60 | bind "1" { GoToTab 1; SwitchToMode "Normal"; } 61 | bind "2" { GoToTab 2; SwitchToMode "Normal"; } 62 | bind "3" { GoToTab 3; SwitchToMode "Normal"; } 63 | bind "4" { GoToTab 4; SwitchToMode "Normal"; } 64 | bind "5" { GoToTab 5; SwitchToMode "Normal"; } 65 | bind "6" { GoToTab 6; SwitchToMode "Normal"; } 66 | bind "7" { GoToTab 7; SwitchToMode "Normal"; } 67 | bind "8" { GoToTab 8; SwitchToMode "Normal"; } 68 | bind "9" { GoToTab 9; SwitchToMode "Normal"; } 69 | bind "Tab" { ToggleTab; } 70 | } 71 | scroll { 72 | bind "Ctrl s" { SwitchToMode "Normal"; } 73 | bind "e" { EditScrollback; SwitchToMode "Normal"; } 74 | bind "s" { SwitchToMode "EnterSearch"; SearchInput 0; } 75 | bind "Ctrl c" { ScrollToBottom; SwitchToMode "Normal"; } 76 | bind "j" "Down" { ScrollDown; } 77 | bind "k" "Up" { ScrollUp; } 78 | bind "Ctrl f" "PageDown" "Right" "l" { PageScrollDown; } 79 | bind "Ctrl b" "PageUp" "Left" "h" { PageScrollUp; } 80 | bind "d" { HalfPageScrollDown; } 81 | bind "u" { HalfPageScrollUp; } 82 | // uncomment this and adjust key if using copy_on_select=false 83 | // bind "Alt c" { Copy; } 84 | } 85 | search { 86 | bind "Ctrl s" { SwitchToMode "Normal"; } 87 | bind "Ctrl c" { ScrollToBottom; SwitchToMode "Normal"; } 88 | bind "j" "Down" { ScrollDown; } 89 | bind "k" "Up" { ScrollUp; } 90 | bind "Ctrl f" "PageDown" "Right" "l" { PageScrollDown; } 91 | bind "Ctrl b" "PageUp" "Left" "h" { PageScrollUp; } 92 | bind "d" { HalfPageScrollDown; } 93 | bind "u" { HalfPageScrollUp; } 94 | bind "n" { Search "down"; } 95 | bind "p" { Search "up"; } 96 | bind "c" { SearchToggleOption "CaseSensitivity"; } 97 | bind "w" { SearchToggleOption "Wrap"; } 98 | bind "o" { SearchToggleOption "WholeWord"; } 99 | } 100 | entersearch { 101 | bind "Ctrl c" "Esc" { SwitchToMode "Scroll"; } 102 | bind "Enter" { SwitchToMode "Search"; } 103 | } 104 | renametab { 105 | bind "Ctrl c" { SwitchToMode "Normal"; } 106 | bind "Esc" { UndoRenameTab; SwitchToMode "Tab"; } 107 | } 108 | renamepane { 109 | bind "Ctrl c" { SwitchToMode "Normal"; } 110 | bind "Esc" { UndoRenamePane; SwitchToMode "Pane"; } 111 | } 112 | session { 113 | bind "Ctrl o" { SwitchToMode "Normal"; } 114 | bind "Ctrl s" { SwitchToMode "Scroll"; } 115 | bind "d" { Detach; } 116 | bind "w" { 117 | LaunchOrFocusPlugin "zellij:session-manager" { 118 | floating true 119 | move_to_focused_tab true 120 | }; 121 | SwitchToMode "Normal" 122 | } 123 | } 124 | tmux { 125 | bind "[" { SwitchToMode "Scroll"; } 126 | bind "Ctrl b" { Write 2; SwitchToMode "Normal"; } 127 | bind "\"" { NewPane "Down"; SwitchToMode "Normal"; } 128 | bind "%" { NewPane "Right"; SwitchToMode "Normal"; } 129 | bind "z" { ToggleFocusFullscreen; SwitchToMode "Normal"; } 130 | bind "c" { NewTab; SwitchToMode "Normal"; } 131 | bind "," { SwitchToMode "RenameTab"; } 132 | bind "p" { GoToPreviousTab; SwitchToMode "Normal"; } 133 | bind "n" { GoToNextTab; SwitchToMode "Normal"; } 134 | bind "Left" { MoveFocus "Left"; SwitchToMode "Normal"; } 135 | bind "Right" { MoveFocus "Right"; SwitchToMode "Normal"; } 136 | bind "Down" { MoveFocus "Down"; SwitchToMode "Normal"; } 137 | bind "Up" { MoveFocus "Up"; SwitchToMode "Normal"; } 138 | bind "h" { MoveFocus "Left"; SwitchToMode "Normal"; } 139 | bind "l" { MoveFocus "Right"; SwitchToMode "Normal"; } 140 | bind "j" { MoveFocus "Down"; SwitchToMode "Normal"; } 141 | bind "k" { MoveFocus "Up"; SwitchToMode "Normal"; } 142 | bind "o" { FocusNextPane; } 143 | bind "d" { Detach; } 144 | bind "Space" { NextSwapLayout; } 145 | bind "x" { CloseFocus; SwitchToMode "Normal"; } 146 | } 147 | shared_except "locked" { 148 | bind "Ctrl g" { SwitchToMode "Locked"; } 149 | bind "Ctrl q" { Quit; } 150 | bind "Alt n" { NewPane; } 151 | bind "Alt h" "Alt Left" { MoveFocusOrTab "Left"; } 152 | bind "Alt l" "Alt Right" { MoveFocusOrTab "Right"; } 153 | bind "Alt j" "Alt Down" { MoveFocus "Down"; } 154 | bind "Alt k" "Alt Up" { MoveFocus "Up"; } 155 | bind "Alt =" "Alt +" { Resize "Increase"; } 156 | bind "Alt -" { Resize "Decrease"; } 157 | bind "Alt [" { PreviousSwapLayout; } 158 | bind "Alt ]" { NextSwapLayout; } 159 | } 160 | shared_except "normal" "locked" { 161 | bind "Enter" "Esc" { SwitchToMode "Normal"; } 162 | } 163 | shared_except "pane" "locked" { 164 | bind "Ctrl p" { SwitchToMode "Pane"; } 165 | } 166 | shared_except "resize" "locked" { 167 | bind "Ctrl n" { SwitchToMode "Resize"; } 168 | } 169 | shared_except "scroll" "locked" { 170 | bind "Ctrl s" { SwitchToMode "Scroll"; } 171 | } 172 | shared_except "session" "locked" { 173 | bind "Ctrl o" { SwitchToMode "Session"; } 174 | } 175 | shared_except "tab" "locked" { 176 | bind "Ctrl t" { SwitchToMode "Tab"; } 177 | } 178 | shared_except "move" "locked" { 179 | bind "Ctrl h" { SwitchToMode "Move"; } 180 | } 181 | shared_except "tmux" "locked" { 182 | bind "Ctrl b" { SwitchToMode "Tmux"; } 183 | } 184 | } 185 | 186 | plugins { 187 | tab-bar { path "tab-bar"; } 188 | status-bar { path "status-bar"; } 189 | strider { path "strider"; } 190 | compact-bar { path "compact-bar"; } 191 | session-manager { path "session-manager"; } 192 | } 193 | 194 | // Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP 195 | // eg. when terminal window with an active zellij session is closed 196 | // Options: 197 | // - detach (Default) 198 | // - quit 199 | // 200 | // on_force_close "quit" 201 | 202 | // Send a request for a simplified ui (without arrow fonts) to plugins 203 | // Options: 204 | // - true 205 | // - false (Default) 206 | // 207 | // simplified_ui true 208 | 209 | // Choose the path to the default shell that zellij will use for opening new panes 210 | // Default: $SHELL 211 | // 212 | // default_shell "fish" 213 | 214 | // Choose the path to override cwd that zellij will use for opening new panes 215 | // 216 | // default_cwd "" 217 | 218 | // Toggle between having pane frames around the panes 219 | // Options: 220 | // - true (default) 221 | // - false 222 | // 223 | // pane_frames true 224 | 225 | // Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible 226 | // Options: 227 | // - true (default) 228 | // - false 229 | // 230 | // auto_layout true 231 | 232 | // Define color themes for Zellij 233 | // For more examples, see: https://github.com/zellij-org/zellij/tree/main/example/themes 234 | // Once these themes are defined, one of them should to be selected in the "theme" section of this file 235 | // 236 | // themes { 237 | // dracula { 238 | // fg 248 248 242 239 | // bg 40 42 54 240 | // red 255 85 85 241 | // green 80 250 123 242 | // yellow 241 250 140 243 | // blue 98 114 164 244 | // magenta 255 121 198 245 | // orange 255 184 108 246 | // cyan 139 233 253 247 | // black 0 0 0 248 | // white 255 255 255 249 | // } 250 | // } 251 | 252 | // Choose the theme that is specified in the themes section. 253 | // Default: default 254 | // 255 | theme "catppuccin-mocha" 256 | 257 | // The name of the default layout to load on startup 258 | // Default: "default" 259 | // 260 | // default_layout "compact" 261 | 262 | // Choose the mode that zellij uses when starting up. 263 | // Default: normal 264 | // 265 | // default_mode "locked" 266 | 267 | // Toggle enabling the mouse mode. 268 | // On certain configurations, or terminals this could 269 | // potentially interfere with copying text. 270 | // Options: 271 | // - true (default) 272 | // - false 273 | // 274 | // mouse_mode false 275 | 276 | // Configure the scroll back buffer size 277 | // This is the number of lines zellij stores for each pane in the scroll back 278 | // buffer. Excess number of lines are discarded in a FIFO fashion. 279 | // Valid values: positive integers 280 | // Default value: 10000 281 | // 282 | // scroll_buffer_size 10000 283 | 284 | // Provide a command to execute when copying text. The text will be piped to 285 | // the stdin of the program to perform the copy. This can be used with 286 | // terminal emulators which do not support the OSC 52 ANSI control sequence 287 | // that will be used by default if this option is not set. 288 | // Examples: 289 | // 290 | // copy_command "xclip -selection clipboard" // x11 291 | // copy_command "wl-copy" // wayland 292 | // copy_command "pbcopy" // osx 293 | 294 | // Choose the destination for copied text 295 | // Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard. 296 | // Does not apply when using copy_command. 297 | // Options: 298 | // - system (default) 299 | // - primary 300 | // 301 | // copy_clipboard "primary" 302 | 303 | // Enable or disable automatic copy (and clear) of selection when releasing mouse 304 | // Default: true 305 | // 306 | // copy_on_select false 307 | 308 | // Path to the default editor to use to edit pane scrollbuffer 309 | // Default: $EDITOR or $VISUAL 310 | // 311 | // scrollback_editor "/usr/bin/vim" 312 | 313 | // When attaching to an existing session with other users, 314 | // should the session be mirrored (true) 315 | // or should each user have their own cursor (false) 316 | // Default: false 317 | // 318 | // mirror_session true 319 | 320 | // The folder in which Zellij will look for layouts 321 | // 322 | // layout_dir "/path/to/my/layout_dir" 323 | 324 | // The folder in which Zellij will look for themes 325 | // 326 | // theme_dir "/path/to/my/theme_dir" 327 | -------------------------------------------------------------------------------- /claude/.claude/skills/testing/SKILL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: testing 3 | description: Testing patterns for behavior-driven tests. Use when writing tests or test factories. 4 | --- 5 | 6 | # Testing Patterns 7 | 8 | ## Core Principle 9 | 10 | **Test behavior, not implementation.** 100% coverage through business behavior, not implementation details. 11 | 12 | **Example:** Validation code in `payment-validator.ts` gets 100% coverage by testing `processPayment()` behavior, NOT by directly testing validator functions. 13 | 14 | --- 15 | 16 | ## Test Through Public API Only 17 | 18 | Never test implementation details. Test behavior through public APIs. 19 | 20 | **Why this matters:** 21 | - Tests remain valid when refactoring 22 | - Tests document intended behavior 23 | - Tests catch real bugs, not implementation changes 24 | 25 | ### Examples 26 | 27 | ❌ **WRONG - Testing implementation:** 28 | ```typescript 29 | // ❌ Testing HOW (implementation detail) 30 | it('should call validateAmount', () => { 31 | const spy = jest.spyOn(validator, 'validateAmount'); 32 | processPayment(payment); 33 | expect(spy).toHaveBeenCalled(); // Tests HOW, not WHAT 34 | }); 35 | 36 | // ❌ Testing private methods 37 | it('should validate CVV format', () => { 38 | const result = validator._validateCVV('123'); // Private method! 39 | expect(result).toBe(true); 40 | }); 41 | 42 | // ❌ Testing internal state 43 | it('should set isValidated flag', () => { 44 | processPayment(payment); 45 | expect(processor.isValidated).toBe(true); // Internal state 46 | }); 47 | ``` 48 | 49 | ✅ **CORRECT - Testing behavior through public API:** 50 | ```typescript 51 | it('should reject negative amounts', () => { 52 | const payment = getMockPayment({ amount: -100 }); 53 | const result = processPayment(payment); 54 | expect(result.success).toBe(false); 55 | expect(result.error).toContain('Amount must be positive'); 56 | }); 57 | 58 | it('should reject invalid CVV', () => { 59 | const payment = getMockPayment({ cvv: '12' }); // Only 2 digits 60 | const result = processPayment(payment); 61 | expect(result.success).toBe(false); 62 | expect(result.error).toContain('Invalid CVV'); 63 | }); 64 | 65 | it('should process valid payments', () => { 66 | const payment = getMockPayment({ amount: 100, cvv: '123' }); 67 | const result = processPayment(payment); 68 | expect(result.success).toBe(true); 69 | expect(result.data.transactionId).toBeDefined(); 70 | }); 71 | ``` 72 | 73 | --- 74 | 75 | ## Coverage Through Behavior 76 | 77 | Validation code gets 100% coverage by testing the behavior it protects: 78 | 79 | ```typescript 80 | // Tests covering validation WITHOUT testing validator directly 81 | describe('processPayment', () => { 82 | it('should reject negative amounts', () => { 83 | const payment = getMockPayment({ amount: -100 }); 84 | const result = processPayment(payment); 85 | expect(result.success).toBe(false); 86 | }); 87 | 88 | it('should reject amounts over 10000', () => { 89 | const payment = getMockPayment({ amount: 15000 }); 90 | const result = processPayment(payment); 91 | expect(result.success).toBe(false); 92 | }); 93 | 94 | it('should reject invalid CVV', () => { 95 | const payment = getMockPayment({ cvv: '12' }); 96 | const result = processPayment(payment); 97 | expect(result.success).toBe(false); 98 | }); 99 | 100 | it('should process valid payments', () => { 101 | const payment = getMockPayment({ amount: 100, cvv: '123' }); 102 | const result = processPayment(payment); 103 | expect(result.success).toBe(true); 104 | }); 105 | }); 106 | 107 | // ✅ Result: payment-validator.ts has 100% coverage through behavior 108 | ``` 109 | 110 | **Key insight:** When coverage drops, ask **"What business behavior am I not testing?"** not "What line am I missing?" 111 | 112 | --- 113 | 114 | ## Test Factory Pattern 115 | 116 | For test data, use factory functions with optional overrides. 117 | 118 | ### Core Principles 119 | 120 | 1. Return complete objects with sensible defaults 121 | 2. Accept `Partial` overrides for customization 122 | 3. Validate with real schemas (don't redefine) 123 | 4. NO `let`/`beforeEach` - use factories for fresh state 124 | 125 | ### Basic Pattern 126 | 127 | ```typescript 128 | const getMockUser = (overrides?: Partial): User => { 129 | return UserSchema.parse({ 130 | id: 'user-123', 131 | name: 'Test User', 132 | email: 'test@example.com', 133 | role: 'user', 134 | ...overrides, 135 | }); 136 | }; 137 | 138 | // Usage 139 | it('creates user with custom email', () => { 140 | const user = getMockUser({ email: 'custom@example.com' }); 141 | const result = createUser(user); 142 | expect(result.success).toBe(true); 143 | }); 144 | ``` 145 | 146 | ### Complete Factory Example 147 | 148 | ```typescript 149 | import { UserSchema } from '@/schemas'; // Import real schema 150 | 151 | const getMockUser = (overrides?: Partial): User => { 152 | return UserSchema.parse({ 153 | id: 'user-123', 154 | name: 'Test User', 155 | email: 'test@example.com', 156 | role: 'user', 157 | isActive: true, 158 | createdAt: new Date('2024-01-01'), 159 | ...overrides, 160 | }); 161 | }; 162 | ``` 163 | 164 | **Why validate with schema?** 165 | - Ensures test data is valid according to production schema 166 | - Catches breaking changes early (schema changes fail tests) 167 | - Single source of truth (no schema redefinition) 168 | 169 | ### Factory Composition 170 | 171 | For nested objects, compose factories: 172 | 173 | ```typescript 174 | const getMockItem = (overrides?: Partial): Item => { 175 | return ItemSchema.parse({ 176 | id: 'item-1', 177 | name: 'Test Item', 178 | price: 100, 179 | ...overrides, 180 | }); 181 | }; 182 | 183 | const getMockOrder = (overrides?: Partial): Order => { 184 | return OrderSchema.parse({ 185 | id: 'order-1', 186 | items: [getMockItem()], // ✅ Compose factories 187 | customer: getMockCustomer(), // ✅ Compose factories 188 | payment: getMockPayment(), // ✅ Compose factories 189 | ...overrides, 190 | }); 191 | }; 192 | 193 | // Usage - override nested objects 194 | it('calculates total with multiple items', () => { 195 | const order = getMockOrder({ 196 | items: [ 197 | getMockItem({ price: 100 }), 198 | getMockItem({ price: 200 }), 199 | ], 200 | }); 201 | expect(calculateTotal(order)).toBe(300); 202 | }); 203 | ``` 204 | 205 | ### Anti-Patterns 206 | 207 | ❌ **WRONG: Using `let` and `beforeEach`** 208 | ```typescript 209 | let user: User; 210 | beforeEach(() => { 211 | user = { id: 'user-123', name: 'Test User', ... }; // Shared mutable state! 212 | }); 213 | 214 | it('test 1', () => { 215 | user.name = 'Modified User'; // Mutates shared state 216 | }); 217 | 218 | it('test 2', () => { 219 | expect(user.name).toBe('Test User'); // Fails! Modified by test 1 220 | }); 221 | ``` 222 | 223 | ✅ **CORRECT: Factory per test** 224 | ```typescript 225 | it('test 1', () => { 226 | const user = getMockUser({ name: 'Modified User' }); // Fresh state 227 | // ... 228 | }); 229 | 230 | it('test 2', () => { 231 | const user = getMockUser(); // Fresh state, not affected by test 1 232 | expect(user.name).toBe('Test User'); // ✅ Passes 233 | }); 234 | ``` 235 | 236 | ❌ **WRONG: Incomplete objects** 237 | ```typescript 238 | const getMockUser = () => ({ 239 | id: 'user-123', // Missing name, email, role! 240 | }); 241 | ``` 242 | 243 | ✅ **CORRECT: Complete objects** 244 | ```typescript 245 | const getMockUser = (overrides?: Partial): User => { 246 | return UserSchema.parse({ 247 | id: 'user-123', 248 | name: 'Test User', 249 | email: 'test@example.com', 250 | role: 'user', 251 | ...overrides, // All required fields present 252 | }); 253 | }; 254 | ``` 255 | 256 | ❌ **WRONG: Redefining schemas in tests** 257 | ```typescript 258 | // ❌ Schema already defined in src/schemas/user.ts! 259 | const UserSchema = z.object({ ... }); 260 | const getMockUser = () => UserSchema.parse({ ... }); 261 | ``` 262 | 263 | ✅ **CORRECT: Import real schema** 264 | ```typescript 265 | import { UserSchema } from '@/schemas/user'; 266 | 267 | const getMockUser = (overrides?: Partial): User => { 268 | return UserSchema.parse({ 269 | id: 'user-123', 270 | name: 'Test User', 271 | email: 'test@example.com', 272 | ...overrides, 273 | }); 274 | }; 275 | ``` 276 | 277 | --- 278 | 279 | ## Coverage Theater Detection 280 | 281 | Watch for these patterns that give fake 100% coverage: 282 | 283 | ### Pattern 1: Mock the function being tested 284 | 285 | ❌ **WRONG** - Gives 100% coverage but tests nothing: 286 | ```typescript 287 | it('calls validator', () => { 288 | const spy = jest.spyOn(validator, 'validate'); 289 | validate(payment); 290 | expect(spy).toHaveBeenCalled(); // Meaningless assertion 291 | }); 292 | ``` 293 | 294 | ✅ **CORRECT** - Test actual behavior: 295 | ```typescript 296 | it('should reject invalid payment', () => { 297 | const payment = getMockPayment({ amount: -100 }); 298 | const result = validate(payment); 299 | expect(result.success).toBe(false); 300 | expect(result.error).toContain('Amount must be positive'); 301 | }); 302 | ``` 303 | 304 | ### Pattern 2: Test only that function was called 305 | 306 | ❌ **WRONG** - No behavior validation: 307 | ```typescript 308 | it('processes payment', () => { 309 | const spy = jest.spyOn(processor, 'process'); 310 | handlePayment(payment); 311 | expect(spy).toHaveBeenCalledWith(payment); // So what? 312 | }); 313 | ``` 314 | 315 | ✅ **CORRECT** - Verify the outcome: 316 | ```typescript 317 | it('should process payment and return transaction ID', () => { 318 | const payment = getMockPayment(); 319 | const result = handlePayment(payment); 320 | expect(result.success).toBe(true); 321 | expect(result.transactionId).toBeDefined(); 322 | }); 323 | ``` 324 | 325 | ### Pattern 3: Test trivial getters/setters 326 | 327 | ❌ **WRONG** - Testing implementation, not behavior: 328 | ```typescript 329 | it('sets amount', () => { 330 | payment.setAmount(100); 331 | expect(payment.getAmount()).toBe(100); // Trivial 332 | }); 333 | ``` 334 | 335 | ✅ **CORRECT** - Test meaningful behavior: 336 | ```typescript 337 | it('should calculate total with tax', () => { 338 | const order = createOrder({ items: [item1, item2] }); 339 | const total = order.calculateTotal(); 340 | expect(total).toBe(230); // 200 + 15% tax 341 | }); 342 | ``` 343 | 344 | ### Pattern 4: 100% line coverage, 0% branch coverage 345 | 346 | ❌ **WRONG** - Missing edge cases: 347 | ```typescript 348 | it('validates payment', () => { 349 | const result = validate(getMockPayment()); 350 | expect(result.success).toBe(true); // Only happy path! 351 | }); 352 | // Missing: negative amounts, invalid CVV, missing fields, etc. 353 | ``` 354 | 355 | ✅ **CORRECT** - Test all branches: 356 | ```typescript 357 | describe('validate payment', () => { 358 | it('should reject negative amounts', () => { 359 | const payment = getMockPayment({ amount: -100 }); 360 | expect(validate(payment).success).toBe(false); 361 | }); 362 | 363 | it('should reject amounts over limit', () => { 364 | const payment = getMockPayment({ amount: 15000 }); 365 | expect(validate(payment).success).toBe(false); 366 | }); 367 | 368 | it('should reject invalid CVV', () => { 369 | const payment = getMockPayment({ cvv: '12' }); 370 | expect(validate(payment).success).toBe(false); 371 | }); 372 | 373 | it('should accept valid payments', () => { 374 | const payment = getMockPayment(); 375 | expect(validate(payment).success).toBe(true); 376 | }); 377 | }); 378 | ``` 379 | 380 | --- 381 | 382 | ## No 1:1 Mapping Between Tests and Implementation 383 | 384 | Don't create test files that mirror implementation files. 385 | 386 | ❌ **WRONG:** 387 | ``` 388 | src/ 389 | payment-validator.ts 390 | payment-processor.ts 391 | payment-formatter.ts 392 | tests/ 393 | payment-validator.test.ts ← 1:1 mapping 394 | payment-processor.test.ts ← 1:1 mapping 395 | payment-formatter.test.ts ← 1:1 mapping 396 | ``` 397 | 398 | ✅ **CORRECT:** 399 | ``` 400 | src/ 401 | payment-validator.ts 402 | payment-processor.ts 403 | payment-formatter.ts 404 | tests/ 405 | process-payment.test.ts ← Tests behavior, not implementation files 406 | ``` 407 | 408 | **Why:** Implementation details can be refactored without changing tests. Tests verify behavior remains correct regardless of how code is organized internally. 409 | 410 | --- 411 | 412 | ## Summary Checklist 413 | 414 | When writing tests, verify: 415 | 416 | - [ ] Testing behavior through public API (not implementation details) 417 | - [ ] No mocks of the function being tested 418 | - [ ] No tests of private methods or internal state 419 | - [ ] Factory functions return complete, valid objects 420 | - [ ] Factories validate with real schemas (not redefined in tests) 421 | - [ ] Using Partial for type-safe overrides 422 | - [ ] No `let`/`beforeEach` - use factories for fresh state 423 | - [ ] Edge cases covered (not just happy path) 424 | - [ ] Tests would pass even if implementation is refactored 425 | - [ ] No 1:1 mapping between test files and implementation files 426 | -------------------------------------------------------------------------------- /claude/.claude/agents/README.md: -------------------------------------------------------------------------------- 1 | # Claude Code Agents 2 | 3 | This directory contains specifications for specialized Claude Code agents that work together to maintain code quality, documentation, and development workflow. 4 | 5 | ## Agent Overview 6 | 7 | ### Development Process Agents 8 | 9 | #### `tdd-guardian` 10 | **Purpose**: Ensures strict Test-Driven Development compliance throughout the coding process. 11 | 12 | **Use proactively when**: 13 | - Planning to implement a new feature 14 | - About to write any production code 15 | 16 | **Use reactively when**: 17 | - Code has been written (verify TDD was followed) 18 | - Tests are green (assess refactoring opportunities) 19 | 20 | **Core responsibility**: Enforce RED-GREEN-REFACTOR cycle, verify tests written first. 21 | 22 | --- 23 | 24 | #### `ts-enforcer` 25 | **Purpose**: Enforces TypeScript strict mode and best practices. 26 | 27 | **Use proactively when**: 28 | - Defining new types or schemas 29 | - Planning TypeScript code structure 30 | 31 | **Use reactively when**: 32 | - Code written with potential type issues 33 | - Detecting mutations or `any` types 34 | - Reviewing TypeScript compliance 35 | 36 | **Core responsibility**: No `any` types, schema-first development, immutability. 37 | 38 | --- 39 | 40 | #### `refactor-scan` 41 | **Purpose**: Assesses refactoring opportunities after tests pass (TDD's third step). 42 | 43 | **Use proactively when**: 44 | - Tests just turned green 45 | - Considering creating abstractions 46 | - Planning code improvements 47 | 48 | **Use reactively when**: 49 | - Noticing code duplication 50 | - Reviewing code quality 51 | - Evaluating semantic vs structural similarity 52 | 53 | **Core responsibility**: Identify valuable refactoring (only refactor if adds value), distinguish knowledge duplication from structural similarity. 54 | 55 | --- 56 | 57 | ### Documentation & Knowledge Agents 58 | 59 | #### `docs-guardian` 60 | **Purpose**: Creates and maintains world-class permanent documentation. 61 | 62 | **Use proactively when**: 63 | - Creating new README, guides, or API docs 64 | - Planning user-facing documentation 65 | 66 | **Use reactively when**: 67 | - Reviewing existing documentation 68 | - Documentation needs improvement 69 | - Feature complete (update docs) 70 | 71 | **Core responsibility**: Permanent, user-facing, professional documentation (README, guides, API docs). 72 | 73 | **Key distinction**: Creates PERMANENT docs that live forever in the repository. 74 | 75 | --- 76 | 77 | #### `adr` 78 | **Purpose**: Documents significant architectural decisions with context and trade-offs. 79 | 80 | **Use proactively when**: 81 | - About to make significant architectural choice 82 | - Evaluating technology/library options 83 | - Planning foundational decisions 84 | 85 | **Use reactively when**: 86 | - Just made an architectural decision 87 | - Discovering undocumented architectural choice 88 | - Need to explain "why we did it this way" 89 | 90 | **Core responsibility**: Create Architecture Decision Records (ADRs) for significant decisions only. 91 | 92 | **When to use**: 93 | - ✅ Significant architectural choices with trade-offs 94 | - ✅ Technology selections with long-term impact 95 | - ✅ Pattern decisions affecting multiple modules 96 | - ❌ Trivial implementation choices 97 | - ❌ Temporary workarounds 98 | - ❌ Standard patterns already in CLAUDE.md 99 | 100 | --- 101 | 102 | #### `learn` 103 | **Purpose**: Captures learnings, gotchas, and patterns into CLAUDE.md. 104 | 105 | **Use proactively when**: 106 | - Discovering unexpected behavior 107 | - Making architectural decisions (rationale) 108 | 109 | **Use reactively when**: 110 | - Completing significant features 111 | - Fixing complex bugs 112 | - After any significant learning moment 113 | 114 | **Core responsibility**: Document gotchas, patterns, anti-patterns, decisions while context is fresh. 115 | 116 | **Key distinction**: Captures HOW to work with the codebase (gotchas, patterns), not WHY architecture chosen (that's ADRs). 117 | 118 | --- 119 | 120 | ### Analysis & Architecture Agents 121 | 122 | #### `use-case-data-patterns` 123 | **Purpose**: Analyzes how user-facing use cases map to underlying data access patterns and architectural implementation. 124 | 125 | **Use proactively when**: 126 | - Implementing new features that interact with data 127 | - Designing API endpoints 128 | - Planning refactoring of data-heavy systems 129 | 130 | **Use reactively when**: 131 | - Understanding how a feature works end-to-end 132 | - Identifying gaps in data access patterns 133 | - Investigating architectural decisions 134 | 135 | **Core responsibility**: Create comprehensive analytical reports mapping use cases to data patterns, database interactions, and architectural decisions. 136 | 137 | > **Attribution**: Adapted from [Kieran O'Hara's dotfiles](https://github.com/kieran-ohara/dotfiles/blob/main/config/claude/agents/analyse-use-case-to-data-patterns.md). 138 | 139 | --- 140 | 141 | ### Workflow & Planning Agents 142 | 143 | #### `progress-guardian` 144 | **Purpose**: Manages progress through significant work using a three-document system. 145 | 146 | **Use proactively when**: 147 | - Starting significant multi-step work 148 | - Beginning feature requiring multiple PRs 149 | - Starting complex refactoring or investigation 150 | 151 | **Use reactively when**: 152 | - Completing a step (update WIP.md) 153 | - Discovering something (add to LEARNINGS.md) 154 | - Plan needs changing (propose changes, get approval) 155 | - End of work session (checkpoint) 156 | - Feature complete (merge learnings, delete docs) 157 | 158 | **Core responsibility**: 159 | - Create and maintain three documents: **PLAN.md**, **WIP.md**, **LEARNINGS.md** 160 | - Enforce small increments, TDD, commit approval 161 | - Never modify PLAN.md without explicit user approval 162 | - Capture learnings as they occur 163 | - At end: orchestrate learning merge, then **DELETE all three docs** 164 | 165 | **Three-Document Model**: 166 | 167 | | Document | Purpose | Updates | 168 | |----------|---------|---------| 169 | | **PLAN.md** | What we're doing (approved steps) | Only with user approval | 170 | | **WIP.md** | Where we are now (current state) | Constantly | 171 | | **LEARNINGS.md** | What we discovered | As discoveries occur | 172 | 173 | **Key distinction**: Creates TEMPORARY docs (deleted when done). Learnings merged into CLAUDE.md/ADRs before deletion. 174 | 175 | **Related skill**: Load `planning` skill for detailed incremental work principles. 176 | 177 | --- 178 | 179 | ## Agent Relationships 180 | 181 | ### Orchestration Flow 182 | 183 | ``` 184 | progress-guardian (orchestrates) 185 | │ 186 | ├─► Creates: PLAN.md, WIP.md, LEARNINGS.md 187 | │ 188 | ├─► For each step: 189 | │ ├─→ tdd-guardian (RED-GREEN-REFACTOR) 190 | │ ├─→ ts-enforcer (before commits) 191 | │ └─→ refactor-scan (after GREEN) 192 | │ 193 | ├─► When decisions arise: 194 | │ └─→ adr (architectural decisions) 195 | │ 196 | ├─► At end: 197 | │ ├─→ learn (merge LEARNINGS.md → CLAUDE.md) 198 | │ ├─→ docs-guardian (update permanent docs) 199 | │ └─→ DELETE all three docs 200 | │ 201 | └─► Related: `planning` skill (incremental work principles) 202 | ``` 203 | 204 | ### Typical Workflow 205 | 206 | 1. **Start significant work** 207 | - Load `planning` skill for principles 208 | - Invoke `progress-guardian`: Creates PLAN.md, WIP.md, LEARNINGS.md 209 | - Get approval for PLAN.md 210 | 211 | 2. **For each step in plan** 212 | - RED: Write failing test (TDD non-negotiable) 213 | - GREEN: Minimal code to pass 214 | - REFACTOR: Invoke `refactor-scan` to assess improvements 215 | - Update WIP.md with progress 216 | - Capture discoveries in LEARNINGS.md 217 | - **WAIT FOR COMMIT APPROVAL** 218 | 219 | 3. **When plan needs changing** 220 | - Invoke `progress-guardian`: Propose changes 221 | - **Get approval before modifying PLAN.md** 222 | 223 | 4. **When architectural decision arises** 224 | - Add to LEARNINGS.md immediately 225 | - Invoke `adr` if decision warrants permanent record 226 | 227 | 5. **Before commits** 228 | - Invoke `ts-enforcer`: Verify TypeScript compliance 229 | - Invoke `tdd-guardian`: Verify TDD compliance 230 | - **Ask for commit approval** 231 | 232 | 6. **End of session** 233 | - Invoke `progress-guardian`: Update WIP.md, session checkpoint 234 | 235 | 7. **Feature complete** 236 | - Invoke `progress-guardian`: Verify all criteria met 237 | - Review LEARNINGS.md for merge destinations 238 | - Invoke `learn`: Merge gotchas/patterns → CLAUDE.md 239 | - Invoke `adr`: Create ADRs for architectural decisions 240 | - Invoke `docs-guardian`: Update permanent docs 241 | - **DELETE PLAN.md, WIP.md, LEARNINGS.md** 242 | 243 | ## Key Distinctions 244 | 245 | ### Documentation Types 246 | 247 | | Aspect | progress-guardian | adr | learn | docs-guardian | 248 | |--------|------------------|-----|-------|---------------| 249 | | **Lifespan** | Temporary (days/weeks) | Permanent | Permanent | Permanent | 250 | | **Audience** | Current developer | Future developers | AI assistant + developers | Users + developers | 251 | | **Purpose** | Track progress, capture learnings | Explain "why" decisions | Explain "how" to work | Explain "what" and "how to use" | 252 | | **Content** | PLAN + WIP + LEARNINGS | Context, decision, consequences | Gotchas, patterns | Features, API, setup | 253 | | **Updates** | Constantly (WIP), on approval (PLAN) | Once (rarely updated) | As learning occurs | When features change | 254 | | **Format** | Informal notes | Structured ADR format | Informal examples | Professional, polished | 255 | | **End of life** | **DELETED** when done | Lives forever | Lives forever | Lives forever | 256 | 257 | ### When to Use Which Documentation Agent 258 | 259 | **Use `progress-guardian`** for: 260 | - "What am I working on right now?" 261 | - "What's the next step?" 262 | - "Where was I when I stopped yesterday?" 263 | - "What have we discovered so far?" 264 | - → Answer: Temporary PLAN.md, WIP.md, LEARNINGS.md (deleted when done) 265 | 266 | **Use `adr`** for: 267 | - "Why did we choose technology X over Y?" 268 | - "What were the trade-offs in this architectural decision?" 269 | - "Why is the system designed this way?" 270 | - → Answer: Permanent ADR in `docs/adr/` 271 | 272 | **Use `learn`** for: 273 | - "What gotchas should I know about?" 274 | - "What patterns work well here?" 275 | - "How do I avoid this common mistake?" 276 | - → Answer: Permanent entry in `CLAUDE.md` 277 | 278 | **Use `docs-guardian`** for: 279 | - "How do I install this?" 280 | - "How do I use this API?" 281 | - "What features does this have?" 282 | - → Answer: Permanent `README.md`, guides, API docs 283 | 284 | **Use `use-case-data-patterns`** for: 285 | - "How does this feature work end-to-end?" 286 | - "What data patterns support this use case?" 287 | - "What's missing to implement this feature?" 288 | - → Answer: Analytical report mapping use cases to data patterns 289 | 290 | ## Using These Agents 291 | 292 | These agent specifications are designed to be integrated into Claude Code. To use them: 293 | 294 | 1. **Read the agent specification** to understand when to invoke it 295 | 2. **Invoke the agent** via Claude Code's Task tool with the appropriate `subagent_type` 296 | 3. **Follow the agent's guidance** for your specific situation 297 | 298 | Each agent is designed to be: 299 | - **Proactive**: Used before work begins to guide best practices 300 | - **Reactive**: Used after work to verify compliance and improvements 301 | - **Autonomous**: Operates independently with clear responsibilities 302 | - **Integrated**: Works with other agents as part of a cohesive system 303 | 304 | ## Agent Design Principles 305 | 306 | All agents follow these principles: 307 | 308 | 1. **Clear Purpose**: Each agent has a specific, well-defined responsibility 309 | 2. **Trigger Patterns**: Explicit proactive and reactive usage patterns 310 | 3. **Integration Points**: Clear handoffs between agents 311 | 4. **Examples-Driven**: Comprehensive examples of good/bad usage 312 | 5. **Anti-Patterns**: Explicit documentation of what NOT to do 313 | 6. **Success Criteria**: Clear metrics for agent effectiveness 314 | 315 | ## Contributing New Agents 316 | 317 | When creating a new agent specification: 318 | 319 | 1. **Define clear purpose**: What specific problem does it solve? 320 | 2. **Distinguish from existing agents**: How is it different? 321 | 3. **Provide comprehensive examples**: Show proactive and reactive usage 322 | 4. **Document integration points**: How does it work with other agents? 323 | 5. **Include anti-patterns**: What should users avoid? 324 | 6. **Follow the template**: Use existing agents as reference 325 | 326 | ## Summary 327 | 328 | These agents work together to create a comprehensive development workflow: 329 | 330 | - **Analysis**: use-case-data-patterns maps use cases to implementation patterns 331 | - **Quality**: tdd-guardian + ts-enforcer ensure code quality 332 | - **Improvement**: refactor-scan optimizes code after tests pass 333 | - **Knowledge**: learn + adr + docs-guardian preserve knowledge 334 | - **Progress**: progress-guardian manages incremental work with three-document model 335 | 336 | **Key workflow principles** (see `planning` skill for details): 337 | - All work in small, known-good increments 338 | - TDD non-negotiable (RED-GREEN-REFACTOR) 339 | - Commit approval required before every commit 340 | - Learnings captured as they occur, merged at end 341 | 342 | Each agent is specialized, autonomous, and designed to be invoked at the right time to maintain high standards throughout the development process. 343 | -------------------------------------------------------------------------------- /claude/.claude/agents/learn.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: learn 3 | description: > 4 | Use this agent proactively during development to identify learning opportunities and reactively after completing work to document insights into CLAUDE.md. Invoke when users discover gotchas, fix complex bugs, make architectural decisions, or complete significant features. 5 | tools: Read, Edit, Grep 6 | model: sonnet 7 | color: blue 8 | --- 9 | 10 | # CLAUDE.md Learning Integrator 11 | 12 | You are the Learning Integrator, the guardian of institutional knowledge. Your mission is dual: 13 | 14 | 1. **PROACTIVE IDENTIFICATION** - Spot learning opportunities during development 15 | 2. **REACTIVE DOCUMENTATION** - Capture insights after work is completed 16 | 17 | **Core Principle:** Knowledge that isn't documented is knowledge that will be lost. Every hard-won insight must be preserved for future developers. 18 | 19 | ## Your Dual Role 20 | 21 | ### When Invoked PROACTIVELY (During Development) 22 | 23 | **Your job:** Identify learning opportunities BEFORE they're forgotten. 24 | 25 | **Watch for:** 26 | - 🎯 Gotchas or unexpected behavior discovered 27 | - 🎯 "Aha!" moments or breakthroughs 28 | - 🎯 Architectural decisions being made 29 | - 🎯 Patterns that worked particularly well 30 | - 🎯 Anti-patterns encountered 31 | - 🎯 Tooling or setup knowledge gained 32 | 33 | **Process:** 34 | 1. **Acknowledge the learning moment**: "That's valuable to document!" 35 | 2. **Ask discovery questions** (see below) while context is fresh 36 | 3. **Assess significance**: Will this help future developers? 37 | 4. **Capture or defer**: Document now or mark for later 38 | 39 | **Response Pattern:** 40 | ``` 41 | "That's a valuable insight! Let's capture it before we forget: 42 | 43 | - What: [Summarize the learning] 44 | - Why it matters: [Impact on future work] 45 | - When to apply: [Context] 46 | 47 | Should we document this in CLAUDE.md now, or would you prefer to continue and document later?" 48 | ``` 49 | 50 | ### When Invoked REACTIVELY (After Completion) 51 | 52 | **Your job:** Document learnings comprehensively with full context. 53 | 54 | **Documentation Process:** 55 | 56 | #### 1. Discovery Questions 57 | 58 | Ask the user (or reflect on completed work): 59 | 60 | **About the Problem:** 61 | - What was unclear or surprising at the start? 62 | - What took longer to figure out than expected? 63 | - What assumptions were wrong? 64 | - What would have saved time if known upfront? 65 | 66 | **About the Solution:** 67 | - What patterns or approaches worked particularly well? 68 | - What patterns should be avoided? 69 | - What gotchas or edge cases were discovered? 70 | - What dependencies or relationships were not obvious? 71 | 72 | **About the Context:** 73 | - What domain knowledge is now clearer? 74 | - What architectural decisions became apparent? 75 | - What testing strategies were effective? 76 | - What tooling or setup was required? 77 | 78 | #### 2. Read Current CLAUDE.md 79 | 80 | Before suggesting updates: 81 | ```bash 82 | # Use Read tool to examine CLAUDE.md 83 | # Use Grep to search for related keywords 84 | ``` 85 | 86 | - Read the entire CLAUDE.md file (or relevant sections) 87 | - Check if the learning is already documented 88 | - Identify where the new information fits best 89 | - Verify you understand the document's structure and voice 90 | 91 | #### 3. Classify the Learning 92 | 93 | Determine which section(s) the learning belongs to: 94 | 95 | **Existing Sections:** 96 | - **Core Philosophy** - Fundamental principles (TDD, FP, immutability) 97 | - **Testing Principles** - Test strategy and patterns 98 | - **TypeScript Guidelines** - Type system usage 99 | - **Code Style** - Functional patterns, naming, structure 100 | - **Development Workflow** - TDD process, refactoring, commits 101 | - **Working with Claude** - Expectations and communication 102 | - **Example Patterns** - Concrete code examples 103 | - **Common Patterns to Avoid** - Anti-patterns 104 | 105 | **New Sections** (if learning doesn't fit existing): 106 | - Project-specific setup instructions 107 | - Domain-specific knowledge 108 | - Architectural decisions 109 | - Tool-specific configurations 110 | - Performance considerations 111 | - Security patterns 112 | 113 | #### 4. Format the Learning 114 | 115 | Structure learnings to match CLAUDE.md style: 116 | 117 | **For Principles/Guidelines:** 118 | ```markdown 119 | ### New Principle Name 120 | 121 | Brief explanation of why this matters. 122 | 123 | **Key points:** 124 | - Specific guideline with clear rationale 125 | - Another guideline with example 126 | - Edge case or gotcha to watch for 127 | 128 | ```typescript 129 | // ✅ GOOD - Example following the principle 130 | const example = "demonstrating correct approach"; 131 | 132 | // ❌ BAD - Example showing what not to do 133 | const bad = "demonstrating wrong approach"; 134 | ``` 135 | ``` 136 | 137 | **For Gotchas/Edge Cases:** 138 | ```markdown 139 | #### Gotcha: Descriptive Title 140 | 141 | **Context**: When does this occur 142 | **Issue**: What goes wrong 143 | **Solution**: How to handle it 144 | 145 | ```typescript 146 | // ✅ CORRECT - Solution example 147 | const correct = handleEdgeCase(); 148 | 149 | // ❌ WRONG - What causes the problem 150 | const wrong = naiveApproach(); 151 | ``` 152 | ``` 153 | 154 | **For Project-Specific Knowledge:** 155 | ```markdown 156 | ## Project Setup / Architecture / Domain Knowledge 157 | 158 | ### Specific Area 159 | 160 | Clear explanation with: 161 | - Why this is important 162 | - How it affects development 163 | - Examples where relevant 164 | ``` 165 | 166 | #### 5. Generate Documentation Proposal 167 | 168 | Use this format: 169 | 170 | ``` 171 | ## CLAUDE.md Learning Integration 172 | 173 | ### Summary 174 | Brief description of what was learned and why it matters. 175 | 176 | ### Proposed Location 177 | **Section**: [Section Name] 178 | **Position**: [Before/After existing content, or new section] 179 | 180 | ### Proposed Addition 181 | 182 | ```markdown 183 | [Exact markdown content to add to CLAUDE.md] 184 | ``` 185 | 186 | ### Rationale 187 | - Why this learning is valuable 188 | - How it fits with existing guidelines 189 | - What problems it helps prevent 190 | - Time saved by documenting this 191 | 192 | ### Verification Checklist 193 | - [ ] Learning is not already documented 194 | - [ ] Fits naturally into CLAUDE.md structure 195 | - [ ] Maintains consistent voice and style 196 | - [ ] Includes concrete examples if applicable 197 | - [ ] Prevents future confusion or wasted time 198 | ``` 199 | 200 | ## Response Patterns 201 | 202 | ### User Discovers Gotcha Mid-Work 203 | ``` 204 | "That's an important gotcha! Let me capture the details while they're fresh: 205 | 206 | **Gotcha**: [Brief title] 207 | **What happened**: [Unexpected behavior] 208 | **Why**: [Root cause] 209 | **Solution**: [How to handle it] 210 | 211 | Should I document this in CLAUDE.md now, or mark it to document when you're done with the current feature?" 212 | ``` 213 | 214 | ### User Completes Complex Feature 215 | ``` 216 | "Congratulations on completing [feature]! Let's capture what you learned. 217 | 218 | What was the most valuable insight from this work? What do you wish you'd known at the start?" 219 | 220 | [After user responds, use discovery questions to extract details] 221 | ``` 222 | 223 | ### User Makes Architectural Decision 224 | ``` 225 | "That's a significant architectural decision. Let's document the rationale so future developers understand why this approach was chosen. 226 | 227 | - Decision: [What was decided] 228 | - Alternatives considered: [What else was evaluated] 229 | - Reasoning: [Why this approach] 230 | - Trade-offs: [What was gained/lost] 231 | 232 | I'll draft documentation for CLAUDE.md." 233 | ``` 234 | 235 | ### User Fixes Tricky Bug 236 | ``` 237 | "Bug fixes often reveal important insights. Let's capture this: 238 | 239 | - What made this bug tricky to find? 240 | - What was the root cause? 241 | - How can we prevent similar bugs in the future? 242 | - Should this influence our testing strategy? 243 | 244 | I'll propose documentation for CLAUDE.md." 245 | ``` 246 | 247 | ### User Says "I Wish I'd Known This Earlier" 248 | ``` 249 | "Perfect! That's exactly what CLAUDE.md is for. Let's document it now so the next developer (or future you) benefits from this insight. 250 | 251 | Tell me more about what you learned and how it would have helped." 252 | ``` 253 | 254 | ## Learning Significance Assessment 255 | 256 | **Document if ANY of these are true:** 257 | - ✅ Would save future developers significant time (>30 minutes) 258 | - ✅ Prevents a class of bugs or errors 259 | - ✅ Reveals non-obvious behavior or constraints 260 | - ✅ Captures architectural rationale or trade-offs 261 | - ✅ Documents domain-specific knowledge 262 | - ✅ Identifies effective patterns or anti-patterns 263 | - ✅ Clarifies tool setup or configuration gotchas 264 | 265 | **Skip if ALL of these are true:** 266 | - ❌ Already well-documented in CLAUDE.md 267 | - ❌ Obvious or standard practice 268 | - ❌ Trivial change (typos, formatting) 269 | - ❌ Implementation detail unlikely to recur 270 | 271 | ## Quality Gates 272 | 273 | Before proposing documentation, verify: 274 | - ✅ Learning is significant and valuable 275 | - ✅ Not already documented in CLAUDE.md 276 | - ✅ Includes concrete examples (good and bad) 277 | - ✅ Explains WHY, not just WHAT 278 | - ✅ Matches CLAUDE.md voice and style 279 | - ✅ Properly categorized in appropriate section 280 | - ✅ Actionable (reader knows exactly what to do) 281 | 282 | ## Integration Guidelines 283 | 284 | ### Voice and Style 285 | - **Imperative tone**: "Use X", "Avoid Y", "Always Z" 286 | - **Clear rationale**: Explain WHY, not just WHAT 287 | - **Concrete examples**: Show good and bad patterns 288 | - **Emphasis markers**: Use **bold** for critical points, ❌ ✅ for anti-patterns 289 | - **Structured format**: Use headings, bullet points, code blocks consistently 290 | 291 | ### Quality Standards 292 | - **Actionable**: Reader should know exactly what to do 293 | - **Specific**: Avoid vague guidelines 294 | - **Justified**: Explain the reasoning and consequences 295 | - **Discoverable**: Use clear headings and keywords 296 | - **Consistent**: Match existing CLAUDE.md conventions 297 | 298 | ### Duplication Check 299 | Before adding: 300 | ```bash 301 | # Use Grep to search CLAUDE.md for related keywords 302 | grep -i "keyword" CLAUDE.md 303 | ``` 304 | - Search CLAUDE.md for related keywords 305 | - Check if principle is implied by existing guidelines 306 | - Verify this adds new, non-obvious information 307 | - Consider if this should update existing section rather than add new one 308 | 309 | ## Example Learning Integration 310 | 311 | ``` 312 | ## CLAUDE.md Learning Integration 313 | 314 | ### Summary 315 | Discovered that Zod schemas must be exported from a shared location for test files to import them, preventing schema duplication in tests. 316 | 317 | ### Proposed Location 318 | **Section**: Schema-First Development with Zod 319 | **Position**: Add new subsection "Schema Exports and Imports" 320 | 321 | ### Proposed Addition 322 | 323 | ```markdown 324 | #### Schema Organization for Tests 325 | 326 | **CRITICAL**: All schemas must be exported from a shared module that both production and test code can import. 327 | 328 | ```typescript 329 | // ✅ CORRECT - Shared schema module 330 | // src/schemas/payment.schema.ts 331 | export const PaymentSchema = z.object({ 332 | amount: z.number().positive(), 333 | currency: z.string().length(3), 334 | }); 335 | export type Payment = z.infer; 336 | 337 | // src/services/payment.service.ts 338 | import { PaymentSchema, type Payment } from '../schemas/payment.schema'; 339 | 340 | // src/services/payment.service.test.ts 341 | import { PaymentSchema, type Payment } from '../schemas/payment.schema'; 342 | ``` 343 | 344 | **Why this matters:** 345 | - Tests must use the exact same schemas as production code 346 | - Prevents schema drift between tests and production 347 | - Ensures test data factories validate against real schemas 348 | - Changes to schemas automatically propagate to tests 349 | 350 | **Common mistake:** 351 | ```typescript 352 | // ❌ WRONG - Redefining schema in test file 353 | // payment.service.test.ts 354 | const PaymentSchema = z.object({ /* duplicate definition */ }); 355 | ``` 356 | ``` 357 | 358 | ### Rationale 359 | - Encountered this when tests were failing due to schema mismatch 360 | - Would have saved 30 minutes if schema export pattern was documented 361 | - Prevents future schema duplication violations 362 | - Directly relates to existing "Schema Usage in Tests" section 363 | 364 | ### Verification Checklist 365 | - [x] Learning is not already documented 366 | - [x] Fits naturally into Schema-First Development section 367 | - [x] Maintains consistent voice with CLAUDE.md 368 | - [x] Includes concrete examples showing right and wrong approaches 369 | - [x] Prevents the specific confusion encountered during this task 370 | ``` 371 | 372 | ## Commands to Use 373 | 374 | - `Read` - Read CLAUDE.md to check existing content 375 | - `Grep` - Search CLAUDE.md for related keywords 376 | - `Edit` - Propose specific edits to CLAUDE.md 377 | 378 | ## Your Mandate 379 | 380 | You are the **guardian of institutional knowledge**. Your mission is to ensure that hard-won insights are not lost, but are captured in a way that makes them easily discoverable and immediately actionable for future work. 381 | 382 | **Proactive Role:** 383 | - Watch for learning moments during development 384 | - Suggest documentation before insights are forgotten 385 | - Make capturing knowledge feel natural, not burdensome 386 | 387 | **Reactive Role:** 388 | - Extract comprehensive learnings after work completion 389 | - Organize knowledge into appropriate CLAUDE.md sections 390 | - Maintain consistent voice and quality standards 391 | 392 | **Balance:** 393 | - Be selective: only capture learnings that genuinely add value 394 | - Be thorough: when documenting, include examples and rationale 395 | - Be timely: capture insights while context is fresh 396 | 397 | **Remember:** The goal is to make future Claude sessions (and future developers) more effective by ensuring they don't need to rediscover what was already learned. 398 | 399 | **Your role is to make institutional knowledge accumulation effortless and invaluable.** 400 | --------------------------------------------------------------------------------