Update AI skills: ranger tags, self-improving triggers, Rust binary scaffold
Assisted-by: Claude Opus 4.6 via pi
change onzmnnpnkouvunynxorsvqsumxoklknm
commit 28345cc6bccd6fd09814ccbebe744b1d50bb81a1
author Alpha Chen <alpha@kejadlen.dev>
date
parent wryytrkl
diff --git a/ai/claude/skills/ranger/SKILL.md b/ai/claude/skills/ranger/SKILL.md
index 28b95be3..8629bb58 100644
--- a/ai/claude/skills/ranger/SKILL.md
+++ b/ai/claude/skills/ranger/SKILL.md
@@ -28,6 +28,13 @@ ranger task move <key> -A <other>             # Reorder: place after another tas
 ranger task delete <key>                      # Delete a task
 ```
 
+```bash
+# Tags (created implicitly — no "tag create" command; adding a new name creates the tag)
+ranger tag list                              # List all tags
+ranger tag add <task-key> <tag-name>         # Add a tag to a task (creates tag if new)
+ranger tag remove <task-key> <tag-name>      # Remove a tag from a task (alias: rm)
+```
+
 Task states for `--state`: `icebox`, `queued`, `in_progress`, `done`.
 
 The `RANGER_DEFAULT_BACKLOG` env var sets the default `--backlog` value so you can omit it.
diff --git a/ai/claude/skills/scaffolding-rust-binary/SKILL.md b/ai/claude/skills/scaffolding-rust-binary/SKILL.md
new file mode 100644
index 00000000..78b7933f
--- /dev/null
+++ b/ai/claude/skills/scaffolding-rust-binary/SKILL.md
@@ -0,0 +1,312 @@
+---
+name: scaffolding-rust-binary
+description: Use when creating a new Rust binary project from scratch — scaffolding the crate, justfile, CI, coverage, release pipeline, and test harness
+---
+
+# Creating a Rust Binary Project
+
+Scaffold a single-crate Rust binary with library code, a justfile, 100% coverage enforcement, GitHub Actions CI, and CalVer releases.
+
+*This is a self-improving skill — see the `self-improving-skills` skill.*
+
+## Project Structure
+
+Single crate with both library and binary targets. All domain logic lives in the library; the binary is a thin CLI shell.
+
+```
+project/
+├── Cargo.toml
+├── Cargo.lock              # committed — it's a binary
+├── justfile
+├── .gitignore              # /target
+├── .github/workflows/
+│   ├── ci.yml
+│   └── release.yml
+├── src/
+│   ├── lib.rs              # Library root — re-exports modules
+│   ├── error.rs            # thiserror enum
+│   ├── (domain modules)
+│   └── bin/<name>/
+│       ├── main.rs          # Entrypoint, clap, thin dispatch
+│       ├── output.rs        # Human/JSON output helpers
+│       └── commands/        # One module per subcommand group
+│           └── mod.rs
+├── tests/
+│   └── cli.rs              # Integration tests via assert_cmd
+└── migrations/             # If using a database
+```
+
+## Cargo.toml
+
+```toml
+[package]
+name = "<name>"
+version = "0.1.0"
+edition = "2024"
+
+[[bin]]
+name = "<name>"
+path = "src/bin/<name>/main.rs"
+
+[dependencies]
+clap = { version = "*", features = ["derive", "env"] }
+color-eyre = "*"
+thiserror = "*"
+tokio = { version = "*", features = ["full"] }
+tracing-subscriber = { version = "*", features = ["env-filter"] }
+
+[dev-dependencies]
+assert_cmd = "*"
+predicates = "*"
+tempfile = "*"
+```
+
+Key choices:
+- **`edition = "2024"`** — latest stable edition.
+- **Unpinned dependencies (`"*"`)** — `Cargo.lock` is committed (it's a binary), so builds are reproducible. Unpinned versions mean `cargo update` gets the latest compatible releases without editing `Cargo.toml`.
+- **`clap` with `derive` + `env`** — declarative CLI with env var fallbacks.
+- **`color-eyre`** — pretty error reports in the binary.
+- **`thiserror`** — structured errors in the library.
+
+## Entrypoint Pattern
+
+```rust
+// src/bin/<name>/main.rs
+mod commands;
+mod output;
+
+use clap::{Parser, Subcommand};
+use tracing_subscriber::{EnvFilter, fmt, prelude::*};
+
+#[derive(Parser)]
+#[command(name = "<name>", about = "<description>")]
+struct Cli {
+    #[arg(long, global = true)]
+    json: bool,
+
+    #[command(subcommand)]
+    command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+    #[command(visible_alias = "x")]
+    Example {
+        #[command(subcommand)]
+        command: commands::example::ExampleCommands,
+    },
+}
+
+#[tokio::main]
+async fn main() -> color_eyre::Result<()> {
+    color_eyre::install()?;
+    tracing_subscriber::registry()
+        .with(fmt::layer())
+        .with(EnvFilter::from_default_env())
+        .init();
+
+    let cli = Cli::parse();
+    match cli.command {
+        Commands::Example { command } => {
+            commands::example::run(command, cli.json).await?;
+        }
+    }
+    Ok(())
+}
+```
+
+## Error Pattern
+
+Library errors use `thiserror`. The binary uses `color_eyre::Result`.
+
+```rust
+// src/error.rs
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("not found: {0}")]
+    NotFound(String),
+    #[error("io error: {0}")]
+    Io(#[from] std::io::Error),
+}
+```
+
+## justfile
+
+```just
+default: all
+
+fmt:
+    cargo fmt --all
+
+check:
+    cargo check --workspace
+
+clippy:
+    cargo clippy --workspace -- -D warnings
+
+coverage:
+    #!/usr/bin/env bash
+    set -euo pipefail
+    export RUSTFLAGS="-Cinstrument-coverage"
+    export CARGO_TARGET_DIR="target/coverage"
+    export LLVM_PROFILE_FILE="target/coverage/profraw/%p-%m.profraw"
+    rm -rf target/coverage
+    cargo test --workspace -q
+    REPORT=$(grcov target/coverage/profraw \
+        --binary-path ./target/coverage/debug/ \
+        -s . \
+        -t covdir \
+        --ignore-not-existing \
+        --keep-only 'src/**' \
+        --ignore 'src/bin/**' \
+        --excl-line 'cov-excl-line' \
+        --excl-start 'cov-excl-start' \
+        --excl-stop 'cov-excl-stop')
+    echo "$REPORT" | jq -r '
+        def files:
+            to_entries[] | .value |
+            if .children then .children | files
+            else "\(.name): \(.coveragePercent)% (\(.linesCovered)/\(.linesTotal))"
+            end;
+        .children | files
+    '
+    COVERAGE=$(echo "$REPORT" | jq '.coveragePercent')
+    echo ""
+    echo "Total: ${COVERAGE}%"
+    if [ "$(echo "$COVERAGE < 100" | bc -l)" -eq 1 ]; then
+        echo "ERROR: Coverage is below 100%"
+        exit 1
+    fi
+
+all: fmt clippy coverage
+
+install:
+    cargo install --locked --path .
+```
+
+Key design:
+- **`coverage` uses a separate `CARGO_TARGET_DIR`** — prevents instrumented and non-instrumented artifacts from mixing, which causes phantom uncovered lines with grcov.
+- **100% coverage on library code only** — `--keep-only 'src/**' --ignore 'src/bin/**'`. Binary code is tested via integration tests but not measured.
+- **`covdir` output** — machine-readable JSON, parsed with `jq` for a clean summary.
+- **Exclusion markers** — `cov-excl-line`, `cov-excl-start`/`cov-excl-stop` for structurally unreachable code.
+
+## CI Workflow
+
+```yaml
+# .github/workflows/ci.yml
+name: CI
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+
+jobs:
+  ci:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - run: rustup component add clippy rustfmt llvm-tools
+      - run: cargo install grcov
+      - run: cargo install just
+      - run: cargo fmt --check
+      - run: just clippy coverage
+```
+
+`cargo fmt --check` instead of `just fmt` — CI should fail on unformatted code, not silently fix it.
+
+## Release Workflow
+
+CalVer (`YYYY-MM-DD+SHORT_SHA`) with automatic releases on green main builds.
+
+```yaml
+# .github/workflows/release.yml
+name: Release
+
+on:
+  workflow_dispatch:
+  workflow_run:
+    workflows: [CI]
+    types: [completed]
+    branches: [main]
+
+permissions:
+  contents: write
+
+jobs:
+  build:
+    if: >-
+      github.event_name == 'workflow_dispatch'
+      || github.event.workflow_run.conclusion == 'success'
+    runs-on: macos-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Calculate version
+        id: version
+        run: |
+          CALVER=$(date -u +"%Y-%m-%d")
+          SHORT_SHA=$(git rev-parse --short HEAD)
+          echo "version=${CALVER}+${SHORT_SHA}" >> $GITHUB_OUTPUT
+
+      - name: Build
+        run: |
+          cargo build --release
+          tar -czf <name>-aarch64-apple-darwin.tar.gz -C target/release <name>
+
+      - name: Publish
+        run: |
+          VERSION="${{ steps.version.outputs.version }}"
+          git tag "v${VERSION}"
+          git push origin "v${VERSION}"
+          gh release create "v${VERSION}" \
+            --title "v${VERSION}" \
+            --generate-notes \
+            <name>-aarch64-apple-darwin.tar.gz
+        env:
+          GH_TOKEN: ${{ github.token }}
+```
+
+Adjust `runs-on` and archive name for target platform. Add matrix builds for cross-platform.
+
+## Testing
+
+**Library tests**: Unit tests in each module, using `tempfile` for isolation when state is involved.
+
+**Integration tests**: `tests/cli.rs` exercises the compiled binary end-to-end via `assert_cmd`.
+
+```rust
+// tests/cli.rs
+use assert_cmd::Command;
+use assert_cmd::cargo::cargo_bin_cmd;
+use tempfile::tempdir;
+
+fn cmd() -> Command {
+    Command::from(cargo_bin_cmd!("<name>"))
+}
+
+#[test]
+fn shows_help() {
+    cmd().arg("--help").assert().success();
+}
+```
+
+## Prerequisites
+
+```bash
+rustup component add clippy rustfmt llvm-tools
+cargo install grcov just
+```
+
+## Quick Reference
+
+| Task | Command |
+|------|---------|
+| Format | `just fmt` |
+| Lint | `just clippy` |
+| Coverage | `just coverage` |
+| All checks | `just all` |
+| Install from source | `just install` |
+| Find uncovered lines | Change `-t covdir` to `-t markdown` in justfile |
diff --git a/ai/claude/skills/self-improving-skills/SKILL.md b/ai/claude/skills/self-improving-skills/SKILL.md
index 57622ba0..50b49ab3 100644
--- a/ai/claude/skills/self-improving-skills/SKILL.md
+++ b/ai/claude/skills/self-improving-skills/SKILL.md
@@ -23,12 +23,16 @@ Once you've figured it out, that's exactly the kind of thing to capture in the s
 
 ## When to Improve
 
+- **You had to check `--help`.** If the skill didn't have the command you needed and you had to fall back to `--help` or docs, that's the clearest signal the skill is missing something. After the task, add what you learned.
+- **You tried a command that doesn't exist.** If you assumed a subcommand or flag existed and it failed, capture the correct approach so the mistake isn't repeated.
 - Skill is sparse or missing sections you'd expect
 - You discover a command, pattern, or gotcha not yet documented
 - You hit an error and figure out the fix
 - An existing section is wrong or outdated
 - You find a workflow that works well and isn't captured
 
+**Do it immediately after the task — don't wait to be asked.** If the user has to say "update the skill," you've already missed the cue.
+
 ## How to Improve
 
 1. **Do the task first.** Don't stop to write docs. Get the work done.