From 2b91dc4da957f82dd1431fe88c5ddfd2d88e340a Mon Sep 17 00:00:00 2001 From: hidi Date: Mon, 27 Jul 2026 11:20:13 +0900 Subject: [PATCH] chore(skills): benchmark-driven cleanup of repo-local skills & commands Ran a controlled benchmark (same task per overlapping skill, scored against answer keys, outputs verified with `cargo test --offline`). Applied the verdict: - Remove rtk-tdd: 11/12 in the TDD benchmark (used synthetic fixtures, the anti-pattern cli-testing.md warns against) vs tdd-rust's 12/12 at lower cost. TDD is now consolidated on tdd-rust. - Rename ship -> rtk-release: the repo `ship` skill was shadowed by gstack's `ship` in the skill list and was unreachable. Renamed the dir, added an explicit `name:`, and updated the `/ship` invocation hint to `/rtk-release`. - Consolidate worktree commands: they existed twice (root and tech/) with diverged content. Kept the richer root versions (they document --check/--fast, .worktreeinclude, manual force-remove), deleted the tech/ duplicates, and moved remove-worktree into root so the whole worktree family lives in one place. tech/ now holds only its unique commands (audit-codebase, codereview). Co-Authored-By: Claude Opus 4.8 --- .../commands/{tech => }/remove-worktree.md | 8 +- .claude/commands/tech/clean-worktree.md | 87 -------- .claude/commands/tech/clean-worktrees.md | 162 --------------- .claude/commands/tech/worktree-status.md | 116 ----------- .claude/commands/tech/worktree.md | 188 ------------------ .claude/skills/{ship => rtk-release}/SKILL.md | 9 +- .claude/skills/rtk-tdd/SKILL.md | 85 -------- .../rtk-tdd/references/testing-patterns.md | 124 ------------ 8 files changed, 10 insertions(+), 769 deletions(-) rename .claude/commands/{tech => }/remove-worktree.md (95%) delete mode 100644 .claude/commands/tech/clean-worktree.md delete mode 100644 .claude/commands/tech/clean-worktrees.md delete mode 100644 .claude/commands/tech/worktree-status.md delete mode 100644 .claude/commands/tech/worktree.md rename .claude/skills/{ship => rtk-release}/SKILL.md (95%) delete mode 100644 .claude/skills/rtk-tdd/SKILL.md delete mode 100644 .claude/skills/rtk-tdd/references/testing-patterns.md diff --git a/.claude/commands/tech/remove-worktree.md b/.claude/commands/remove-worktree.md similarity index 95% rename from .claude/commands/tech/remove-worktree.md rename to .claude/commands/remove-worktree.md index 90a63632dd..9a0588ee0e 100644 --- a/.claude/commands/tech/remove-worktree.md +++ b/.claude/commands/remove-worktree.md @@ -11,8 +11,8 @@ Remove a specific worktree, cleaning up directory, git references, and optionall ## Usage ```bash -/tech:remove-worktree feature/new-filter -/tech:remove-worktree fix/session-bug +/remove-worktree feature/new-filter +/remove-worktree fix/session-bug ``` ## Implementation @@ -26,10 +26,10 @@ set -euo pipefail BRANCH_NAME="$ARGUMENTS" if [ -z "$BRANCH_NAME" ]; then - echo "โŒ Usage: /tech:remove-worktree " + echo "โŒ Usage: /remove-worktree " echo "" echo "Example:" - echo " /tech:remove-worktree feature/new-filter" + echo " /remove-worktree feature/new-filter" exit 1 fi diff --git a/.claude/commands/tech/clean-worktree.md b/.claude/commands/tech/clean-worktree.md deleted file mode 100644 index 32b01afba2..0000000000 --- a/.claude/commands/tech/clean-worktree.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -model: haiku -description: Clean stale worktrees (interactive) ---- - -# Clean Worktree (Interactive) - -Audit and clean obsolete worktrees interactively: merged, pruned, orphaned branches. - -**vs `/tech:clean-worktrees`**: -- `/tech:clean-worktree`: Interactive, asks confirmation before deletion -- `/tech:clean-worktrees`: Automatic, no interaction (merged branches only) - -## Usage - -```bash -/tech:clean-worktree -``` - -## Implementation - -```bash -#!/bin/bash - -echo "=== Worktrees Status ===" -git worktree list -echo "" - -echo "=== Pruning stale references ===" -git worktree prune -echo "" - -echo "=== Merged branches (safe to delete) ===" -while IFS= read -r line; do - path=$(echo "$line" | awk '{print $1}') - branch=$(echo "$line" | grep -oE '\[.*\]' | tr -d '[]') - [ -z "$branch" ] && continue - [ "$branch" = "master" ] && continue - [ "$branch" = "main" ] && continue - - if git branch --merged master | grep -q "^[* ] ${branch}$"; then - echo " - $branch (at $path) โ€” MERGED" - fi -done < <(git worktree list) -echo "" - -echo "=== Clean merged worktrees? [y/N] ===" -read -r confirm -if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then - while IFS= read -r line; do - path=$(echo "$line" | awk '{print $1}') - branch=$(echo "$line" | grep -oE '\[.*\]' | tr -d '[]') - [ -z "$branch" ] && continue - [ "$branch" = "master" ] && continue - [ "$branch" = "main" ] && continue - - if git branch --merged master | grep -q "^[* ] ${branch}$"; then - echo " Removing $branch..." - git worktree remove "$path" 2>/dev/null || rm -rf "$path" - git branch -d "$branch" 2>/dev/null || echo " (branch already deleted)" - fi - done < <(git worktree list) - echo "Done." -else - echo "Aborted." -fi - -echo "" -echo "=== Disk usage ===" -du -sh .worktrees/ 2>/dev/null || echo "No .worktrees directory" -``` - -## Safety - -- **Never** removes `master` or `main` worktrees -- **Only** removes merged branches (safe) -- **Asks confirmation** before deletion -- Cleans both worktree reference AND physical directory - -## Manual Override - -Force remove an unmerged worktree: - -```bash -git worktree remove --force -git branch -D -``` diff --git a/.claude/commands/tech/clean-worktrees.md b/.claude/commands/tech/clean-worktrees.md deleted file mode 100644 index dadcb993a8..0000000000 --- a/.claude/commands/tech/clean-worktrees.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -model: haiku -description: Auto-clean all stale worktrees (merged branches) ---- - -# Clean Worktrees (Automatic) - -Automatically clean all stale worktrees: merged branches and orphaned git references. - -**vs `/tech:clean-worktree`**: -- `/tech:clean-worktree`: Interactive, asks confirmation -- `/tech:clean-worktrees`: **Automatic**, no interaction (safe: merged only) - -## Usage - -```bash -/tech:clean-worktrees # Clean all merged worktrees -/tech:clean-worktrees --dry-run # Preview what would be deleted -``` - -## Implementation - -```bash -#!/bin/bash -set -euo pipefail - -DRY_RUN=false -if [[ "${ARGUMENTS:-}" == *"--dry-run"* ]]; then - DRY_RUN=true -fi - -echo "๐Ÿงน Cleaning Worktrees" -echo "=====================" -echo "" - -# Step 1: Prune stale git references -echo "1๏ธโƒฃ Pruning stale git references..." -PRUNED=$(git worktree prune -v 2>&1) -if [ -n "$PRUNED" ]; then - echo "$PRUNED" - echo "โœ… Stale references pruned" -else - echo "โœ… No stale references found" -fi -echo "" - -# Step 2: Find merged worktrees -echo "2๏ธโƒฃ Finding merged worktrees..." -MERGED_COUNT=0 -MERGED_BRANCHES=() - -while IFS= read -r line; do - path=$(echo "$line" | awk '{print $1}') - branch=$(echo "$line" | grep -oE '\[.*\]' | tr -d '[]' || true) - - [ -z "$branch" ] && continue - [ "$branch" = "master" ] && continue - [ "$branch" = "main" ] && continue - [ "$path" = "$(pwd)" ] && continue - - if git branch --merged master | grep -q "^[* ] ${branch}$" 2>/dev/null; then - MERGED_COUNT=$((MERGED_COUNT + 1)) - MERGED_BRANCHES+=("$branch|$path") - echo " โœ“ $branch (merged)" - fi -done < <(git worktree list) - -if [ $MERGED_COUNT -eq 0 ]; then - echo "โœ… No merged worktrees found" - echo "" - echo "๐Ÿ“Š Current worktrees:" - git worktree list - exit 0 -fi - -echo "" -echo "๐Ÿ“‹ Found $MERGED_COUNT merged worktree(s)" -echo "" - -if [ "$DRY_RUN" = true ]; then - echo "๐Ÿ” DRY RUN MODE - No changes will be made" - echo "" - echo "Would delete:" - for item in "${MERGED_BRANCHES[@]}"; do - branch=$(echo "$item" | cut -d'|' -f1) - path=$(echo "$item" | cut -d'|' -f2) - echo " - $branch" - echo " Path: $path" - done - echo "" - echo "Run without --dry-run to actually delete" - exit 0 -fi - -# Step 3: Remove merged worktrees -echo "3๏ธโƒฃ Removing merged worktrees..." -REMOVED_COUNT=0 -FAILED_COUNT=0 - -for item in "${MERGED_BRANCHES[@]}"; do - branch=$(echo "$item" | cut -d'|' -f1) - path=$(echo "$item" | cut -d'|' -f2) - - echo "" - echo "๐Ÿ—‘๏ธ Removing: $branch" - - if git worktree remove "$path" 2>/dev/null; then - echo " โœ… Worktree removed" - else - echo " โš ๏ธ Git remove failed, forcing..." - rm -rf "$path" 2>/dev/null || true - git worktree prune 2>/dev/null || true - echo " โœ… Worktree forcefully removed" - fi - - if git branch -d "$branch" 2>/dev/null; then - echo " โœ… Local branch deleted" - else - echo " โš ๏ธ Local branch already deleted" - fi - - if git ls-remote --heads origin "$branch" 2>/dev/null | grep -q "$branch"; then - echo " ๐ŸŒ Remote branch exists: $branch" - echo " (Skipping auto-delete - use /tech:remove-worktree for manual removal)" - fi - - REMOVED_COUNT=$((REMOVED_COUNT + 1)) -done - -echo "" -echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" -echo "โœ… Cleanup Complete!" -echo "" -echo "๐Ÿ“Š Summary:" -echo " - Removed: $REMOVED_COUNT worktree(s)" -if [ $FAILED_COUNT -gt 0 ]; then - echo " - Failed: $FAILED_COUNT worktree(s)" -fi -echo "" -echo "๐Ÿ“‚ Remaining worktrees:" -git worktree list -echo "" - -WORKTREES_SIZE=$(du -sh .worktrees/ 2>/dev/null | awk '{print $1}' || echo "N/A") -echo "๐Ÿ’พ Worktrees disk usage: $WORKTREES_SIZE" -``` - -## Safety Features - -- โœ… **Only merged branches**: Never touches unmerged work -- โœ… **Protected branches**: Skips `master` and `main` -- โœ… **Main repo**: Never removes current working directory -- โœ… **Remote branches**: Reports but doesn't auto-delete -- โœ… **Dry-run mode**: Preview before deletion - -## When to Use - -- After merging PRs into master -- Weekly maintenance -- Before creating new worktrees (keep things clean) - -For unmerged branches: use `/tech:remove-worktree ` (confirms deletion). diff --git a/.claude/commands/tech/worktree-status.md b/.claude/commands/tech/worktree-status.md deleted file mode 100644 index faa5ca49da..0000000000 --- a/.claude/commands/tech/worktree-status.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -model: haiku -description: Worktree Cargo Check Status -argument-hint: "" ---- - -# Worktree Status Check - -Check the status of background cargo check for a git worktree. - -## Usage - -```bash -/tech:worktree-status feature/new-filter -/tech:worktree-status fix/session-bug -``` - -## Implementation - -Execute this script with branch name from `$ARGUMENTS`: - -```bash -#!/bin/bash -set -euo pipefail - -BRANCH_NAME="$ARGUMENTS" -LOG_FILE="/tmp/worktree-cargo-check-${BRANCH_NAME//\//-}.log" - -if [ ! -f "$LOG_FILE" ]; then - echo "โŒ No cargo check found for branch: $BRANCH_NAME" - echo "" - echo "Possible reasons:" - echo "1. Worktree was created with --fast / --no-check flag" - echo "2. Branch name mismatch (use exact branch name)" - echo "3. Cargo check hasn't started yet (wait a few seconds)" - echo "" - echo "Available logs:" - ls -1 /tmp/worktree-cargo-check-*.log 2>/dev/null || echo " (none)" - exit 1 -fi - -LOG_CONTENT=$(head -n 1000 "$LOG_FILE") - -if echo "$LOG_CONTENT" | grep -q "โœ… Cargo check passed"; then - TIMESTAMP=$(echo "$LOG_CONTENT" | grep "Cargo check passed" | sed 's/.*at //') - echo "โœ… Cargo check passed" - echo " Completed at: $TIMESTAMP" - echo "" - echo "Worktree is ready for development!" - -elif echo "$LOG_CONTENT" | grep -q "โŒ Cargo check failed"; then - TIMESTAMP=$(echo "$LOG_CONTENT" | grep "Cargo check failed" | sed 's/.*at //') - echo "โŒ Cargo check failed" - echo " Completed at: $TIMESTAMP" - echo "" - ERROR_COUNT=$(grep -v "Cargo check" "$LOG_FILE" | grep -c "^error" || echo "0") - echo "Errors:" - echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" - grep "^error" "$LOG_FILE" | head -20 - echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" - echo "" - echo "Full log: cat $LOG_FILE" - echo "" - echo "โš ๏ธ You can still work on the worktree - fix errors as you go." - -elif echo "$LOG_CONTENT" | grep -q "โณ Cargo check started"; then - START_TIME=$(echo "$LOG_CONTENT" | grep "Cargo check started" | sed 's/.*at //') - CURRENT_TIME=$(date +%H:%M:%S) - echo "โณ Cargo check still running..." - echo " Started at: $START_TIME" - echo " Current time: $CURRENT_TIME" - echo "" - echo "Check again in a few seconds or view live progress:" - echo " tail -f $LOG_FILE" - -else - echo "โš ๏ธ Cargo check in unknown state" - echo "" - echo "Log content:" - cat "$LOG_FILE" -fi -``` - -## Output Examples - -### Success -``` -โœ… Cargo check passed - Completed at: 14:23:45 - -Worktree is ready for development! -``` - -### Failed -``` -โŒ Cargo check failed - Completed at: 14:24:12 - -Errors: -โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -error[E0308]: mismatched types - --> src/git.rs:45:12 -โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -Full log: cat /tmp/worktree-cargo-check-feature-new-filter.log -``` - -### Still Running -``` -โณ Cargo check still running... - Started at: 14:22:30 - Current time: 14:22:45 - -Check again in a few seconds or view live progress: - tail -f /tmp/worktree-cargo-check-feature-new-filter.log -``` diff --git a/.claude/commands/tech/worktree.md b/.claude/commands/tech/worktree.md deleted file mode 100644 index 69dfc04d8c..0000000000 --- a/.claude/commands/tech/worktree.md +++ /dev/null @@ -1,188 +0,0 @@ ---- -model: haiku -description: Git Worktree Setup for RTK -argument-hint: "" ---- - -# Git Worktree Setup - -Create isolated git worktrees with instant feedback and background Cargo check. - -**Performance**: ~1s setup + background cargo check - -## Usage - -```bash -/tech:worktree feature/new-filter # Creates worktree + background cargo check -/tech:worktree fix/typo --fast # Skip cargo check (instant) -/tech:worktree feature/perf --no-check # Skip cargo check -``` - -**Behavior**: Creates the worktree and displays the path. Navigate manually with `cd .worktrees/{branch-name}`. - -**โš ๏ธ Important - Claude Context**: If Claude Code is currently running, restart it in the new worktree: -```bash -/exit # Exit current Claude session -cd .worktrees/fix-bug-name # Navigate to worktree -claude # Start Claude in worktree context -``` - -Check cargo check status: `/tech:worktree-status feature/new-filter` - -## Branch Naming Convention - -**Always use Git branch naming with slashes:** - -- โœ… `feature/new-filter` โ†’ Branch: `feature/new-filter`, Directory: `.worktrees/feature-new-filter` -- โœ… `fix/bug-name` โ†’ Branch: `fix/bug-name`, Directory: `.worktrees/fix-bug-name` -- โŒ `feature-new-filter` โ†’ Wrong: Missing category prefix - -## Implementation - -Execute this **single bash script** with branch name from `$ARGUMENTS`: - -```bash -#!/bin/bash -set -euo pipefail - -trap 'kill $(jobs -p) 2>/dev/null || true' EXIT - -# Validate git repository - always use main repo root (not worktree root) -GIT_COMMON_DIR="$(git rev-parse --git-common-dir 2>/dev/null)" -if [ -z "$GIT_COMMON_DIR" ]; then - echo "โŒ Not in a git repository" - exit 1 -fi -REPO_ROOT="$(cd "$GIT_COMMON_DIR/.." && pwd)" - -# Parse flags -RAW_ARGS="$ARGUMENTS" -BRANCH_NAME="$RAW_ARGS" -SKIP_CHECK=false - -if [[ "$RAW_ARGS" == *"--fast"* ]]; then - SKIP_CHECK=true - BRANCH_NAME="${BRANCH_NAME// --fast/}" -fi -if [[ "$RAW_ARGS" == *"--no-check"* ]]; then - SKIP_CHECK=true - BRANCH_NAME="${BRANCH_NAME// --no-check/}" -fi - -# Validate branch name -if [[ "$BRANCH_NAME" =~ [[:space:]\$\`] ]]; then - echo "โŒ Invalid branch name (spaces or special characters not allowed)" - exit 1 -fi -if [[ "$BRANCH_NAME" =~ [~^:?*\\\[\]] ]]; then - echo "โŒ Invalid branch name (git forbidden characters: ~ ^ : ? * [ ])" - exit 1 -fi - -# Paths - sanitize slashes to avoid nested directories -WORKTREE_NAME="${BRANCH_NAME//\//-}" -WORKTREE_DIR="$REPO_ROOT/.worktrees/$WORKTREE_NAME" -LOG_FILE="/tmp/worktree-cargo-check-${WORKTREE_NAME}.log" - -# 1. Check .gitignore (fail-fast) -if ! grep -qE "^\.worktrees/?$" "$REPO_ROOT/.gitignore" 2>/dev/null; then - echo "โŒ .worktrees/ not in .gitignore" - echo "Run: echo '.worktrees/' >> .gitignore && git add .gitignore && git commit -m 'chore: ignore worktrees'" - exit 1 -fi - -# 2. Create worktree (fail-fast) -echo "Creating worktree for $BRANCH_NAME..." -mkdir -p "$REPO_ROOT/.worktrees" -if ! git worktree add "$WORKTREE_DIR" -b "$BRANCH_NAME" 2>/tmp/worktree-error.log; then - echo "โŒ Failed to create worktree" - cat /tmp/worktree-error.log - exit 1 -fi - -# 3. Background cargo check (unless --fast / --no-check) -if [ "$SKIP_CHECK" = false ] && [ -f "$WORKTREE_DIR/Cargo.toml" ]; then - ( - cd "$WORKTREE_DIR" - echo "โณ Cargo check started at $(date +%H:%M:%S)" > "$LOG_FILE" - if cargo check --all-targets >> "$LOG_FILE" 2>&1; then - echo "โœ… Cargo check passed at $(date +%H:%M:%S)" >> "$LOG_FILE" - else - echo "โŒ Cargo check failed at $(date +%H:%M:%S)" >> "$LOG_FILE" - fi - ) & - CHECK_RUNNING=true -else - CHECK_RUNNING=false -fi - -# 4. Report (instant feedback) -echo "" -echo "โœ… Worktree ready: $WORKTREE_DIR" - -if [ "$CHECK_RUNNING" = true ]; then - echo "โณ Cargo check running in background..." - echo "๐Ÿ“ Check status: /tech:worktree-status $BRANCH_NAME" - echo "๐Ÿ“ Or view log: cat $LOG_FILE" -elif [ "$SKIP_CHECK" = true ]; then - echo "โšก Cargo check skipped (--fast / --no-check mode)" -fi - -echo "" -echo "๐Ÿš€ Next steps:" -echo "" -echo "If Claude Code is running:" -echo " 1. /exit" -echo " 2. cd $WORKTREE_DIR" -echo " 3. claude" -echo "" -echo "If Claude Code is NOT running:" -echo " cd $WORKTREE_DIR && claude" -echo "" -echo "โœ… Ready to work!" -``` - -## Flags - -### `--fast` / `--no-check` - -Skip cargo check entirely (instant setup). - -**Use when**: Quick fixes, documentation, README changes. - -```bash -/tech:worktree fix/typo --fast -โ†’ โœ… Ready in 1s (no cargo check) -``` - -## Status Check - -```bash -/tech:worktree-status feature/new-filter -โ†’ โœ… Cargo check passed (0 errors) -โ†’ โŒ Cargo check failed (see log) -โ†’ โณ Still running... -``` - -## Cleanup - -```bash -/tech:remove-worktree feature/new-filter -# Or manually: -git worktree remove .worktrees/feature-new-filter -git worktree prune -``` - -## Troubleshooting - -**"worktree already exists"** -```bash -git worktree remove .worktrees/$BRANCH_NAME -# Then retry -``` - -**"branch already exists"** -```bash -git branch -D $BRANCH_NAME -# Then retry -``` diff --git a/.claude/skills/ship/SKILL.md b/.claude/skills/rtk-release/SKILL.md similarity index 95% rename from .claude/skills/ship/SKILL.md rename to .claude/skills/rtk-release/SKILL.md index 6ae7fc2eab..396cfd3ea1 100644 --- a/.claude/skills/ship/SKILL.md +++ b/.claude/skills/rtk-release/SKILL.md @@ -1,12 +1,15 @@ --- -description: Build, commit, push & version bump workflow - automates the complete release cycle +name: rtk-release +description: RTK release workflow - build verification, version bump, CHANGELOG, git tag & push to trigger CI/CD. RTK-specific; renamed from "ship" to avoid collision with the gstack ship skill. allowed-tools: Read Write Edit Bash Grep Glob --- -# Ship Release +# RTK Release Systematic release workflow for RTK: build verification, version bump, changelog update, git tag, and push to trigger CI/CD. +> Formerly named `ship`. Renamed to `rtk-release` on 2026-07-26 because the gstack `ship` skill shadowed it in the skill list, making this one unreachable. + ## When to Use - **Manual invocation**: When ready to release a new version @@ -15,7 +18,7 @@ Systematic release workflow for RTK: build verification, version bump, changelog ## Pre-Release Checklist (Auto-Verified) -Before running `/ship`, verify: +Before running `/rtk-release`, verify: ### 1. Quality Checks Pass ```bash diff --git a/.claude/skills/rtk-tdd/SKILL.md b/.claude/skills/rtk-tdd/SKILL.md deleted file mode 100644 index e13ec58a54..0000000000 --- a/.claude/skills/rtk-tdd/SKILL.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -name: rtk-tdd -description: > - Enforces TDD (Red-Green-Refactor) for Rust development. Auto-triggers on - implementation, testing, refactoring, and bug fixing tasks. Provides - Rust-idiomatic testing patterns with anyhow/thiserror, cfg(test), and - Arrange-Act-Assert workflow. -allowed-tools: - - Read - - Write - - Edit - - Bash -effort: medium -tags: [tdd, testing, rust, red-green-refactor, rtk] ---- - -# Rust TDD Workflow - -## Three Laws of TDD - -1. Do NOT write production code without a failing test -2. Write only enough test to fail (including compilation failure) -3. Write only enough production code to pass the failing test - -Cycle: **RED** (test fails) -> **GREEN** (minimum to pass) -> **REFACTOR** (cleanup, cargo test) - -## Red-Green-Refactor Steps - -``` -1. Write test in #[cfg(test)] mod tests of the SAME file -2. cargo test MODULE::tests::test_name -- must FAIL (red) -3. Implement the minimum in the function -4. cargo test MODULE::tests::test_name -- must PASS (green) -5. Refactor if needed, re-run cargo test (still green) -6. cargo fmt && cargo clippy --all-targets && cargo test (final gate) -``` - -Never skip step 2. If the test passes immediately, it tests nothing. - -## Idiomatic Rust Test Patterns - -| Pattern | Usage | When | -|---------|-------|------| -| Arrange-Act-Assert | Base structure for every test | Always | -| `assert_eq!` / `assert!` | Direct comparison / booleans | Deterministic values | -| `assert!(result.is_err())` | Error path testing | Invalid inputs | -| `Result<()>` return type | Tests with `?` operator | Fallible functions | -| `#[should_panic]` | Expected panic | Invariants, preconditions | -| `tempfile::NamedTempFile` | File/I/O tests | Filesystem-dependent code | - -## Patterns by Code Type - -| Code Type | Test Pattern | Example | -|-----------|-------------|---------| -| Pure function (str -> str) | Input literal -> assert output | `assert_eq!(truncate("hello", 3), "...")` | -| Parsing/filtering | Raw string -> filter -> contains/not-contains | `assert!(filter(raw).contains("expected"))` | -| Validation/security | Boundary inputs -> assert bool | `assert!(!is_valid("../etc/passwd"))` | -| Error handling | Bad input -> `is_err()` | `assert!(parse("garbage").is_err())` | -| Struct/enum roundtrip | Construct -> serialize -> deserialize -> eq | `assert_eq!(from_str(to_str(x)), x)` | - -## Naming Convention - -``` -test_{function}_{scenario} -test_{function}_{input_type} -``` - -Examples: `test_truncate_edge_case`, `test_parse_invalid_input`, `test_filter_empty_string` - -## When NOT to Use Pure TDD - -- Functions calling `Command::new()` -> test the parser, not the execution -- `std::process::exit()` -> refactor to `Result` first, then test the Result -- Direct I/O (SQLite, network) -> use tempfile/mock or test the pure logic separately -- Main/CLI wiring -> covered by integration/smoke tests - -## Pre-Commit Gate - -```bash -cargo fmt --all --check -cargo clippy --all-targets -cargo test -``` - -All 3 must pass. No exceptions. No `#[allow(...)]` without documented justification. diff --git a/.claude/skills/rtk-tdd/references/testing-patterns.md b/.claude/skills/rtk-tdd/references/testing-patterns.md deleted file mode 100644 index e594c8a0bf..0000000000 --- a/.claude/skills/rtk-tdd/references/testing-patterns.md +++ /dev/null @@ -1,124 +0,0 @@ -# RTK Testing Patterns Reference - -## Untested Modules Backlog - -Prioritized by testability (pure functions first, I/O-heavy last). - -### High Priority (pure functions, trivial to test) - -| Module | Testable Functions | Notes | -|--------|-------------------|-------| -| `diff_cmd.rs` | `compute_diff`, `similarity`, `truncate`, `condense_unified_diff` | 4 pure functions, 0 tests | -| `env_cmd.rs` | `mask_value`, `is_lang_var`, `is_cloud_var`, `is_tool_var`, `is_interesting_var` | 5 categorization functions | - -### Medium Priority (need tempfile or parsed input) - -| Module | Testable Functions | Notes | -|--------|-------------------|-------| -| `tracking.rs` | `estimate_tokens`, `Tracker::new`, query methods | Use tempfile for SQLite | -| `config.rs` | `Config::default`, config parsing | Test default values and TOML parsing | -| `deps.rs` | Dependency file parsing | Test with sample Cargo.toml/package.json strings | -| `summary.rs` | Output type detection heuristics | Pure string analysis | - -### Low Priority (heavy I/O, CLI wiring) - -| Module | Testable Functions | Notes | -|--------|-------------------|-------| -| `container.rs` | Docker/kubectl output filters | Requires mocking Command output | -| `find_cmd.rs` | Directory grouping logic | Filesystem-dependent | -| `wget_cmd.rs` | `compact_url`, `format_size`, `truncate_line`, `extract_filename_from_output` | Some pure helpers worth testing | -| `gain.rs` | Display formatting | Depends on tracking DB | -| `init.rs` | CLAUDE.md generation | File I/O | -| `main.rs` | CLI routing | Covered by smoke tests | - -## RTK Test Patterns - -### Pattern 1: Filter Function (most common in RTK) - -```rust -#[test] -fn test_FILTER_happy_path() { - // Arrange: raw command output as string literal - let input = r#" -line of noise -line with relevant data -more noise -"#; - // Act - let result = filter_COMMAND(input); - // Assert: output contains expected, excludes noise - assert!(result.contains("relevant data")); - assert!(!result.contains("noise")); -} -``` - -Used in: `git.rs`, `grep_cmd.rs`, `lint_cmd.rs`, `tsc_cmd.rs`, `vitest_cmd.rs`, `pnpm_cmd.rs`, `next_cmd.rs`, `prettier_cmd.rs`, `playwright_cmd.rs`, `prisma_cmd.rs` - -### Pattern 2: Pure Computation - -```rust -#[test] -fn test_FUNCTION_deterministic() { - assert_eq!(truncate("hello world", 8), "hello..."); - assert_eq!(truncate("short", 10), "short"); -} -``` - -Used in: `gh_cmd.rs` (`truncate`), `utils.rs` (`truncate`, `format_tokens`, `format_usd`) - -### Pattern 3: Validation / Security - -```rust -#[test] -fn test_VALIDATOR_rejects_injection() { - assert!(!is_valid("malicious; rm -rf /")); - assert!(!is_valid("../../../etc/passwd")); -} -``` - -Used in: `pnpm_cmd.rs` (`is_valid_package_name`) - -### Pattern 4: ANSI Stripping - -```rust -#[test] -fn test_strip_ansi() { - let input = "\x1b[32mgreen\x1b[0m normal"; - let output = strip_ansi(input); - assert_eq!(output, "green normal"); - assert!(!output.contains("\x1b[")); -} -``` - -Used in: `vitest_cmd.rs`, `utils.rs` - -## Test Skeleton Template - -```rust -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_FUNCTION_happy_path() { - // Arrange - let input = r#"..."#; - // Act - let result = FUNCTION(input); - // Assert - assert!(result.contains("expected")); - assert!(!result.contains("noise")); - } - - #[test] - fn test_FUNCTION_empty_input() { - let result = FUNCTION(""); - assert!(...); - } - - #[test] - fn test_FUNCTION_edge_case() { - // Boundary conditions: very long input, special chars, unicode - } -} -```