diff --git a/packages/docs/logs/2026-08-03_locate-chezmoi-agent-skills.md b/packages/docs/logs/2026-08-03_locate-chezmoi-agent-skills.md new file mode 100644 index 0000000000..57eb948cfe --- /dev/null +++ b/packages/docs/logs/2026-08-03_locate-chezmoi-agent-skills.md @@ -0,0 +1,36 @@ +--- +id: log-locate-chezmoi-agent-skills-2026-08-03 +type: log +status: complete +board: false +--- + +# Locate Chezmoi Agent Skills + +Confirmed with `chezmoi source-path ~/.agents/skills` that the managed source +directory is `packages/dotfiles/dot_agents/skills`. + +Of the 65 top-level managed skills, 32 (49.2%) contain a `What's New` section. +These sections are static snapshots, commonly labeled 2024–2026, rather than +automatically refreshed release feeds. + +## Session Log — 2026-08-03 + +### Done + +- Located the chezmoi-managed agent skills directory at + `packages/dotfiles/dot_agents/skills`. +- Confirmed the mapping with the live `chezmoi source-path` command. +- Measured the release-oriented content: 32 of 65 skills contain a + `What's New` section. + +### Remaining + +- None. + +### Caveats + +- `bun run check-todos` is currently blocked by the invalid frontmatter ID in + the existing untracked file + `packages/docs/logs/2026-08-03_scout-evals-populate-100-case-dataset.md`. +- Release/version claims in skills are static Markdown and can become stale. diff --git a/packages/docs/plans/2026-08-03_agent-skills-release-refresh.md b/packages/docs/plans/2026-08-03_agent-skills-release-refresh.md new file mode 100644 index 0000000000..635992c24e --- /dev/null +++ b/packages/docs/plans/2026-08-03_agent-skills-release-refresh.md @@ -0,0 +1,180 @@ +--- +id: agent-skills-release-refresh +type: plan +status: in-progress +board: true +verification: agent +disposition: active +--- + +# Agent Skills Release Refresh + +## Goal + +Refresh the chezmoi-managed agent-skill corpus against current primary +documentation, add missing high-value skills when the installed tool and +repository inventory justify them, and retain only operationally useful, +non-obvious guidance. + +## Scope + +Treat one top-level directory under `packages/dotfiles/dot_agents/skills/` as +one researched item. Require at least 30 distinct, successfully fetched +documentation pages for each of the 65 current skills. This produces a minimum +of 1,950 fetched sources before follow-up research or newly added skills. + +Commands, libraries, and features mentioned inside a skill are covered by that +skill's 30-source set rather than treated as independent 30-source units. + +## Research Questions and Source Strategy + +1. Which existing claims are stale, removed, renamed, or incomplete? Use + official release notes, versioned manuals, API references, and upstream + repositories. +2. Which new releases materially change agent behavior? Compare current stable + releases with the newest version already covered by each skill. +3. Which deprecated commands or unsafe examples should be removed? Verify + against current command references, migration guides, schemas, and changelogs. +4. Which missing workflows recur in the monorepo? Compare repository packages, + installed tools, recent logs, and existing skill triggers. +5. Which skills are too large for reliable loading? Apply progressive + disclosure and move detailed reference material out of `SKILL.md` where it + improves usability. +6. Which internal workflow skills have drifted from live repository policy? + Verify against the `AGENTS.md` hierarchy and current implementation. +7. Which source claims disagree? Prefer current primary sources and record + unresolved contradictions explicitly. +8. Which updates survive independent forward tests? Give revised skills to + clean-context agents using realistic tasks without leaking expected answers. + +## Execution Model + +- Create an isolated worktree from `origin/main` and register it as a git-spice + stack with `git-spice branch track feature/agent-skills-refresh --base main` + (see `git-spice-helper`). +- Divide the corpus into disjoint research batches and rotate three concurrent + subagents through them. Research agents must use Lightpanda first for page + extraction, then `curl`/`wget`, and PinchTab only for blocked or interactive + sites. +- Require a Research ledger per skill: a numbered list of the successfully + fetched primary source titles and URLs, appended to the skill's + `references/` material, sufficient to verify the 30-source quality gate. +- Keep source ledgers in the skill's own `references/` files rather than + bloating the runtime `SKILL.md` context. +- Edit skills only after the relevant 30-source threshold is met. Preserve + durable workflows; replace release catalogs with concise current behavior and + migration guidance. +- Split the final change into cohesive git-spice stack layers by technology + group if the diff is too large for one reviewable PR. + +## Proposed Research Batches + +1. Languages and runtimes: Bun, TypeScript, Go, Rust, Python, JVM, and Lua. +2. Application frameworks and data tooling: Vite/React, Hono, XState, Zod, + Prisma, ESLint, Satori, and Mastra. +3. Containers and orchestration: Docker, Kubernetes, Helm, Argo CD, Talos, + cdk8s, LinuxServer containers, storage, and deployment patterns. +4. Infrastructure and networking: Terraform/OpenTofu, Tailscale, Grafana, + OpenTelemetry, PagerDuty, Sentry, and Bugsink. +5. Developer CLI and configuration: Git, GitHub, git-spice, worktrees, modern + CLI tools, Fish, Zellij, chezmoi, 1Password, Lightpanda, and PinchTab. +6. Product/API helpers: Buildkite, Discord, Riot/League, Apple HIG, Figma, + Typst, and Xcode Cloud. +7. Repository-native workflows: PR health/monitoring/automation, version + management, review, reflection, grading, and monorepo documentation. +8. Gap analysis: installed and repository-critical tools that lack a skill. + +## Quality Gates + +- Every refreshed skill has at least 30 successfully fetched source pages in + the evidence ledger. +- Every shipped URL is checked live and does not return a dead link. +- Every factual release/version claim is traceable to a visited primary source. +- Skill bodies remain concise and use progressive disclosure; detailed material + belongs in directly linked `references/` files. +- Existing user changes to `bugsink-helper/SKILL.md` are preserved and + reconciled rather than overwritten. +- Run skill validation for every changed or added skill. +- Run focused formatting/link checks, `bun run check-todos`, and the staged + pre-commit hook; fix all failures within the requested scope. +- Run independent clean-context forward tests for representative revised skills + and adversarial source review before publication. +- Verify the chezmoi source/live mapping after the source changes. Do not apply + repository state over the live skills without checking direction first. + +## Remaining + +- [x] Confirm that “30 sources” means per top-level skill, with commands and + libraries inside that skill covered by the same 30-source set. +- [x] Create the isolated worktree and register it as a git-spice stack. +- [x] Inventory the full corpus, existing references, current versions, and + missing high-value skills. +- [ ] Run the multi-agent research batches and build the per-skill evidence + ledger. +- [ ] Reconcile overlapping and contradictory findings. +- [ ] Refresh existing skills and add justified missing skills. +- [ ] Validate links, skill structure, focused checks, docs, and chezmoi drift. +- [ ] Forward-test representative skills with clean-context agents. +- [ ] Publish the reviewable git-spice stack with source and verification + evidence. + +## Comment Log + +- 2026-08-03: Initial corpus inventory found 65 top-level skills; 32 currently + contain a `What's New` section. The main checkout also contains an active + user change to `bugsink-helper/SKILL.md`, which must be preserved. +- 2026-08-03: The user confirmed the 30-source requirement applies per + top-level skill. Created `.claude/worktrees/agent-skills-refresh` on + `feature/agent-skills-refresh`, at the time initialized with native + `gh stack` per the then-current policy. That policy was reverted repo-wide + to git-spice-only on 2026-08-03 (#1970); see the 2026-08-04 entry below for + the correction. +- 2026-08-03: Completed the first two research waves for Git, Bun, TypeScript, + Rust, and Python: 186 primary pages plus 15 supplemental official or + project-primary pages, 201 total, were successfully fetched and inspected. The first + five skills were rewritten as concise routing entrypoints with focused + references and Research ledgers (numbered title/URL lists, the format the + Execution Model now specifies — not the richer fetch-method/retrieval-result/ + date-version ledger originally described); the skill validator passes for + all five. See the 2026-08-04 entry below for independent liveness + verification of every listed source. +- 2026-08-04: Re-verified the five shipped Research ledgers (`git-helper`, + `bun-runtime-best-practices`, `typescript-helper`, `rust-helper`, + `python-helper`) by extracting all linked URLs and live-checking each with + `curl -L --max-time 15`: 202 unique URLs, 202 returned HTTP 200, 0 dead + links. This closes the reproducibility gap in the prior entry — a later + reviewer can now confirm every listed source resolves, independent of the + fetch-method/date detail the Execution Model no longer requires per entry. +- 2026-08-04: Registered `feature/agent-skills-refresh` with git-spice + (`git-spice branch track feature/agent-skills-refresh --base main`), + reconciling the branch's actual tracking with the corrected Execution + Model. The branch is git-spice-tracked going forward; no `gh stack` state + remains authoritative for this work. + +## Session Log — 2026-08-03 + +### Done + +- Loaded the skill-authoring, deep-research, Lightpanda, chezmoi, worktree, + native GitHub stack, Git, and monorepo-documentation guidance. +- Inventoried 65 top-level managed skills and identified the existing Bugsink + overlap that must be preserved. +- Confirmed the 30-source-per-top-level-skill scope with the user. +- Created and initialized the isolated native-stack worktree. +- Completed and distilled the first five 30-source research audits: Git, Bun, + TypeScript, Rust, and Python. +- Replaced stale monolithic tutorials with concise entrypoints and focused + reference files, including current release and compatibility boundaries. +- Validated all five rewritten skill structures with the skill-creator + validator. + +### Remaining + +- Complete all research, implementation, verification, forward-testing, and + publication work. + +### Caveats + +- `bun run check-todos` in the main checkout stopped on the invalid frontmatter + ID in the existing untracked file + `packages/docs/logs/2026-08-03_scout-evals-populate-100-case-dataset.md`. diff --git a/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/SKILL.md b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/SKILL.md index cda4c305ee..8121efa8c7 100644 --- a/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/SKILL.md +++ b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/SKILL.md @@ -1,673 +1,161 @@ --- name: bun-runtime-best-practices -description: | - Bun runtime best practices and API usage patterns - When user works with file I/O, environment variables, subprocess spawning, or uses Node.js APIs that have Bun equivalents +description: Bun runtime APIs and current operational patterns for files, processes, modules, networking, databases, tests, and deployment. Use when writing or reviewing Bun-only TypeScript, selecting Bun versus Web or Node APIs, or migrating Node code to Bun. --- -# Bun Runtime Best Practices Agent +# Bun Runtime Best Practices -## What's New in Bun 1.3 (October 2025) +Use Bun APIs when they provide a clear Bun-native capability. Use standard Web APIs for portability and `node:` APIs when Bun does not cover the operation or the code must remain Node-compatible. Do not replace a correct portable API merely because a Bun equivalent exists. -- **Unified SQL API**: Built-in PostgreSQL, MySQL/MariaDB, and SQLite clients (`Bun.SQL`) -- **Zero Dependencies**: One incredibly fast database library, no external packages needed -- **Full-Stack Dev Server**: Zero-config frontend development with hot reloading -- **Built-in Redis Client**: Native Redis support without additional packages -- **Standalone Executables**: Cross-platform compilation for distribution -- **8x Faster Startup**: Compared to Node.js, with 145k req/s HTTP throughput (Node: 65k) -- **Isolated Installs**: Minimize dependency conflicts across projects -- **Vercel Runtime Support**: Deploy Bun apps to Vercel seamlessly +## Current baseline -## Overview +Verified against Bun 1.3.14 on 2026-08-03. Check the project pin and runtime before relying on a newly added API: -This agent teaches Bun-specific APIs and patterns to replace Node.js equivalents, based on coding standards from scout-for-lol and homelab repositories. Bun provides faster, more modern alternatives to Node.js APIs. - -**Performance Note**: Bun 1.3 delivers 8x faster startup than Node.js with 145k requests/second HTTP server performance. Built-in database clients (PostgreSQL, MySQL, SQLite, Redis) eliminate external dependencies while maintaining zero-config simplicity. - -## Core Principle - -**Prefer Bun APIs over Node.js imports** for better performance and modern patterns. - -## File I/O - -### Use Bun.file() and Bun.write() - -**❌ Avoid: Node.js fs module** - -```typescript -import fs from "fs"; -import { promises as fs } from "fs/promises"; -import * as fs from "node:fs/promises"; - -// Don't use these -const content = await fs.readFile("file.txt", "utf-8"); -await fs.writeFile("file.txt", "content"); -``` - -**✅ Prefer: Bun.file() and Bun.write()** - -```typescript -// Reading files -const file = Bun.file("file.txt"); -const content = await file.text(); -const json = await file.json(); -const arrayBuffer = await file.arrayBuffer(); -const stream = file.stream(); - -// Writing files -await Bun.write("output.txt", "Hello, world!"); -await Bun.write("data.json", JSON.stringify({ foo: "bar" })); -await Bun.write("binary.dat", new Uint8Array([1, 2, 3])); - -// With options -await Bun.write("file.txt", "content", { - createPath: true, // Create parent directories -}); +```bash +bun --version ``` -### File Operations - -```typescript -// Check if file exists -const file = Bun.file("file.txt"); -const exists = await file.exists(); - -// Get file size -const size = file.size; +Bun 1.3.14 adds `Bun.Image`, faster isolated-linker warm installs, and experimental HTTP/2 and HTTP/3 server support. Features explicitly marked experimental in Bun's documentation are not stable defaults. -// Get file type -const type = file.type; // MIME type +Read [references/release-notes-1.3.md](references/release-notes-1.3.md) when upgrading Bun or evaluating a new 1.3 API. Read [references/io-process-modules.md](references/io-process-modules.md) for detailed file, subprocess, shell, environment, module, stream, binary, hashing, and worker patterns. Read [references/data-networking.md](references/data-networking.md) for HTTP, WebSocket, SQL, Redis, SQLite, S3, cookies, CSRF, secrets, and cron guidance. -// Read file in chunks -const file = Bun.file("large-file.txt"); -for await (const chunk of file.stream()) { - console.log(chunk); -} -``` - -## Environment Variables +## Choose the narrowest correct API -### Use Bun.env Instead of process.env +| Need | Default | Why | +| --- | --- | --- | +| Read or write a whole file | `Bun.file()` / `Bun.write()` | Concise Bun-native blob and sink APIs | +| Directory traversal, permissions, links, metadata mutation | `node:fs/promises` | Bun documents Node file APIs for uncovered operations | +| Portable HTTP, streams, binary data | Web APIs | `fetch`, `Request`, `Response`, streams, and typed arrays work across runtimes | +| Spawn a program with known arguments | `Bun.spawn()` | Literal argv avoids shell parsing | +| Cross-platform shell pipeline | Bun Shell | Supports pipelines and escapes interpolated strings | +| Path manipulation | `node:path` | Handles separators and normalization correctly | +| Password hashing | `Bun.password` | Argon2 and bcrypt password-specific API | +| General cryptography | Web Crypto or `node:crypto` | Required for cryptographic hashes, signatures, ciphers, and key operations | +| Non-cryptographic hashing | `Bun.hash` | Fast checksums and hash tables; never passwords or signatures | -**❌ Avoid: process.env** - -```typescript -const apiKey = process.env.API_KEY; -const port = process.env.PORT || "3000"; -``` +## Validate system boundaries -**✅ Prefer: Bun.env** - -```typescript -const apiKey = Bun.env.API_KEY; -const port = Bun.env.PORT ?? "3000"; - -// Bun.env is typed and provides better autocomplete -// Load from .env file automatically -``` - -### Environment Variable Validation +Environment variables, request bodies, database results, Redis values, and file JSON are untrusted input. Parse them before assigning domain types. ```typescript import { z } from "zod"; -// Validate environment variables with Zod -const EnvSchema = z.object({ +const Environment = z.object({ DATABASE_URL: z.string().url(), - API_KEY: z.string().min(1), - PORT: z.coerce.number().int().positive().default(3000), - NODE_ENV: z.enum(["development", "production", "test"]), + PORT: z.coerce.number().int().min(1).max(65_535).default(3000), }); -const env = EnvSchema.parse(Bun.env); +const environment = Environment.parse(Bun.env); ``` -## Process Spawning - -### Use Bun.spawn() Instead of child_process +`Bun.env`, `process.env`, and `import.meta.env` expose the same environment. `Bun.env` does not make arbitrary variables statically safe; schema validation does. -**❌ Avoid: child_process** +## Files and paths -```typescript -import { spawn } from "child_process"; -import { exec } from "node:child_process"; - -const child = spawn("ls", ["-la"]); -``` - -**✅ Prefer: Bun.spawn()** +`Bun.file("relative/path")` resolves relative to the process working directory, not the current source file. Anchor source-relative resources explicitly. ```typescript -// Simple command -const proc = Bun.spawn(["ls", "-la"]); -const output = await new Response(proc.stdout).text(); -console.log(output); - -// With options -const proc = Bun.spawn(["git", "status"], { - cwd: "/path/to/repo", - env: { ...Bun.env, GIT_AUTHOR_NAME: "Bot" }, - stdout: "pipe", - stderr: "pipe", -}); - -// Read output -const stdout = await new Response(proc.stdout).text(); -const stderr = await new Response(proc.stderr).text(); - -// Wait for exit -const exitCode = await proc.exited; -``` - -### Running Shell Commands +import { join } from "node:path"; +import { z } from "zod"; -```typescript -// Execute shell command -const proc = Bun.spawn(["sh", "-c", "echo Hello && date"], { - stdout: "pipe", +const Config = z.object({ + name: z.string(), + retries: z.number().int().min(0), }); -const output = await new Response(proc.stdout).text(); - -// Pipe to another command -const proc1 = Bun.spawn(["ls", "-la"], { stdout: "pipe" }); -const proc2 = Bun.spawn(["grep", ".ts"], { - stdin: proc1.stdout, - stdout: "pipe", +const configPath = join(import.meta.dir, "config.json"); +const config = Config.parse(await Bun.file(configPath).json()); +await Bun.write(join(import.meta.dir, "generated", "output.json"), JSON.stringify(config), { + createPath: true, }); - -const result = await new Response(proc2.stdout).text(); ``` -## Path Handling +Use `node:fs/promises` for directory and metadata operations that `Bun.file` and `Bun.write` do not model. -### Use import.meta.dir and import.meta.path +## Subprocesses -**❌ Avoid: path module and \_\_dirname** +Prefer literal argv. Capture stderr and enforce the exit status before consuming output as successful. ```typescript -import path from "path"; -import { dirname } from "node:path"; - -const dir = __dirname; -const file = __filename; -const joined = path.join(__dirname, "config.json"); -``` - -**✅ Prefer: import.meta** - -```typescript -// Get current directory -const currentDir = import.meta.dir; - -// Get current file path -const currentFile = import.meta.path; - -// Join paths -const configPath = `${import.meta.dir}/config.json`; - -// Or use Bun.file() with relative paths -const config = Bun.file("./config.json"); // Relative to current file -``` - -### Path Utilities - -```typescript -// Resolve absolute path -import { resolve } from "path"; // Can still use for complex operations - -const absolutePath = resolve(import.meta.dir, "../config.json"); - -// But prefer simpler string operations when possible -const configPath = `${import.meta.dir}/../config.json`; -``` - -## Cryptography - -### Use Bun.password, Bun.hash(), or Web Crypto API - -**❌ Avoid: crypto module** - -```typescript -import crypto from "crypto"; -import { createHash } from "node:crypto"; - -const hash = crypto.createHash("sha256").update("data").digest("hex"); -``` - -**✅ Prefer: Bun APIs** - -```typescript -// Password hashing -const hashedPassword = await Bun.password.hash("my-password"); -const isValid = await Bun.password.verify("my-password", hashedPassword); - -// With options -const hashedPassword = await Bun.password.hash("my-password", { - algorithm: "argon2id", // or "bcrypt", "scrypt" - memoryCost: 65536, - timeCost: 3, +const process = Bun.spawn(["git", "status", "--short"], { + cwd: import.meta.dir, + stdout: "pipe", + stderr: "pipe", }); -// Hashing (SHA, MD5, etc.) -const hasher = new Bun.CryptoHasher("sha256"); -hasher.update("data"); -const hash = hasher.digest("hex"); - -// One-liner -const hash = Bun.hash("data"); // Returns integer hash - -// Web Crypto API for advanced crypto -const encoder = new TextEncoder(); -const data = encoder.encode("data"); -const hashBuffer = await crypto.subtle.digest("SHA-256", data); -const hashArray = Array.from(new Uint8Array(hashBuffer)); -const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); -``` - -## Binary Data - -### Prefer Uint8Array Over Buffer - -**❌ Avoid: Buffer** - -```typescript -const buffer = Buffer.from("hello"); -const buffer2 = Buffer.alloc(10); -``` - -**✅ Prefer: Uint8Array and Bun APIs** - -```typescript -// Create binary data -const encoder = new TextEncoder(); -const bytes = encoder.encode("hello"); - -// Bun.file() handles binary data natively -const file = Bun.file("image.png"); -const arrayBuffer = await file.arrayBuffer(); -const bytes = new Uint8Array(arrayBuffer); - -// Write binary data -await Bun.write("output.bin", bytes); -``` - -## Module System - -### Use ESM Imports, Never require() - -**❌ Avoid: CommonJS require** - -```typescript -const fs = require("fs"); -const { parse } = require("./parser"); -``` - -**✅ Prefer: ESM imports** - -```typescript -import fs from "fs"; -import { parse } from "./parser.ts"; - -// Dynamic imports -const module = await import("./dynamic-module.ts"); -``` - -### Import Extensions - -**Always use `.ts` extensions in imports:** - -```typescript -// ✅ Good -import { helper } from "./utils/helper.ts"; -import type { User } from "./types/user.ts"; - -// ❌ Bad -import { helper } from "./utils/helper"; -import type { User } from "./types/user"; -``` - -## Bun-Specific Features - -### Bun.sleep() - -```typescript -// Sleep for specified time -await Bun.sleep(1000); // 1 second -await Bun.sleep(100); // 100ms - -// Better than setTimeout for awaiting -``` - -### Bun.which() - -```typescript -// Find executable in PATH -const git = Bun.which("git"); -console.log(git); // /usr/bin/git or null - -const nonexistent = Bun.which("nonexistent-command"); -console.log(nonexistent); // null -``` - -### Bun.peek() +const [stdout, stderr, exitCode] = await Promise.all([ + new Response(process.stdout).text(), + new Response(process.stderr).text(), + process.exited, +]); -```typescript -// Peek at stream without consuming -const proc = Bun.spawn(["echo", "hello"], { stdout: "pipe" }); - -const peeked = await Bun.peek(proc.stdout); -console.log(peeked); // Uint8Array - -// Stream is still readable -const full = await new Response(proc.stdout).text(); +if (exitCode !== 0) { + throw new Error(`git status failed (${exitCode}): ${stderr}`); +} ``` -### Bun.$. +Do not pass dynamic values through `sh -c`. Use `Bun.spawn()` argv or Bun Shell interpolation. Bun Shell escapes interpolated strings, but callers must still prevent option injection when user-controlled values can begin with `-`. -```typescript -// Shell-like command execution -import { $ } from "bun"; +## Modules and compatibility -// Execute and get output -const output = await $`ls -la`.text(); -console.log(output); +Prefer ESM for new Bun code, but do not claim CommonJS is unsupported. Bun supports both. Follow the repository's import-extension convention; Bun does not require `.ts` extensions universally. -// Pipe commands -const result = await $`ls -la | grep .ts`.text(); +Use `Buffer` when an API contract requires it. It is a supported `Uint8Array` subclass. Prefer `Uint8Array`, `ArrayBuffer`, `Blob`, and Web streams for portable new interfaces. -// With error handling -try { - await $`some-failing-command`; -} catch (error) { - console.error("Command failed:", error); -} -``` +Check Bun's Node compatibility table before replacing or adopting a Node API. Compatibility is API-specific, not all-or-nothing. -## HTTP Server +## Servers and network clients -### Use Bun.serve() +Use `Bun.serve()` for Bun-native HTTP or WebSocket servers and Web `fetch()` for outbound HTTP. Validate request data before use and make error paths explicit. ```typescript -// Simple HTTP server -Bun.serve({ - port: 3000, - fetch(request) { - return new Response("Hello World!"); +const server = Bun.serve({ + port: environment.PORT, + routes: { + "/health": new Response("ok"), }, -}); - -// With routing -Bun.serve({ - port: 3000, fetch(request) { - const url = new URL(request.url); - - if (url.pathname === "/api/users") { - return Response.json({ users: [] }); - } - - if (url.pathname === "/health") { - return new Response("OK"); - } - - return new Response("Not Found", { status: 404 }); - }, -}); - -// WebSocket support -Bun.serve({ - port: 3000, - fetch(request, server) { - if (server.upgrade(request)) { - return; // Upgraded to WebSocket - } - return new Response("HTTP response"); - }, - websocket: { - message(ws, message) { - ws.send(`Echo: ${message}`); - }, + return new Response(`Not found: ${new URL(request.url).pathname}`, { status: 404 }); }, }); -``` - -## Testing - -### Use Bun's Built-in Test Runner - -```typescript -import { test, expect, describe, beforeAll, afterAll } from "bun:test"; - -describe("User validation", () => { - beforeAll(() => { - // Setup - }); - - test("validates email format", () => { - const isValid = validateEmail("test@example.com"); - expect(isValid).toBe(true); - }); - - test("rejects invalid email", () => { - const isValid = validateEmail("invalid"); - expect(isValid).toBe(false); - }); - - afterAll(() => { - // Cleanup - }); -}); -``` - -### Run tests - -```bash -# Run all tests -bun test - -# Watch mode -bun test --watch - -# Specific file -bun test user.test.ts - -# With coverage -bun test --coverage -``` - -## Database Access (Bun 1.3+) - -### Unified SQL API - PostgreSQL, MySQL, SQLite - -Bun 1.3 provides a **unified SQL API** (`Bun.SQL`) for PostgreSQL, MySQL/MariaDB, and SQLite with zero external dependencies: - -```typescript -// PostgreSQL -const pg = await Bun.SQL`postgres://user:pass@localhost:5432/db`; -const users = await pg`SELECT * FROM users WHERE active = ${true}`; - -// MySQL / MariaDB -const mysql = await Bun.SQL`mysql://user:pass@localhost:3306/db`; -const posts = await mysql`SELECT * FROM posts LIMIT ${10}`; -// SQLite (in-memory or file) -const sqlite = await Bun.SQL`sqlite:///path/to/db.sqlite`; -const data = await sqlite`SELECT * FROM table WHERE id = ${123}`; +console.log(`Listening on ${server.url}`); ``` -**Benefits:** - -- **Zero dependencies**: No pg, mysql2, or better-sqlite3 packages needed -- **Tagged template literals**: Safe parameterized queries, SQL injection protection -- **Promise-based**: Modern async/await API throughout -- **Connection pooling**: Built-in for PostgreSQL and MySQL -- **Transactions**: Full transaction support across all databases - -### PostgreSQL Example - -```typescript -// Connect -const db = await Bun.SQL`postgres://user:pass@localhost:5432/mydb`; - -// Insert with returning -const [newUser] = await db` - INSERT INTO users (name, email) - VALUES (${name}, ${email}) - RETURNING * -`; - -// Query with parameters -const users = await db` - SELECT * FROM users - WHERE created_at > ${sinceDate} - ORDER BY created_at DESC - LIMIT ${limit} -`; - -// Transaction -await db.transaction(async (tx) => { - await tx`INSERT INTO accounts (user_id, balance) VALUES (${userId}, ${0})`; - await tx`UPDATE users SET has_account = true WHERE id = ${userId}`; -}); - -// Prepared statements (for repeated queries) -const getUser = db.prepare`SELECT * FROM users WHERE id = ${0}`; -const user1 = await getUser(123); -const user2 = await getUser(456); - -// Close connection -await db.close(); -``` +HTTP/2 and HTTP/3 support added in Bun 1.3.14 is experimental. Do not make production compatibility claims without testing the deployed protocol, TLS, proxy, and client path. -### MySQL / MariaDB Example +## Data clients -```typescript -// Connect -const db = await Bun.SQL`mysql://root:password@localhost:3306/testdb`; - -// Insert -await db` - INSERT INTO products (name, price) - VALUES (${productName}, ${price}) -`; - -// Query -const products = await db` - SELECT * FROM products - WHERE category = ${category} - AND price < ${maxPrice} -`; - -// Bulk insert -const values = products.map((p) => [p.name, p.price]); -await db`INSERT INTO products (name, price) VALUES ${values}`; - -// Close -await db.close(); -``` +Bun includes SQL, Redis, SQLite, and S3 clients. Their APIs are not interchangeable: -### SQLite Example (bun:sqlite still available) - -```typescript -// Option 1: Unified SQL API -const db = await Bun.SQL`sqlite:///mydb.sqlite`; -const users = await db`SELECT * FROM users WHERE active = ${true}`; - -// Option 2: bun:sqlite (for synchronous operations) -import { Database } from "bun:sqlite"; - -const db = new Database("mydb.sqlite"); - -// Create table -db.run(` - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - email TEXT UNIQUE NOT NULL - ) -`); - -// Prepared statements (synchronous) -const insert = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)"); -insert.run("Alice", "alice@example.com"); - -const query = db.prepare("SELECT * FROM users WHERE email = ?"); -const user = query.get("alice@example.com"); - -db.close(); -``` - -**Choose `Bun.SQL` for:** - -- Unified API across PostgreSQL/MySQL/SQLite -- Async/promise-based workflows -- Modern tagged template literal syntax - -**Choose `bun:sqlite` for:** - -- Synchronous SQLite operations -- Lower-level control -- Existing code using prepare/run/get patterns - -### Built-in Redis Client (Bun 1.3+) - -```typescript -// Connect to Redis -const redis = await Bun.redis.connect("redis://localhost:6379"); - -// Basic operations -await redis.set("key", "value"); -const value = await redis.get("key"); - -// Hash operations -await redis.hset("user:123", { name: "Alice", email: "alice@example.com" }); -const user = await redis.hgetall("user:123"); - -// Pub/Sub -const subscriber = await Bun.redis.connect("redis://localhost:6379"); -await subscriber.subscribe("channel", (message) => { - console.log("Received:", message); -}); - -const publisher = await Bun.redis.connect("redis://localhost:6379"); -await publisher.publish("channel", "Hello!"); - -// Close connections -await redis.disconnect(); -``` +- Create a SQL connection with `new SQL(connectionString)` and execute parameterized queries with the connection's tagged template. +- Use `redis` for the default client or `new RedisClient(url)` for an explicit connection; close explicit clients with `.close()`. +- Use `bun:sqlite` for embedded SQLite and `Bun.s3` / `S3Client` for S3-compatible object storage. +- Parse database and cache values at the boundary; generated TypeScript types are not runtime validation. -## Best Practices Summary +See [references/data-networking.md](references/data-networking.md) for current examples and lifecycle details. -1. **File I/O**: Use `Bun.file()` and `Bun.write()` instead of `fs` -2. **Environment**: Use `Bun.env` instead of `process.env` -3. **Processes**: Use `Bun.spawn()` instead of `child_process` -4. **Paths**: Use `import.meta.dir` and `import.meta.path` instead of `__dirname` -5. **Crypto**: Use `Bun.password`, `Bun.hash()`, or Web Crypto API -6. **Binary**: Use `Uint8Array` instead of `Buffer` -7. **Modules**: Use ESM imports with `.ts` extensions -8. **Testing**: Use `bun:test` for testing -9. **Databases**: Use `Bun.SQL` for PostgreSQL/MySQL/SQLite (unified API, zero dependencies) -10. **Redis**: Use built-in `Bun.redis` client (no ioredis needed) -11. **HTTP**: Use `Bun.serve()` for servers (145k req/s) +## Security rules -## Performance Benefits +- Use `Bun.password` only for password hashing and verification. Its supported password algorithms are Argon2 and bcrypt; do not document scrypt as supported by this API. +- Use Web Crypto or `node:crypto` for cryptographic digests and signatures. `Bun.hash` is non-cryptographic. +- Bind CSRF tokens to a session identifier and verify them at mutation boundaries. +- Treat the secrets API and newly introduced protocol support according to their documented stability level. +- Never log secrets, full environment objects, authorization headers, or raw database URLs. -Bun 1.3 delivers exceptional performance: +## Tests and scheduled work -- **8x faster startup** than Node.js -- **145k requests/second** HTTP throughput (vs Node.js 65k req/s) -- **Written in Zig**: Close to metal performance -- **JavaScriptCore engine**: Optimized for modern JavaScript -- **Zero-copy operations**: Minimizes memory allocations -- **Built-in database clients**: Faster than external packages (pg, mysql2, better-sqlite3) -- **No external dependencies**: Database and Redis clients built-in +Use the repository's existing test command. Current Bun test capabilities include process isolation, parallel execution, sharding, and changed-file selection; choose flags deliberately so tests remain deterministic. Do not mask missing build artifacts with skipped tests. -## When to Ask for Help +Cron expressions schedule callbacks inside a running Bun process. OS cron support creates operating-system schedules. Neither is a durable distributed scheduler; use the system's established orchestration for retryable, observable production workflows. -Ask the user for clarification when: +## Review checklist -- Legacy Node.js code needs migration strategy -- Performance requirements are critical -- Third-party libraries depend on Node.js APIs -- The project must support both Bun and Node.js runtimes +- Verify the project's pinned Bun version before using a recent API. +- Keep CWD-relative and module-relative paths distinct. +- Check every subprocess exit code and preserve stderr in failures. +- Validate environment, HTTP, file, cache, and database input. +- Use password and cryptographic hash APIs for their intended purposes. +- Close explicit SQL, Redis, file, worker, and server resources when their lifecycle ends. +- Mark experimental APIs and performance figures as conditional, not universal promises. +- Prefer focused Bun-native APIs without banning supported Web or Node APIs. diff --git a/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/data-networking.md b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/data-networking.md new file mode 100644 index 0000000000..ffcbbb1d98 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/data-networking.md @@ -0,0 +1,96 @@ +# Bun data and networking APIs + +Read this when building Bun HTTP or WebSocket services, using SQL, Redis, SQLite, or S3, or implementing cookies, CSRF, secrets, and scheduled work. + +## HTTP, WebSocket, and fetch + +`Bun.serve()` is Bun's native server API. Use `routes` for static or parameterized routes and `fetch` for the fallback handler. WebSocket upgrades attach server-defined data to the socket; validate it before use. + +Outbound HTTP uses Web `fetch()`. Check `response.ok` or the expected status before parsing a success body. + +HTTP/2 and HTTP/3 server support introduced in Bun 1.3.14 is experimental. Verify protocol negotiation and the real proxy/client route before enabling it in production. + +## SQL + +Create a connection, then use that connection as the tagged-template function: + +```typescript +import { SQL } from "bun"; +import { z } from "zod"; + +const Environment = z.object({ DATABASE_URL: z.string().url() }); +const { DATABASE_URL } = Environment.parse(Bun.env); + +const UserRow = z.object({ id: z.number().int(), email: z.string().email() }); +const sql = new SQL(DATABASE_URL); + +try { + const rows = await sql`SELECT id, email FROM users WHERE active = ${true}`; + const users = z.array(UserRow).parse(rows); + console.log(users.length); +} finally { + await sql.close(); +} +``` + +Validate the connection URL before constructing the client. `new SQL(undefined)` does not throw — Bun falls back to implicit connection defaults — so a missing required `DATABASE_URL` silently connects to the wrong database instead of failing fast. + +Do not call `Bun.SQL` itself as a query tag. Do not interpolate SQL fragments or identifiers as values. Use the documented helpers for bulk inserts and transactions rather than inventing a `prepare()` contract. + +## Redis + +Use the default `redis` export when one shared default connection matches the application. Use `new RedisClient(url)` for an explicit endpoint and close it with `.close()`. + +```typescript +import { RedisClient } from "bun"; +import { z } from "zod"; + +const Environment = z.object({ REDIS_URL: z.string().url() }); +const { REDIS_URL } = Environment.parse(Bun.env); + +const client = new RedisClient(REDIS_URL); +try { + await client.set("health:last", new Date().toISOString()); +} finally { + client.close(); +} +``` + +Validate the URL before constructing an explicit-endpoint client, same as `SQL` +above: `new RedisClient(undefined)` does not throw, it silently falls through +to Bun's default Redis connection instead of failing on a missing required +`REDIS_URL`. + +Do not document `.connect(url)` or `.disconnect()` as the URL and lifecycle API. + +## SQLite and S3 + +Use `bun:sqlite` for embedded databases and finalize statements or close databases when their lifecycle ends. Use `Bun.s3` for the environment-configured S3 client or construct `S3Client` for an explicit S3-compatible endpoint. Bound uploads and validate object keys, content types, and access policy. + +## Cookies and CSRF + +Bun exposes cookie parsing and serialization helpers. Configure `HttpOnly`, `Secure`, and `SameSite` according to the application's threat model. + +CSRF token generation and verification must bind the token to session-specific data. A token that is not tied to the authenticated session can be replayed across users. + +## Secrets + +Bun's secrets API is experimental. Treat it as platform credential storage, not as a replacement for the deployed secret-management system, access controls, rotation, or audit logging. + +## Cron + +The cron API schedules callbacks in a running Bun process; OS cron support installs native schedules. Use them for work whose lifecycle matches that process or host. Use the repository's durable scheduler for distributed retries, history, and operator visibility. + +## Primary documentation + +- [HTTP server](https://bun.sh/docs/runtime/http/server) +- [WebSockets](https://bun.sh/docs/runtime/http/websockets) +- [Fetch](https://bun.sh/docs/api/fetch) +- [SQL](https://bun.sh/docs/runtime/sql) +- [Redis](https://bun.sh/docs/runtime/redis) +- [SQLite](https://bun.sh/docs/runtime/sqlite) +- [S3](https://bun.sh/docs/runtime/s3) +- [Secrets](https://bun.sh/docs/runtime/secrets) +- [Cookies](https://bun.sh/docs/runtime/cookies) +- [CSRF](https://bun.sh/docs/runtime/csrf) +- [Cron](https://bun.sh/docs/runtime/cron) diff --git a/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/io-process-modules.md b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/io-process-modules.md new file mode 100644 index 0000000000..7a56a22af0 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/io-process-modules.md @@ -0,0 +1,70 @@ +# Bun I/O, processes, and modules + +Read this when implementing file or binary I/O, subprocesses, shell pipelines, environment handling, modules, streams, hashing, or workers in Bun. + +## File I/O + +`Bun.file(path)` creates a lazy `BunFile`; read it with `.text()`, `.json()`, `.arrayBuffer()`, `.bytes()`, or `.stream()`. `Bun.write(destination, data)` accepts strings, blobs, buffers, typed arrays, responses, and files. + +Relative string paths use the process working directory. Use `import.meta.dir` plus `node:path` for source-relative assets. + +Use `node:fs/promises` for directory traversal, permissions, ownership, symbolic links, and operations not represented by Bun's whole-file APIs. + +## Subprocesses and Bun Shell + +`Bun.spawn([program, ...arguments])` avoids shell parsing. Always await `.exited`; a readable stdout does not imply success. + +Bun Shell provides cross-platform pipelines and throws on a non-zero exit by default. Interpolated strings are escaped, but option injection is still possible when a called program interprets a value beginning with `-`. Use `--` when the program supports it, or validate the argument domain. + +```typescript +import { $ } from "bun"; + +const revision = "HEAD"; +const result = await $`git rev-parse --verify ${revision}`.text(); +``` + +Do not add a catch handler that converts command failure into an empty result. + +## Environment + +`Bun.env`, `process.env`, and `import.meta.env` are aliases for environment access. Bun loads supported `.env` files automatically according to its documented precedence. TypeScript autocomplete does not validate deployment values; use a runtime schema. + +Pass a complete environment intentionally to subprocesses. Spreading `Bun.env` forwards secrets, so prefer the minimum set when launching untrusted or third-party programs. + +## Modules + +Bun supports ESM and CommonJS. ESM is the clearer default for new code, but migration should follow package and consumer contracts. Extensionless imports and TypeScript extensions are supported by Bun; the repository's compiler and publishing setup decides which convention is correct. + +Use the compatibility table for Node APIs. Bun's own documentation explicitly recommends `node:fs` and other Node modules for operations its native APIs do not cover. + +## Binary data and streams + +Web `ReadableStream`, `ArrayBuffer`, `Uint8Array`, `Blob`, `Request`, and `Response` are portable defaults. `Buffer` is supported and is appropriate at Node-compatible boundaries. + +`Bun.peek(promise)` inspects a promise's current state synchronously. It is not a stream peeking API and must not be described as reading a stream without consuming it. + +## Hashing and passwords + +- `Bun.password.hash()` and `.verify()` support password hashing with Argon2 or bcrypt. +- `Bun.hash()` provides fast non-cryptographic hashes and may return a 64-bit `bigint` depending on the selected algorithm. +- Web Crypto and `node:crypto` provide cryptographic hashes, signatures, ciphers, and key management. + +Never substitute a general fast hash for password hashing or a cryptographic digest. + +## Workers + +Workers follow Web Worker concepts with Bun-specific support. Worker termination behavior introduced during Bun 1.3 remains documented as experimental; test cleanup and termination on the pinned runtime before depending on it. + +## Primary documentation + +- [File I/O](https://bun.sh/docs/runtime/file-io) +- [Environment variables](https://bun.sh/docs/runtime/environment-variables) +- [Child processes](https://bun.sh/docs/runtime/child-process) +- [Bun Shell](https://bun.sh/docs/runtime/shell) +- [Module resolution](https://bun.sh/docs/runtime/modules) +- [Node.js compatibility](https://bun.sh/docs/runtime/nodejs-compat) +- [Binary data](https://bun.sh/docs/runtime/binary-data) +- [Streams](https://bun.sh/docs/runtime/streams) +- [Hashing](https://bun.sh/docs/runtime/hashing) +- [Utilities](https://bun.sh/docs/runtime/utils) +- [Workers](https://bun.sh/docs/runtime/workers) diff --git a/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/release-notes-1.3.md b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/release-notes-1.3.md new file mode 100644 index 0000000000..dcbfc523e1 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/bun-runtime-best-practices/references/release-notes-1.3.md @@ -0,0 +1,65 @@ +# Bun 1.3 release lifecycle + +Read this when upgrading Bun, adopting an API added during the 1.3 line, or checking whether an existing release claim is still current. + +## Verified status + +As of 2026-08-03, the current stable release is Bun 1.3.14, released 2026-05-13. Confirm the actual project pin with `bun --version`, `mise`, or the repository toolchain configuration before changing code. + +## Notable 1.3 changes + +| Release | Notable additions relevant to this skill | +| --- | --- | +| 1.3 | Unified SQL support, Redis client, full-stack development, isolated installs, executable improvements | +| 1.3.10 | REPL improvements, single-HTML-file applications, decorator support, Windows ARM progress | +| 1.3.11 | OS cron integration and ANSI utilities | +| 1.3.12 | WebView and Markdown APIs, cron additions, async-stack improvements | +| 1.3.13 | Test isolation, parallel execution, sharding, and changed-file selection | +| 1.3.14 | `Bun.Image`, isolated-linker warm-install improvements, experimental HTTP/2 and HTTP/3 | + +Release posts describe capabilities, not universal performance guarantees. Preserve benchmark context, platform, workload, and version whenever quoting a number. Bun's current runtime overview reports a Linux Hello World comparison; it does not justify a blanket “8x startup” or fixed requests-per-second claim for every application. + +## Adoption procedure + +1. Read every release note between the project's current pin and the target. +2. Check the API reference for stability markers and lifecycle semantics. +3. Update the pinned toolchain and lockfile through the repository's normal version workflow. +4. Run focused build, typecheck, test, and lint tasks. +5. Exercise runtime-specific behavior on the deployed operating system and architecture. +6. Keep experimental features opt-in until their failure and rollback path is proven. + +## Research ledger + +The following 31 official pages were fetched and inspected for this refresh: + +1. [Runtime overview](https://bun.sh/docs/runtime) +2. [File I/O](https://bun.sh/docs/runtime/file-io) +3. [Environment variables](https://bun.sh/docs/runtime/environment-variables) +4. [Child processes](https://bun.sh/docs/runtime/child-process) +5. [Bun Shell](https://bun.sh/docs/runtime/shell) +6. [Module resolution](https://bun.sh/docs/runtime/modules) +7. [Node.js compatibility](https://bun.sh/docs/runtime/nodejs-compat) +8. [Binary data](https://bun.sh/docs/runtime/binary-data) +9. [Streams](https://bun.sh/docs/runtime/streams) +10. [Hashing](https://bun.sh/docs/runtime/hashing) +11. [Utilities](https://bun.sh/docs/runtime/utils) +12. [HTTP server](https://bun.sh/docs/runtime/http/server) +13. [WebSockets](https://bun.sh/docs/runtime/http/websockets) +14. [Fetch](https://bun.sh/docs/api/fetch) +15. [SQL](https://bun.sh/docs/runtime/sql) +16. [Redis](https://bun.sh/docs/runtime/redis) +17. [SQLite](https://bun.sh/docs/runtime/sqlite) +18. [S3](https://bun.sh/docs/runtime/s3) +19. [Secrets](https://bun.sh/docs/runtime/secrets) +20. [Cookies](https://bun.sh/docs/runtime/cookies) +21. [CSRF](https://bun.sh/docs/runtime/csrf) +22. [Cron](https://bun.sh/docs/runtime/cron) +23. [Workers](https://bun.sh/docs/runtime/workers) +24. [Web APIs](https://bun.sh/docs/runtime/web-apis) +25. [Bun 1.3](https://bun.com/blog/bun-v1.3) +26. [Bun 1.3.14](https://bun.com/blog/bun-v1.3.14) +27. [Bun 1.3.13](https://bun.com/blog/bun-v1.3.13) +28. [Bun 1.3.12](https://bun.com/blog/bun-v1.3.12) +29. [Bun 1.3.11](https://bun.com/blog/bun-v1.3.11) +30. [Bun 1.3.10](https://bun.com/blog/bun-v1.3.10) +31. [Installation](https://bun.sh/docs/installation) diff --git a/packages/dotfiles/dot_agents/skills/git-helper/SKILL.md b/packages/dotfiles/dot_agents/skills/git-helper/SKILL.md index 89b5d3fc20..60436134f9 100644 --- a/packages/dotfiles/dot_agents/skills/git-helper/SKILL.md +++ b/packages/dotfiles/dot_agents/skills/git-helper/SKILL.md @@ -1,274 +1,174 @@ --- name: git-helper -description: | - Git version control best practices, advanced operations, and modern features - When user works with git, mentions git commands, branching, rebasing, merging, or git troubleshooting +description: Safe, current Git operations for inspection, commits, history, recovery, merging, rebasing, worktrees, configuration, maintenance, and repository troubleshooting. Use for Git commands and diagnosis; load the repository's owning stack or branching skill before branch or PR mutations. --- -# Git Helper Agent +# Git Helper -> **Branch & PR management in `shepherdjerred/monorepo` uses git-spice — every PR is a stacked PR.** Load the `git-spice-helper` skill first (it's authoritative) before creating branches, stacking, restacking, or opening PRs; create/update PRs with `git-spice branch/stack submit`. The `gh pr create` and manual-`git rebase` examples below are the generic fallback for repos without git-spice. +Inspect first, preserve user work, and use the repository's owning branch workflow for mutations. This skill covers Git itself; a stack or PR skill remains authoritative for branch creation, restacking, publishing, and synchronization. -## What's New in Git (2024-2026) +## Branch workflow ownership -### Git 2.52 (2025) +In `shepherdjerred/monorepo`, every PR — human or agent, new or existing — is a git-spice stack. Load `git-spice-helper` first; it is authoritative for branch creation, restacking, and publishing. -- **`git last-modified`**: New command to determine which commit most recently modified each file in a directory (5.5x faster than ls-tree + log) -- **`git refs list` / `git refs exists`**: Consolidated reference operations -- **`git repo`**: Experimental command for retrieving repository information -- **`git maintenance` geometric task**: Alternative to all-into-one repacks -- **`git sparse-checkout clean`**: Recover from difficult checkout state transitions -- **Default branch change**: Git 3.0 will default to "main" instead of "master" -- **Rust integration**: Optional Rust code for variable-width integer operations -- **`git describe` 30% faster**, `git log -L` faster for merge commits +Load `worktree-workflow` before creating an isolated worktree. Do not use a bare `gh pr create` or hand-written rebase in place of the owning stack tool. -### Git 2.51 (2025) +## Current baseline -- **Stash interchange format**: `git stash export` and `git stash import` subcommands for cross-machine stash migration -- **`--path-walk` repacking**: Significantly smaller pack files by emitting all objects from a given path simultaneously -- **Cruft-free multi-pack indexes**: 38% smaller MIDXs, 35% faster writes, 5% better read performance at GitHub -- **`git switch` / `git restore`**: No longer experimental after six years -- **`git whatchanged`**: Marked for removal in Git 3.0 +Verified against Git 2.55.0 on 2026-08-03: -### Git 2.50 (2025) - -- **ORT merge engine**: Completely replaced the older recursive merge engine -- **`git merge-tree --quiet`**: Check mergeability without writing objects -- **`git maintenance` new tasks**: `worktree-prune`, `rerere-gc`, `reflog-expire` -- **Incremental multi-pack bitmap support**: Fast reachability bitmaps for extremely large repos -- **`git cat-file` object filtering**: Filter objects by type using partial clone mechanisms -- **Bundle URI**: Faster fill-in fetches by advertising all known references from bundles - -### Git 2.49 (2025) - -- **Name-hash v2**: Dramatically improved packing (fluentui: 96s to 34s, 439 MiB to 160 MiB) -- **`git backfill`**: Batch-fault missing blobs in `--filter=blob:none` partial clones -- **zlib-ng support**: ~25% speed improvement for compression -- **`git clone --revision`**: Clone specific commits without branch/tag references -- **`git gc --expire-to`**: Manage pruned objects by moving them elsewhere -- **First Rust code integration** via libgit-sys and libgit crates - -### Git 2.48 (2025) - -- **Faster checksums**: 10-13% performance improvement in serving fetches/clones using non-collision-detecting SHA-1 for trailing checksums -- **`range-diff --remerge-diff`**: Review merge conflict resolutions during rebase -- **Remote HEAD tracking**: Fetch auto-updates `refs/remotes/origin/HEAD` if missing; configure `remote.origin.followRemoteHead` -- **Meson build system**: Alternative build system alongside Make/CMake/Autoconf -- **Memory leak elimination**: Entire test suite passes with leak checking -- **`BreakingChanges.txt`**: Documents anticipated deprecations for future versions - -### Git 2.47 (2024) +```bash +git --version +``` -- **Incremental multi-pack indexes**: Layered MIDX chains for faster object addition -- **Separate hash function for checksums**: 10-13% serving performance improvement +Recent versions added stable `switch` and `restore`, stash export/import, `refs` commands, `last-modified`, `backfill`, `url-parse`, `format-rev`, maintenance improvements, and additional partial-clone and pack tooling. `repo`, `last-modified`, `history`, `format-rev`, and `backfill` should be treated according to their documented experimental or evolving status. -### Git 2.46 (2024) +Git's future-breaking-changes document is the source of truth for planned removals. Do not state a Git 3 release date or default change as certain until the project publishes it. -- **Pseudo-merge bitmaps**: Faster reachability queries -- **`git config list` / `git config get`**: New sub-command interface -- **Reftable migration**: `git refs migrate --ref-format=reftable` for faster reference operations -- **Enhanced credential helpers**: authtype/credential fields, multi-round auth (NTLM, Kerberos) +Read [references/releases.md](references/releases.md) when adopting a recent command or upgrading Git. Read [references/history-and-recovery.md](references/history-and-recovery.md) for rebase, range-diff, bisect, reflog, stash, sparse checkout, bundles, and recovery. Read [references/configuration-and-maintenance.md](references/configuration-and-maintenance.md) for config, hooks, credentials, maintenance, refs, and repository health. -### Git 2.45 (2024) +## Start read-only -- **Reftable backend**: New reference storage with faster lookups, reads, and writes +Useful inspection commands: -### Git 2.44 (2024) +```bash +git status --short --branch +git diff --stat +git diff +git diff --cached +git log --oneline --graph --decorate --all +git branch --verbose --verbose +git remote --verbose +git reflog +git worktree list --porcelain +git config --list --show-origin --show-scope +``` -- **Multi-pack reuse optimization**: Faster fetches and clones -- **`builtin_objectmode` pathspec**: Filter paths by mode +Use `git show :`, `git log -- `, and `git diff ...` to inspect another branch without switching the working tree. -## Overview +## Preserve concurrent work -Git is the distributed version control system used by virtually all modern software projects. This skill covers general Git best practices, advanced operations, branching strategies, and modern features. For worktree-specific workflows (parallel development, AI agent isolation), see the `worktree-workflow` skill instead. +- Never discard, stash, reset, restore, or switch away from unexpected work just to investigate. +- Treat every existing modification as user-owned unless provenance proves otherwise. +- Keep all agents read-only on history unless explicitly assigned an isolated branch mutation. +- Verify the current branch and recent movements before pushing: -## CLI Commands +```bash +git status --short --branch +git reflog -n 10 +``` -### Auto-Approved (Safe, Read-Only) +- Stage whole files by explicit path. Do not use interactive, hunk-level, current-directory, or repository-wide staging in agent workflows. -These commands are safe to run without user confirmation: +## Intentional commits -- `git status` - Working tree status -- `git log` - Commit history (with `--oneline`, `--graph`, `--all`, `--since`, `--author`) -- `git diff` - Show changes (staged: `--cached`, between branches, specific files) -- `git branch` - List branches (`-a` for all, `-v` for verbose, `--merged`, `--no-merged`) -- `git tag` - List tags (`-l "v1.*"` for patterns) -- `git show` - Show commit details -- `git remote -v` - List remotes -- `git stash list` - List stashed changes -- `git reflog` - Reference log history -- `git blame` - Line-by-line authorship -- `git shortlog` - Summarized log output -- `git config --list` - Show configuration -- `git rev-parse` - Parse revision/path info -- `git ls-files` - Show tracked files -- `git describe` - Human-readable name from commit +```bash +git add path/to/first path/to/second +git diff --cached --check +git diff --cached +git commit -m "type(scope): concise description" +``` -### Common Operations +If `core.fsmonitor` makes a changed file appear unstaged, compare the working and index object IDs and disable fsmonitor for the affected command: ```bash -# Stage changes -git add # Stage specific file -git add -p # Interactive staging (hunk-by-hunk) -git add -N # Track file without staging content - -# Commit -git commit -m "message" # Commit with message -git commit --amend # Amend last commit (message or content) -git commit --fixup= # Create fixup commit for later autosquash -git commit --allow-empty # Empty commit (useful for CI triggers) - -# Branch operations -git branch # Create branch -git branch -d # Delete merged branch -git branch -D # Force delete branch -git branch -m # Rename branch -git switch # Switch branch (preferred over checkout) -git switch -c # Create and switch - -# Remote operations -git fetch # Fetch from default remote -git fetch --all --prune # Fetch all remotes and prune stale tracking -git pull --rebase # Pull with rebase instead of merge -git push -u origin # Push and set upstream - -# Undoing changes -git restore # Discard working tree changes (preferred over checkout --) -git restore --staged # Unstage file -git reset --soft HEAD~1 # Undo last commit, keep changes staged -git reset --mixed HEAD~1 # Undo last commit, keep changes unstaged -git revert # Create a new commit that undoes a previous commit +git hash-object path/to/file +git rev-parse :path/to/file +git -c core.fsmonitor=false add path/to/file ``` -### Log and History +Do not amend or force-push a commit another person may have based work on without explicit authorization. + +## Compare the right ranges ```bash -# Useful log formats -git log --oneline --graph --all --decorate -git log --since="2 weeks ago" --author="name" -git log --follow -p -- # Full history of a file including renames -git log -S "search_string" # Find commits that add/remove a string (pickaxe) -git log -G "regex_pattern" # Find commits matching regex in diffs -git log --first-parent # Follow only first parent (clean merge history) -git log --diff-filter=D -- # Find when files were deleted - -# Comparing -git diff main..feature # Changes in feature not in main -git diff main...feature # Changes since feature branched from main -git diff --stat # Summary of changes -git diff --name-only # Just filenames -git diff --word-diff # Word-level diff -``` +# Tip-to-tip difference +git diff main..feature -## Essential Workflows +# Changes introduced since the merge base +git diff main...feature -### Creating Good Commits +# Patch-series comparison before and after a rebase +git range-diff old-base..old-tip new-base..new-tip +``` -1. **Atomic commits**: Each commit should represent one logical change -2. **Write clear messages**: Follow conventional commit format +`range-diff` compares two commit ranges. Do not use the invalid single-range shorthand found in older copies of this skill. - ``` - type(scope): short description +## Mergeability without changing the checkout - Longer explanation if needed. Wrap at 72 characters. +Use the repository's independent merge oracle when readiness matters: - Refs: #123 - ``` +```bash +git merge-tree --write-tree +``` - Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`, `build`, `revert` +Treat the exit status as the merge result: 0 is a clean merge, 1 is a real conflict. `--quiet` (Git 2.50+) additionally suppresses most output and object creation when the exit status is all that's needed, but isn't available on older Git — omit it rather than assume it exists, or gate it behind a version check. Do not replace this check with a checkout or an untrusted hosted mergeability field. -3. **Stage intentionally**: Review staged changes with `git diff --cached` before committing -4. **Verify before committing**: Run `git diff --cached` to review staged changes +## Undo and recovery -### Monorepo commit-msg convention (this repo) +Choose the least destructive operation: -Lefthook enforces `type(scope): description` with a scope that is a -`packages/` directory name or one of `root` / `practice` / `archive` -(`monorepo`, `repo`, and `ALL` are invalid). Use `root` for sweeping -cross-package commits (for example, `feat(root): …`). Types: `feat fix chore ci -docs refactor test perf build style revert misc`. The pre-commit hook runs -changed-file safety checks, including staged-file Prettier and Gitleaks; focused -package tests remain the developer's responsibility and the exhaustive gate -runs in Buildkite. +| Goal | Operation | Boundary | +| --- | --- | --- | +| Undo a published commit | `git revert ` | Adds a new inverse commit | +| Unstage a path | `git restore --staged ` | Keeps working-tree content | +| Recover a lost commit | inspect `git reflog`, then create a branch | Preserves the recovered object | +| Move a private branch while keeping changes | `git reset --soft` or `--mixed` after inspection | Rewrites only local branch position | +| Discard work | destructive reset/restore | Requires exact target resolution and explicit user intent | -### Syncing with Upstream +Never present `git reset --hard HEAD@{n}` as a routine recovery recipe. First inspect the reflog entry and preserve it with a branch: ```bash -# Rebase approach (linear history) -git fetch origin -git rebase origin/main - -# If conflicts arise during rebase -git status # See conflicted files -# ... resolve conflicts ... -git add -git rebase --continue # Continue after resolving -git rebase --abort # Abort and return to pre-rebase state - -# Merge approach (preserves branch topology) -git fetch origin -git merge origin/main +git reflog +git show +git branch recovery/ ``` -### Cleaning Up Before PR +## Rebasing and pushing -```bash -# Interactive rebase to clean commit history -git rebase -i origin/main +Use the owning stack tool for repositories with stacked PR workflows. In a generic repository, inspect the branch, fetch explicitly, and compare the rewritten series with `range-diff`. -# With autosquash (processes fixup!/squash! commits automatically) -git rebase -i --autosquash origin/main +Rebase does not always require a force push: an unpublished local branch can be pushed normally. When a published branch is intentionally rewritten, background fetches can invalidate the protection expected from plain `--force-with-lease`; verify the remote tip and use an explicit expected object when safety matters. -# Enable autosquash globally -git config --global rebase.autoSquash true -``` +Never force-push main, release branches, shared branches, or work another person may have based on without current authorization. -### Recovering from Mistakes +## Hooks and credentials -```bash -# Find lost commits or states -git reflog # Show recent HEAD movements -git reflog show # Show branch-specific reflog - -# Recover after bad rebase/reset -git reset --hard HEAD@{2} # Reset to state 2 moves ago +Repository hooks are executable policy. Inspect the configured hook path and hook source before relying on them: -# Recover deleted branch -git reflog | grep "checkout.*branch-name" -git branch # Recreate from found SHA +```bash +git config --get core.hooksPath +ls -la "$(git rev-parse --git-path hooks)" ``` -## Safety Rules (Agent) +`git config --get`/`--list` work on every supported Git version; the newer `git config get`/`git config list` subcommand syntax does not exist before Git 2.46. `git hook list ` (added in Git 2.54, and it requires a hook name — it does not list every hook) can supplement this once the installed version is confirmed; do not rely on it as the portable default. -- **Never destroy uncommitted work to investigate.** Don't `git stash`, `git checkout -- `, `git restore`, or switch branches just to test something (e.g. checking whether failures are pre-existing) — these discard the user's or a concurrent agent's in-progress changes. Note the observation and move on, or ask the user. -- **Force-push only branches you own.** Never force-push `main`, release branches, or any branch others have pushed to or based work on without explicit confirmation at the moment of execution. Force-pushing a feature branch Claude created and owns this session is fine — use `--force-with-lease`, never `--force`. If unsure who else touched it, treat it as shared and ask first. -- **Subagents must stay read-only on history.** When spawning Explore/Plan/general-purpose agents, explicitly forbid `git checkout `, `git switch`, `git stash`, `git reset`, or anything that moves HEAD or the working tree — they can silently leave the user on a stale commit. Tell them to use `git show :`, `git log `, and `git diff ..` for cross-branch inspection. Verify HEAD with `git reflog` before any push after a subagent ran. -- **Never revert changes you didn't make.** Unexpected file modifications may be a concurrent human or agent, not a rogue linter — reverting them causes build failures and lost work. If a change looks intentional (new types, refactored functions, new files) rather than formatting-only, leave it; ask before reverting anything. -- **Whole-file staging only.** Split work into multiple commits with plain `git add ...` grouped by file or concern — never `git add -p` / `-i` / `--patch` or any interactive hunk staging (it is opaque and hard to review). This overrides the `git add -p` suggestions elsewhere in this skill; if one file truly mixes two unrelated concerns, ask rather than reaching for `-p`. -- **`core.fsmonitor` can silently drop `git add`.** The user's git config enables `core.fsmonitor` + `core.untrackedCache`; a stale cache can make `git status` report a clean tree and `git add` no-op after tool-driven edits. Detect it when `git hash-object ` ≠ `git rev-parse :`. Work around by prefixing commands with `-c core.fsmonitor=false` (e.g. `git -c core.fsmonitor=false add `). +Use credential helpers or `GIT_ASKPASS`; never embed tokens in remotes, configuration, logs, or files. Treat hook input and filenames as untrusted shell data. -## When to Ask for Help +## Maintenance and repository health -Ask the user for clarification when: +Use `git maintenance` tasks documented for the installed version. `geometric-repack` is a maintenance task; `geometric` is a strategy, not a task name. Use `git maintenance is-needed` where supported to avoid unnecessary work. -- Choosing between rebase vs merge strategy for their team -- Whether to force push after rebase (check if others use the branch) -- How to handle complex merge conflicts -- Repository-specific branching conventions -- Whether to squash commits before merging +For diagnosis: -## References - -- [Git Official Documentation](https://git-scm.com/doc) -- [Git Release Notes](https://github.com/git/git/tree/master/Documentation/RelNotes) -- [GitHub Blog - Git Updates](https://github.blog/open-source/git/) -- [Pro Git Book](https://git-scm.com/book/en/v2) -- [Conventional Commits](https://www.conventionalcommits.org/) +```bash +git fsck --full +git count-objects -vH +git gc --auto +git reflog expire --dry-run --all +``` -### Skill References +Do not change reflog expiry, run aggressive collection, or delete unreachable objects until recovery requirements and repository ownership are clear. -For detailed coverage of specific topics, see: +## Review checklist -- `references/advanced-operations.md` - Interactive rebase, bisect, reflog, cherry-pick, filter-repo, stash, rerere, blame, notes, bundle, sparse-checkout -- `references/branching-workflows.md` - Branching strategies, commit conventions, merge vs rebase, signed commits, tags, release workflows -- `references/config-hooks.md` - Git configuration, conditional includes, aliases, hooks, maintenance, scalar, performance, .gitattributes, .gitignore +- Load and obey the repository's owning stack/branch skill. +- Inspect status, diff, branch, and worktrees before mutation. +- Keep user-owned and concurrent work intact. +- Stage explicit whole-file paths and review the staged diff. +- Use independent merge-tree evidence for mergeability claims. +- Compare rewritten series with a valid two-range `range-diff`. +- Resolve exact reflog/reset/restore targets before destructive actions. +- Verify remote tips before any authorized history rewrite. +- Treat experimental commands and future Git changes as conditional. +- Check hook configuration and credential exposure before publishing. diff --git a/packages/dotfiles/dot_agents/skills/git-helper/references/advanced-operations.md b/packages/dotfiles/dot_agents/skills/git-helper/references/advanced-operations.md deleted file mode 100644 index e2fdf488f6..0000000000 --- a/packages/dotfiles/dot_agents/skills/git-helper/references/advanced-operations.md +++ /dev/null @@ -1,645 +0,0 @@ -# Advanced Git Operations - -## Interactive Rebase - -Interactive rebase (`git rebase -i`) rewrites commit history by allowing you to reorder, edit, squash, fixup, or drop commits. - -### Basic Usage - -```bash -# Rebase last N commits -git rebase -i HEAD~5 - -# Rebase onto a branch -git rebase -i origin/main - -# Rebase with autosquash (auto-processes fixup!/squash! commits) -git rebase -i --autosquash origin/main -``` - -### Rebase Commands - -In the interactive editor, each commit is prefixed with a command: - -| Command | Short | Effect | -| -------- | ----- | ----------------------------------------------- | -| `pick` | `p` | Use commit as-is | -| `reword` | `r` | Use commit but edit message | -| `edit` | `e` | Pause at commit for amending | -| `squash` | `s` | Meld into previous commit, combine messages | -| `fixup` | `f` | Meld into previous commit, discard this message | -| `drop` | `d` | Remove commit entirely | -| `exec` | `x` | Run shell command after commit | -| `break` | `b` | Stop here (continue with `--continue`) | - -### Fixup and Autosquash Workflow - -The fixup/autosquash pattern lets you create correction commits that automatically fold into their targets: - -```bash -# Make original commit -git commit -m "feat: add user validation" - -# Later, fix something in that commit -git add fixed-file.ts -git commit --fixup= # Creates "fixup! feat: add user validation" - -# Or create an amend fixup (also updates commit message) -git commit --fixup=amend: - -# When ready, autosquash folds fixups into their targets -git rebase -i --autosquash origin/main - -# Enable autosquash globally so you never forget -git config --global rebase.autoSquash true -``` - -### Rebase with Merge Commits - -```bash -# Preserve merge commits during rebase -git rebase -i --rebase-merges origin/main - -# Review merge conflict resolutions with range-diff (Git 2.48+) -git range-diff --remerge-diff main..feature -``` - -### Safety During Rebase - -```bash -# Before rebase, save a reference -git branch backup-branch - -# If rebase goes wrong, abort -git rebase --abort - -# After rebase, compare results -git diff backup-branch..HEAD # Should be empty if only history changed -git log --oneline backup-branch..HEAD # New commits -git log --oneline HEAD..backup-branch # Old commits (should be replaced) -``` - -## Git Bisect - -Binary search through commit history to find which commit introduced a bug. - -### Manual Bisect - -```bash -# Start bisecting -git bisect start - -# Mark current state as bad -git bisect bad - -# Mark a known good commit -git bisect good v1.0.0 # or a specific SHA - -# Git checks out a middle commit - test it, then: -git bisect good # If this commit works -git bisect bad # If this commit has the bug - -# Repeat until Git identifies the first bad commit -# When done: -git bisect reset # Return to original state -``` - -### Automated Bisect - -```bash -# Automate with a test script (exit 0 = good, exit 1 = bad) -git bisect start HEAD v1.0.0 -git bisect run npm test - -# Use any command - exits 0 for good, non-zero for bad -git bisect run make test -git bisect run ./check-bug.sh - -# Skip untestable commits (e.g., broken build) -git bisect skip - -# Exit code 125 means "skip this commit" in automated bisect -git bisect run sh -c 'make || exit 125; ./test.sh' -``` - -### Bisect with Specific Paths - -```bash -# Only consider commits that touched specific paths -git bisect start -- src/auth/ -``` - -### Recovery and Logging - -```bash -# View bisect log -git bisect log - -# Replay a bisect session -git bisect log > bisect-log.txt -git bisect replay bisect-log.txt - -# If you marked a commit incorrectly -git bisect log > bisect-log.txt -# Edit the file to remove incorrect entries -git bisect reset -git bisect replay bisect-log.txt -``` - -## Reflog - -The reflog records every change to HEAD and branch tips locally. It is your safety net for recovering from mistakes. - -### Viewing Reflog - -```bash -# Show HEAD reflog (default) -git reflog - -# Show reflog for a specific branch -git reflog show feature/auth - -# Show reflog with timestamps -git reflog --date=iso - -# Show reflog for stash -git reflog show stash - -# List all refs with reflogs -git reflog list -``` - -### Recovery Patterns - -```bash -# Recover from bad rebase -git reflog -# Find entry before the rebase, e.g., HEAD@{5} -git reset --hard HEAD@{5} - -# Recover deleted branch -git reflog | grep "checkout.*deleted-branch" -# Find the SHA, then: -git branch recovered-branch - -# Recover lost commit after reset -git reflog -git cherry-pick - -# Recover dropped stash -git fsck --unreachable | grep commit -git show # Inspect to find your stash -git stash apply # Apply the lost stash -``` - -### Reflog Expiration - -```bash -# Reflog entries expire (default: 90 days unreachable, 30 days reachable) -# Customize expiration -git config gc.reflogExpire "180 days" -git config gc.reflogExpireUnreachable "90 days" - -# Manually expire reflog entries -git reflog expire --expire=90.days.ago --all -``` - -## Cherry-Pick - -Apply specific commits from one branch to another. - -### Basic Usage - -```bash -# Cherry-pick a single commit -git cherry-pick - -# Cherry-pick without committing (stage changes only) -git cherry-pick --no-commit -# or -git cherry-pick -n - -# Cherry-pick and edit the commit message -git cherry-pick --edit -``` - -### Multiple and Range Cherry-Picks - -```bash -# Cherry-pick multiple non-consecutive commits -git cherry-pick - -# Cherry-pick a range (exclusive start, inclusive end) -git cherry-pick A..B # Does NOT include A - -# Cherry-pick a range (inclusive of both endpoints) -git cherry-pick A^..B # Includes A through B - -# Cherry-pick and mark the source commit -git cherry-pick -x # Appends "(cherry picked from commit ...)" to message -``` - -### Handling Conflicts - -```bash -# If conflicts occur during cherry-pick -git status # See conflicted files -# ... resolve conflicts ... -git add -git cherry-pick --continue - -# Abort cherry-pick -git cherry-pick --abort - -# Skip current commit and continue with remaining -git cherry-pick --skip -``` - -### Best Practices - -- Prefer merge or rebase over cherry-pick when possible to maintain cleaner history -- Use `-x` flag to record the source commit SHA for traceability -- Cherry-pick small, self-contained commits; avoid large refactors -- Create a dedicated branch before cherry-picking a range of commits - -## git filter-repo - -`git filter-repo` is the recommended tool for rewriting repository history (replacing the deprecated `git filter-branch`). It must be installed separately. - -### Installation - -```bash -# macOS -brew install git-filter-repo - -# pip -pip install git-filter-repo -``` - -### Common Operations - -```bash -# Remove a file from all history -git filter-repo --path secrets.env --invert-paths - -# Remove a directory from all history -git filter-repo --path src/deprecated/ --invert-paths - -# Keep only a subdirectory (extract into its own repo) -git filter-repo --subdirectory-filter src/lib/ - -# Rename/move paths throughout history -git filter-repo --path-rename old/path/:new/path/ - -# Remove large files from history -git filter-repo --strip-blobs-bigger-than 10M - -# Replace text in all files throughout history -git filter-repo --replace-text expressions.txt -# expressions.txt format: literal:old_text==>new_text - -# Mailmap-style author rewriting -git filter-repo --mailmap mailmap.txt -``` - -### Safety Notes - -- Always work on a fresh clone: `git clone --mirror && cd ` -- filter-repo intentionally removes the remote to prevent accidental pushes -- After filtering, force-push: `git remote add origin && git push --force --all` -- All collaborators must re-clone after history rewriting - -## Subtree vs Submodule - -### Git Subtree - -Subtree merges external repository content directly into your repository. - -```bash -# Add a subtree -git subtree add --prefix=lib/external https://github.com/org/lib.git main --squash - -# Pull updates from the external repo -git subtree pull --prefix=lib/external https://github.com/org/lib.git main --squash - -# Push changes back to the external repo -git subtree push --prefix=lib/external https://github.com/org/lib.git main - -# Split subtree into its own branch (for extraction) -git subtree split --prefix=lib/external -b extracted-lib -``` - -**When to use subtree**: Vendoring third-party code, shared internal libraries, simpler onboarding (no extra commands for collaborators). - -### Git Submodule - -Submodule maintains a pointer to a specific commit in another repository. - -```bash -# Add a submodule -git submodule add https://github.com/org/lib.git lib/external - -# Clone a repo with submodules -git clone --recurse-submodules - -# Initialize and update submodules after clone -git submodule update --init --recursive - -# Update all submodules to latest -git submodule update --remote - -# Remove a submodule -git submodule deinit lib/external -git rm lib/external -rm -rf .git/modules/lib/external -``` - -**When to use submodule**: Strict version pinning, large external dependencies, separate build/CI for the dependency. - -## Stash Operations - -### Basic Stash - -```bash -# Stash working directory changes -git stash # or git stash push -git stash push -m "description" # With a message - -# Include untracked files -git stash push -u # or --include-untracked - -# Stash only staged changes (Git 2.35+) -git stash push --staged - -# Stash specific files -git stash push -- path/to/file1 path/to/file2 - -# Interactive stash (select hunks) -git stash push -p -``` - -### Managing Stashes - -```bash -# List stashes -git stash list - -# Apply most recent stash (keep in stash list) -git stash apply - -# Apply and remove from stash list -git stash pop - -# Apply a specific stash -git stash apply stash@{2} - -# Show stash contents -git stash show # Summary -git stash show -p # Full diff -git stash show -p stash@{1} # Specific stash - -# Create branch from stash -git stash branch stash@{0} - -# Drop a specific stash -git stash drop stash@{0} - -# Clear all stashes -git stash clear -``` - -### Stash Export/Import (Git 2.51+) - -```bash -# Export stashes to a reference (for sharing across machines) -git stash export - -# Import stashes from a reference -git stash import - -# Push exported stashes to remote -git push origin refs/stashes/ - -# Fetch and import from remote -git fetch origin refs/stashes/ -git stash import -``` - -## Rerere (Reuse Recorded Resolution) - -Rerere records how you resolve merge conflicts and automatically applies the same resolution if the same conflict recurs. - -### Setup - -```bash -# Enable rerere globally -git config --global rerere.enabled true -``` - -### Usage - -```bash -# After resolving a conflict, rerere automatically records it -# Next time the same conflict occurs, Git auto-resolves it - -# View current rerere state -git rerere status - -# Show diff of what rerere would resolve -git rerere diff - -# Forget a recorded resolution -git rerere forget - -# Clear all recorded resolutions -git rerere clear -``` - -### When Rerere Helps - -- Repeatedly rebasing a long-lived branch onto main -- Undoing a merge to redo it differently (same conflicts, same resolutions) -- Testing merge results before actually merging (merge, test, reset, then merge later) -- Maintaining topic branches that are frequently re-integrated - -### Expiration - -```bash -# Default: unresolved 15 days, resolved 60 days -# Customize -git config gc.rerereResolved "90 days" -git config gc.rerereUnresolved "30 days" - -# Git 2.50+ maintenance task -git maintenance run --task=rerere-gc -``` - -## Git Blame - -### Basic Usage - -```bash -# Blame a file -git blame - -# Blame specific lines -git blame -L 10,20 # Lines 10-20 -git blame -L '/^function/',+10 # Regex start, 10 lines - -# Show email instead of name -git blame -e - -# Show original commit for moved/copied lines -git blame -C # Detect copies within same commit -git blame -C -C # Detect copies from other files in same commit -git blame -C -C -C # Detect copies from any commit -git blame -M # Detect moved lines within a file -``` - -### Ignore Revisions - -Skip bulk formatting or refactoring commits in blame output: - -```bash -# Ignore a specific revision -git blame --ignore-rev - -# Use an ignore file -git blame --ignore-revs-file .git-blame-ignore-revs - -# Configure globally for the repo -git config blame.ignoreRevsFile .git-blame-ignore-revs -``` - -The `.git-blame-ignore-revs` file format: - -``` -# Prettier formatting migration -abc123def456789... - -# ESLint autofix bulk commit -def789abc123456... -``` - -GitHub automatically recognizes `.git-blame-ignore-revs` files. - -## Git Notes - -Attach metadata to commits without modifying commit history. - -```bash -# Add a note to a commit -git notes add -m "This commit fixes CVE-2024-1234" - -# Add a note to HEAD -git notes add -m "Reviewed by: security team" - -# Show notes -git log --show-notes - -# Edit an existing note -git notes edit - -# Remove a note -git notes remove - -# Push notes to remote -git push origin refs/notes/commits - -# Fetch notes from remote -git fetch origin refs/notes/*:refs/notes/* - -# Notes in different namespaces -git notes --ref=review add -m "LGTM" -git log --show-notes=review -``` - -## Git Bundle - -Create portable, offline-transferable repository archives. - -### Creating Bundles - -```bash -# Full repository bundle -git bundle create repo.bundle --all - -# Bundle specific branch -git bundle create feature.bundle main..feature/auth - -# Incremental bundle (since last bundle) -git bundle create incremental.bundle --since="2025-01-01" --all - -# Bundle with tags -git bundle create release.bundle v1.0..v2.0 -``` - -### Using Bundles - -```bash -# Verify a bundle -git bundle verify repo.bundle - -# Clone from a bundle -git clone repo.bundle my-repo - -# Fetch from a bundle into existing repo -git fetch repo.bundle main:refs/remotes/bundle/main - -# List references in a bundle -git bundle list-heads repo.bundle -``` - -### Bundle URI (Git 2.50+) - -Bundle URIs allow Git hosting services to provide pre-computed bundles, speeding up initial clones: - -```bash -# Clone using bundle URI -git clone --bundle-uri=https://example.com/repo.bundle https://example.com/repo.git -``` - -## Sparse Checkout - -Work with only a subset of files in a large repository. - -### Setup - -```bash -# Enable sparse checkout -git sparse-checkout init - -# Use cone mode (recommended, faster) -git sparse-checkout init --cone - -# Set directories to include -git sparse-checkout set src/frontend src/shared docs/ - -# Add directories -git sparse-checkout add tests/frontend - -# List current sparse checkout patterns -git sparse-checkout list - -# Disable sparse checkout (restore full working tree) -git sparse-checkout disable -``` - -### With Partial Clone - -```bash -# Clone large repo with sparse checkout and partial clone -git clone --filter=blob:none --sparse https://github.com/org/monorepo.git -cd monorepo -git sparse-checkout set src/my-service - -# Backfill historical blobs efficiently (Git 2.49+) -git backfill -``` - -### Clean Recovery (Git 2.52+) - -```bash -# Recover from files left outside sparse-checkout definition -git sparse-checkout clean -``` diff --git a/packages/dotfiles/dot_agents/skills/git-helper/references/branching-workflows.md b/packages/dotfiles/dot_agents/skills/git-helper/references/branching-workflows.md deleted file mode 100644 index e9cbcea7e3..0000000000 --- a/packages/dotfiles/dot_agents/skills/git-helper/references/branching-workflows.md +++ /dev/null @@ -1,454 +0,0 @@ -# Branching Strategies and Workflows - -## Branching Strategies - -### Trunk-Based Development - -All developers commit to a single main branch ("trunk"). Short-lived feature branches (1-2 days max) are used, merged frequently. Best for teams practicing continuous integration/deployment. - -``` -main ─────●───●───●───●───●───●───●──→ - \─●─/ \─●─●─/ - feature-a feature-b - (1 day) (2 days) -``` - -**When to use**: CI/CD pipelines, small-to-medium teams, rapid iteration, microservices. - -**Key practices**: - -- Feature flags to hide incomplete work -- Short-lived branches (< 2 days) -- Merge to main at least daily -- Automated testing on every commit -- No long-lived feature branches - -```bash -# Typical trunk-based workflow -git switch -c feature/quick-change -# ... make changes ... -git commit -m "feat: add validation" -git push -u origin feature/quick-change -# Create PR, get quick review, merge same day -gh pr create --fill -``` - -### GitHub Flow - -Single main branch with feature branches. PRs are the review mechanism. Deployed from main after merge. Simpler than GitFlow, suits most teams. - -``` -main ─────●───────●─────────●───────●──→ - \─●─●─/ (PR) \─●─●─/ (PR) - feature-a feature-b -``` - -**When to use**: Web applications, SaaS, open-source projects, teams that deploy frequently. - -**Key practices**: - -- `main` is always deployable -- Branch from `main` for any change -- Open PR early for discussion -- Deploy from `main` after merge -- No release branches needed - -```bash -# GitHub Flow workflow -git switch -c feature/user-settings -# ... develop feature ... -git push -u origin feature/user-settings -gh pr create --title "feat: user settings page" -# After review and CI passes, merge via PR -``` - -### GitFlow - -Structured branching with long-lived `develop` and `main` branches. Release branches for stabilization. Hotfix branches for production fixes. - -``` -main ────●──────────────●──────●──→ - \ / \ / -release \ release/1.0 hotfix/1.0.1 - \ / -develop ──●──●──●──●──●──●──●──●──→ - \─●─/ \─●─●─/ - feature-a feature-b -``` - -**When to use**: Packaged software with formal releases, long release cycles, need for multiple supported versions, large teams. - -**Key practices**: - -- `main` contains production-ready code with version tags -- `develop` is the integration branch for features -- `release/*` branches for release stabilization -- `hotfix/*` branches for emergency production fixes -- Feature branches merge into `develop` - -```bash -# Start a feature -git switch develop -git switch -c feature/new-module - -# Start a release -git switch develop -git switch -c release/1.2.0 -# ... bug fixes only on release branch ... -git switch main && git merge release/1.2.0 -git tag -a v1.2.0 -m "Release 1.2.0" -git switch develop && git merge release/1.2.0 - -# Emergency hotfix -git switch main -git switch -c hotfix/critical-fix -# ... fix ... -git switch main && git merge hotfix/critical-fix -git tag -a v1.2.1 -m "Hotfix 1.2.1" -git switch develop && git merge hotfix/critical-fix -``` - -### Choosing a Strategy - -| Factor | Trunk-Based | GitHub Flow | GitFlow | -| ----------------- | ------------ | ------------ | ------------ | -| Team size | Any | Small-Medium | Medium-Large | -| Release cadence | Continuous | Frequent | Scheduled | -| Deploy frequency | Multiple/day | Daily-Weekly | Per release | -| Complexity | Low | Low | High | -| CI/CD maturity | Required | Recommended | Optional | -| Multiple versions | No | No | Yes | - -## Commit Conventions - -### Conventional Commits - -The standard format adopted by Angular, Vue, and many open-source projects: - -``` -(): - -[optional body] - -[optional footer(s)] -``` - -**Types**: - -| Type | Purpose | -| ---------- | --------------------------------------------- | -| `feat` | New feature (correlates with MINOR in semver) | -| `fix` | Bug fix (correlates with PATCH in semver) | -| `docs` | Documentation only | -| `style` | Formatting, semicolons, etc. (not CSS) | -| `refactor` | Code change that neither fixes nor adds | -| `perf` | Performance improvement | -| `test` | Adding or correcting tests | -| `chore` | Build process, auxiliary tools | -| `ci` | CI configuration changes | -| `build` | Build system or external dependency changes | -| `revert` | Reverts a previous commit | - -**Breaking changes**: Add `!` after type or `BREAKING CHANGE:` in footer: - -``` -feat(api)!: remove deprecated endpoints - -BREAKING CHANGE: The /v1/users endpoint has been removed. -Use /v2/users instead. -``` - -### Commit Message Best Practices - -1. **Subject line**: Imperative mood, max 50 characters, no period - - "Add user validation" not "Added user validation" -2. **Body**: Explain what and why, not how. Wrap at 72 characters -3. **Footer**: Reference issues, breaking changes, co-authors -4. **Scope**: Keep consistent within project (module names, directories) - -```bash -# Good commit messages -git commit -m "feat(auth): add OAuth2 PKCE flow for mobile clients" -git commit -m "fix(api): handle null response from payment gateway" -git commit -m "refactor: extract validation logic into shared module" - -# Bad commit messages -git commit -m "fix stuff" -git commit -m "WIP" -git commit -m "Updated the code to fix the bug that was happening when users tried to login with their email and password combination and the server returned an error" -``` - -## Merge vs Rebase - -### Merge - -Creates a merge commit that combines two branches. Preserves full branch history and topology. - -```bash -# Merge feature into main -git switch main -git merge feature/auth - -# Merge with no fast-forward (always create merge commit) -git merge --no-ff feature/auth - -# Merge with squash (combine all commits, no merge commit) -git merge --squash feature/auth -git commit -m "feat(auth): add authentication module" -``` - -**Advantages**: Non-destructive, preserves context of when branches existed, simple. -**Disadvantages**: Can create noisy history with many merge commits. - -### Rebase - -Replays commits on top of another branch. Creates a linear history. - -```bash -# Rebase feature onto main -git switch feature/auth -git rebase main - -# Interactive rebase for cleanup -git rebase -i main - -# After rebase, must force push (branch history changed) -git push --force-with-lease origin feature/auth -``` - -**Advantages**: Clean, linear history. Easier to read `git log`. Easier `git bisect`. -**Disadvantages**: Rewrites history (do not rebase shared branches). Can be confusing with conflicts. - -### When to Use Which - -| Scenario | Recommendation | -| ----------------------------------- | ------------------------------ | -| Integrating feature branch to main | Merge (--no-ff or squash) | -| Keeping feature branch up to date | Rebase onto main | -| Shared/public branch | Never rebase (use merge) | -| Personal/unshared branch | Rebase for clean history | -| Long-lived branch with many commits | Interactive rebase, then merge | -| Simple 1-2 commit feature | Squash merge | - -### Squash and Merge - -Combines all feature branch commits into a single commit on main: - -```bash -# Via git -git switch main -git merge --squash feature/auth -git commit -m "feat(auth): complete authentication module" - -# Via GitHub PR settings (most common in team workflows) -# Configure in repo Settings > General > Pull Requests: -# "Allow squash merging" with "Default commit message: Pull request title" -``` - -### Force Push Safety - -After rebasing, use `--force-with-lease` instead of `--force`: - -```bash -# Safe force push - fails if remote has commits you haven't seen -git push --force-with-lease origin feature/auth - -# Even safer - specify expected remote state -git push --force-with-lease=feature/auth: origin feature/auth - -# NEVER force push to main/master/develop -# NEVER use --force (use --force-with-lease instead) -``` - -## Signed Commits - -### SSH Signing (Recommended, simplest) - -```bash -# Configure SSH signing -git config --global gpg.format ssh -git config --global user.signingkey ~/.ssh/id_ed25519.pub - -# Sign all commits by default -git config --global commit.gpgsign true - -# Sign all tags by default -git config --global tag.gpgsign true - -# Verify signatures -git log --show-signature -git verify-commit -``` - -### GPG Signing - -```bash -# List GPG keys -gpg --list-secret-keys --keyid-format=long - -# Configure GPG signing -git config --global user.signingkey -git config --global commit.gpgsign true - -# Sign a single commit -git commit -S -m "feat: signed commit" - -# Sign a tag -git tag -s v1.0.0 -m "Release 1.0.0" - -# Verify -git verify-commit -git verify-tag v1.0.0 -``` - -### SSH Allowed Signers - -For verifying SSH signatures locally: - -```bash -# Create allowed signers file -echo "user@example.com ssh-ed25519 AAAA..." > ~/.ssh/allowed_signers - -# Configure Git to use it -git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers - -# Now verification works -git verify-commit HEAD -``` - -## Tag Management - -### Tag Types - -```bash -# Lightweight tag (just a pointer, no metadata) -git tag v1.0.0 - -# Annotated tag (recommended for releases - stores tagger, date, message) -git tag -a v1.0.0 -m "Release 1.0.0: initial stable release" - -# Signed tag (annotated + cryptographic signature) -git tag -s v1.0.0 -m "Release 1.0.0" - -# Tag a specific commit -git tag -a v1.0.0 -m "Release 1.0.0" -``` - -### Tag Operations - -```bash -# List tags -git tag # All tags -git tag -l "v1.*" # Pattern matching -git tag -l --sort=-version:refname # Sort by version descending - -# Show tag details -git show v1.0.0 - -# Push tags -git push origin v1.0.0 # Push specific tag -git push origin --tags # Push all tags - -# Delete tags -git tag -d v1.0.0 # Delete local tag -git push origin --delete v1.0.0 # Delete remote tag - -# Rename a tag (delete old, create new) -git tag new-name old-name -git tag -d old-name -git push origin new-name :old-name -``` - -### Semantic Versioning with Tags - -Follow [SemVer](https://semver.org/): `MAJOR.MINOR.PATCH` - -```bash -# Version tags -git tag -a v1.0.0 -m "1.0.0: initial release" -git tag -a v1.1.0 -m "1.1.0: add user profiles" # New feature -git tag -a v1.1.1 -m "1.1.1: fix profile loading" # Bug fix -git tag -a v2.0.0 -m "2.0.0: redesigned API" # Breaking change - -# Pre-release tags -git tag -a v2.0.0-alpha.1 -m "2.0.0 Alpha 1" -git tag -a v2.0.0-beta.1 -m "2.0.0 Beta 1" -git tag -a v2.0.0-rc.1 -m "2.0.0 Release Candidate 1" -``` - -**Best practices**: - -- Use annotated tags for releases (they store metadata) -- Use lightweight tags for personal/temporary markers -- Use signed tags for published/distributed software -- Always push tags explicitly (`--tags`) - -## Release Workflows - -### Tag-Based Releases - -```bash -# Create release tag -git switch main -git pull origin main -git tag -a v1.2.0 -m "Release 1.2.0" -git push origin v1.2.0 - -# Create GitHub release from tag -gh release create v1.2.0 --title "v1.2.0" --notes "Release notes here" - -# Create release with auto-generated notes -gh release create v1.2.0 --generate-notes - -# Create pre-release -gh release create v2.0.0-beta.1 --prerelease --generate-notes -``` - -### Release Branch Workflow - -```bash -# Cut a release branch -git switch main -git switch -c release/1.2.0 - -# Only bug fixes on release branch -git commit -m "fix: correct validation error message" - -# When ready to release -git switch main -git merge --no-ff release/1.2.0 -git tag -a v1.2.0 -m "Release 1.2.0" -git push origin main --follow-tags - -# Back-merge release fixes to develop (if using GitFlow) -git switch develop -git merge release/1.2.0 -git push origin develop - -# Clean up -git branch -d release/1.2.0 -git push origin --delete release/1.2.0 -``` - -### Hotfix Workflow - -```bash -# Branch from the release tag -git switch -c hotfix/1.2.1 v1.2.0 - -# Make the fix -git commit -m "fix: critical security vulnerability" - -# Merge to main and tag -git switch main -git merge --no-ff hotfix/1.2.1 -git tag -a v1.2.1 -m "Hotfix 1.2.1: security fix" -git push origin main --follow-tags - -# Also merge fix to develop/current release -git switch develop -git merge hotfix/1.2.1 - -# Clean up -git branch -d hotfix/1.2.1 -``` diff --git a/packages/dotfiles/dot_agents/skills/git-helper/references/config-hooks.md b/packages/dotfiles/dot_agents/skills/git-helper/references/config-hooks.md deleted file mode 100644 index 149c6d2b19..0000000000 --- a/packages/dotfiles/dot_agents/skills/git-helper/references/config-hooks.md +++ /dev/null @@ -1,635 +0,0 @@ -# Git Configuration, Hooks, and Performance - -## Git Configuration - -### Configuration Levels - -```bash -# System-wide (all users) -git config --system # /etc/gitconfig - -# User-global -git config --global # ~/.gitconfig or ~/.config/git/config - -# Repository-local (default) -git config --local # .git/config - -# Worktree-specific (Git 2.20+) -git config --worktree - -# View all config with origins -git config --list --show-origin - -# View specific value and its source -git config --show-origin user.email - -# New sub-command interface (Git 2.46+) -git config list # List all settings -git config get user.email # Get a specific setting -``` - -### Essential Configuration - -```bash -# Identity -git config --global user.name "Your Name" -git config --global user.email "you@example.com" - -# Prevent identity guessing -git config --global user.useConfigOnly true - -# Default branch name -git config --global init.defaultBranch main - -# Default push behavior -git config --global push.default current -git config --global push.autoSetupRemote true # Auto set upstream on push - -# Pull behavior -git config --global pull.rebase true # Rebase instead of merge on pull - -# Rebase -git config --global rebase.autoSquash true # Auto-process fixup! commits -git config --global rebase.autoStash true # Auto-stash before rebase -git config --global rebase.updateRefs true # Update dependent branches during rebase - -# Merge -git config --global merge.conflictStyle zdiff3 # Better conflict markers (shows base) -git config --global rerere.enabled true # Remember conflict resolutions - -# Diff -git config --global diff.algorithm histogram # Better diff algorithm -git config --global diff.colorMoved default # Highlight moved lines -git config --global diff.colorMovedWS allow-indentation-change - -# Commit signing (SSH) -git config --global gpg.format ssh -git config --global user.signingkey ~/.ssh/id_ed25519.pub -git config --global commit.gpgsign true -git config --global tag.gpgsign true - -# Fetch -git config --global fetch.prune true # Auto-prune stale tracking branches -git config --global fetch.prunetags true # Auto-prune stale tags -git config --global fetch.parallel 0 # Parallel fetch (0 = auto-detect) - -# Column output -git config --global column.ui auto # Display branch/tag lists in columns - -# Remote HEAD tracking (Git 2.48+) -git config --global remote.origin.followRemoteHead warn -``` - -### Conditional Includes - -Apply different configurations based on directory or remote URL: - -```ini -# ~/.gitconfig - -[user] - name = Your Name - email = personal@example.com - -# Work repositories -[includeIf "gitdir:~/work/"] - path = ~/.gitconfig-work - -# Open source repositories -[includeIf "gitdir:~/oss/"] - path = ~/.gitconfig-oss - -# GitHub repositories (Git 2.36+, matches by remote URL) -[includeIf "hasconfig:remote.*.url:https://github.com/**"] - path = ~/.gitconfig-github - -# GitLab repositories -[includeIf "hasconfig:remote.*.url:https://gitlab.com/**"] - path = ~/.gitconfig-gitlab -``` - -```ini -# ~/.gitconfig-work -[user] - email = you@company.com - signingkey = ~/.ssh/work_ed25519.pub - -[commit] - gpgsign = true -``` - -**Note**: The trailing slash in `gitdir:~/work/` is required. Order matters -- later includes override earlier ones. - -### Useful Aliases - -```ini -# ~/.gitconfig -[alias] - # Short forms - st = status - co = checkout - sw = switch - br = branch - ci = commit - - # Log formats - lg = log --oneline --graph --all --decorate - ll = log --oneline -20 - hist = log --pretty=format:'%C(auto)%h %ad | %s%d [%an]' --date=short - - # Diff - ds = diff --staged - dw = diff --word-diff - - # Branch management - branches = branch -a -v - merged = branch --merged - unmerged = branch --no-merged - cleanup = "!git branch --merged | grep -v '\\*\\|main\\|master\\|develop' | xargs -n 1 git branch -d" - - # Undo shortcuts - unstage = restore --staged - uncommit = reset --soft HEAD~1 - amend = commit --amend --no-edit - - # Fixup workflow - fixup = "!f() { git commit --fixup=$1; }; f" - squash-all = "!f() { git rebase -i --autosquash $1; }; f" - - # Working directory - stash-all = stash push --include-untracked - wip = "!git add -A && git commit -m 'WIP'" - - # Information - authors = shortlog -sne - whoami = "!echo \"$(git config user.name) <$(git config user.email)>\"" - root = rev-parse --show-toplevel - - # Find - find-merge = "!f() { git log --merges --ancestry-path --oneline $1..HEAD | tail -1; }; f" - grep-log = "!f() { git log --all -S\"$1\" --oneline; }; f" -``` - -Shell command aliases (prefixed with `!`) run in the repository root. - -## Git Hooks - -Hooks are scripts in `.git/hooks/` that execute at specific points in the Git workflow. They are not committed to the repository by default. - -### Client-Side Hooks - -| Hook | Trigger | Common Use | -| -------------------- | ------------------------------------ | ------------------------------------ | -| `pre-commit` | Before commit is created | Lint, format, run fast tests | -| `prepare-commit-msg` | After default message, before editor | Template commit messages | -| `commit-msg` | After user enters message | Validate commit message format | -| `post-commit` | After commit is created | Notifications | -| `pre-rebase` | Before rebase starts | Prevent rebase of published branches | -| `pre-push` | Before push to remote | Run full test suite | -| `pre-merge-commit` | Before merge commit | Validate merge | -| `post-merge` | After merge completes | Install dependencies | -| `post-checkout` | After checkout/switch | Install dependencies, build | -| `post-rewrite` | After rebase/amend | Update dependent data | - -### Sharing Hooks with the Team - -```bash -# Option 1: core.hooksPath (Git 2.9+) -# Store hooks in the repo -mkdir -p .githooks -# Create hooks in .githooks/ -git config core.hooksPath .githooks - -# Option 2: Use a hook manager -# pre-commit (Python-based, language-agnostic) -pip install pre-commit -# Create .pre-commit-config.yaml in repo root - -# Option 3: Husky (Node.js projects) -npx husky init -``` - -### Pre-Commit Hook Examples - -```bash -#!/bin/sh -# .githooks/pre-commit - -# Prevent committing to main/master directly -branch=$(git rev-parse --abbrev-ref HEAD) -if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then - echo "ERROR: Direct commits to $branch are not allowed." - echo "Create a feature branch instead." - exit 1 -fi - -# Check for debug statements -if git diff --cached --diff-filter=ACM | grep -nE '(console\.log|debugger|binding\.pry|import pdb)'; then - echo "ERROR: Debug statements found in staged changes." - exit 1 -fi - -# Check for secrets patterns -if git diff --cached --diff-filter=ACM | grep -nEi '(password|secret|api_key|token)\s*=\s*["\x27][^"\x27]+'; then - echo "WARNING: Possible secret detected in staged changes." - echo "Review carefully before committing." - exit 1 -fi -``` - -### Commit-msg Hook Example - -```bash -#!/bin/sh -# .githooks/commit-msg -# Enforce conventional commit format - -commit_msg=$(cat "$1") -pattern='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?: .{1,72}' - -if ! echo "$commit_msg" | head -1 | grep -qE "$pattern"; then - echo "ERROR: Commit message does not follow conventional commit format." - echo "" - echo "Expected: (): " - echo "Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert" - echo "" - echo "Your message: $(head -1 "$1")" - exit 1 -fi -``` - -### Pre-Push Hook Example - -```bash -#!/bin/sh -# .githooks/pre-push -# Prevent force push to protected branches - -protected_branches="main master develop" -current_branch=$(git rev-parse --abbrev-ref HEAD) - -for branch in $protected_branches; do - if [ "$current_branch" = "$branch" ]; then - # Check if this is a force push - while read local_ref local_sha remote_ref remote_sha; do - if [ "$remote_sha" != "0000000000000000000000000000000000000000" ]; then - if ! git merge-base --is-ancestor "$remote_sha" "$local_sha" 2>/dev/null; then - echo "ERROR: Force push to $branch is not allowed." - exit 1 - fi - fi - done - fi -done -``` - -### pre-commit Framework - -The [pre-commit](https://pre-commit.com/) framework manages multi-language hook configurations: - -```yaml -# .pre-commit-config.yaml -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - args: ["--maxkb=500"] - - id: check-merge-conflict - - id: detect-private-key - - - repo: https://github.com/compilerla/conventional-pre-commit - rev: v3.4.0 - hooks: - - id: conventional-pre-commit - stages: [commit-msg] -``` - -```bash -# Install hooks from config -pre-commit install -pre-commit install --hook-type commit-msg - -# Run against all files (not just staged) -pre-commit run --all-files - -# Update hook versions -pre-commit autoupdate -``` - -## Git Maintenance - -### Background Maintenance (Git 2.29+) - -```bash -# Register repository for background maintenance -git maintenance register - -# Start background maintenance scheduler -git maintenance start - -# Stop background maintenance -git maintenance stop - -# Unregister repository -git maintenance unregister - -# Run maintenance manually -git maintenance run - -# Run specific task -git maintenance run --task=gc -git maintenance run --task=commit-graph -git maintenance run --task=prefetch -git maintenance run --task=loose-objects -git maintenance run --task=incremental-repack -git maintenance run --task=pack-refs -``` - -### Maintenance Tasks (Git 2.50+) - -```bash -# Additional tasks available in modern Git -git maintenance run --task=worktree-prune # Clean stale worktrees -git maintenance run --task=rerere-gc # Prune old rerere data -git maintenance run --task=reflog-expire # Expire old reflog entries - -# Geometric repacking (Git 2.52+) -# Alternative to all-into-one repacks, avoids full GC -git maintenance run --task=geometric -``` - -### Maintenance Strategies - -```bash -# Use incremental strategy (recommended for most repos) -git config maintenance.strategy incremental - -# Incremental strategy schedule: -# Hourly: prefetch, commit-graph -# Daily: loose-objects, incremental-repack -# Weekly: pack-refs -``` - -## Scalar - -Scalar configures Git for large repositories, enabling sparse-checkout, partial clone, and background maintenance automatically. - -```bash -# Clone with Scalar (auto-configures for performance) -scalar clone https://github.com/org/large-repo.git - -# Clone without sparse checkout -scalar clone --full-clone https://github.com/org/large-repo.git - -# Clone without background maintenance -scalar clone --no-maintenance https://github.com/org/large-repo.git - -# Register existing repo for Scalar management -scalar register - -# Unregister -scalar unregister - -# List Scalar-managed repos -scalar list - -# Run diagnostics -scalar diagnose -``` - -Scalar automatically enables: - -- Background maintenance (`git maintenance`) -- Filesystem monitor (`core.fsmonitor`) -- Multi-pack index (`core.multiPackIndex`) -- Commit graph (`fetch.writeCommitGraph`, `core.commitGraph`) -- Sparse-checkout (unless `--full-clone`) -- Partial clone with blob filter (unless `--full-clone`) - -## Performance Tuning for Large Repos - -### Core Settings - -```bash -# Filesystem monitor (watches for file changes, avoids scanning) -git config core.fsmonitor true -git config core.untrackedCache true - -# Multi-pack index -git config core.multiPackIndex true - -# Commit graph -git config fetch.writeCommitGraph true -git config core.commitGraph true - -# Pack configuration -git config pack.threads 0 # Auto-detect CPU count -git config pack.windowMemory 0 # Unlimited window memory - -# Index settings -git config index.threads 0 # Auto-detect for index operations -git config index.skipHash true # Skip index hash verification (Git 2.40+) - -# Feature flags for performance bundle -git config feature.manyFiles true # Optimizes for repos with many files -``` - -### Reftable Backend (Git 2.45+) - -For repositories with many references (tags, branches): - -```bash -# Migrate existing repo to reftable -git refs migrate --ref-format=reftable - -# Clone with reftable format -git clone --ref-format=reftable -``` - -### Partial Clone - -```bash -# Clone without blobs (download on demand) -git clone --filter=blob:none - -# Clone without trees (very aggressive, download on demand) -git clone --filter=tree:0 - -# Clone with size limit -git clone --filter=blob:limit=1m - -# Backfill missing blobs efficiently (Git 2.49+) -git backfill -``` - -## .gitattributes - -Controls per-path settings for merge, diff, and export: - -```gitattributes -# Auto-detect text files, ensure LF line endings in repo -* text=auto - -# Explicit text files -*.md text -*.txt text -*.csv text -*.json text -*.yml text -*.yaml text - -# Explicit binary files -*.png binary -*.jpg binary -*.gif binary -*.ico binary -*.pdf binary -*.zip binary -*.woff2 binary - -# Language-specific diff drivers -*.rb diff=ruby -*.py diff=python -*.go diff=golang -*.rs diff=rust - -# Lock files - always use theirs on merge -package-lock.json merge=ours -diff -yarn.lock merge=ours -diff -pnpm-lock.yaml merge=ours -diff -Cargo.lock merge=ours -diff - -# Export ignore (excluded from git archive) -.gitattributes export-ignore -.gitignore export-ignore -.github/ export-ignore -tests/ export-ignore -docs/ export-ignore - -# LFS tracking -*.psd filter=lfs diff=lfs merge=lfs -text -*.sketch filter=lfs diff=lfs merge=lfs -text -``` - -## .gitignore Patterns - -### Pattern Syntax - -```gitignore -# Simple file/directory name (matches anywhere) -*.log -node_modules/ - -# Rooted pattern (only matches from repo root) -/build/ -/dist/ - -# Negate a pattern (re-include something) -*.log -!important.log - -# Directory only (trailing slash) -tmp/ - -# Double star (match across directories) -**/logs # logs directory anywhere -**/logs/*.log # .log files in any logs directory -logs/**/*.log # .log files anywhere under logs/ - -# Single character wildcard -file?.txt # file1.txt, fileA.txt - -# Character class -file[0-9].txt # file0.txt through file9.txt -``` - -### Common .gitignore Entries - -```gitignore -# OS files -.DS_Store -Thumbs.db -*.swp -*~ - -# IDE files -.idea/ -.vscode/ -*.sublime-workspace - -# Environment -.env -.env.local -.env.*.local - -# Dependencies -node_modules/ -vendor/ -__pycache__/ -*.pyc -.venv/ - -# Build outputs -/build/ -/dist/ -/out/ -*.o -*.a -*.so -*.dylib - -# Test and coverage -coverage/ -.nyc_output/ -*.lcov - -# Logs -*.log -logs/ - -# Credentials (always ignore) -*.pem -*.key -*.p12 -credentials.json -service-account.json -``` - -### Debugging .gitignore - -```bash -# Check why a file is ignored -git check-ignore -v - -# List all ignored files -git ls-files --ignored --exclude-standard - -# Force add an ignored file (rarely needed) -git add -f - -# Stop tracking a file that was previously committed -git rm --cached -echo "" >> .gitignore -git commit -m "chore: stop tracking " -``` - -### Global Gitignore - -```bash -# Set up a global gitignore for OS/editor files -git config --global core.excludesFile ~/.gitignore_global -``` - -```gitignore -# ~/.gitignore_global -.DS_Store -Thumbs.db -*.swp -*~ -.idea/ -.vscode/ -``` diff --git a/packages/dotfiles/dot_agents/skills/git-helper/references/configuration-and-maintenance.md b/packages/dotfiles/dot_agents/skills/git-helper/references/configuration-and-maintenance.md new file mode 100644 index 0000000000..d892134277 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/git-helper/references/configuration-and-maintenance.md @@ -0,0 +1,77 @@ +# Git configuration and maintenance + +Read this when changing Git configuration, hooks, credentials, maintenance, refs, garbage collection, or repository storage. + +## Configuration provenance + +Inspect value, scope, and origin: + +```bash +git config --get --show-origin --show-scope +git config --list --show-origin --show-scope +``` + +Use the `--get`/`--list` flag forms, not the newer `git config get`/`git config list` subcommand syntax — the subcommand form does not exist before Git 2.46. + +Conditional includes are useful for identity and repository-family policy. Keep predicates narrow and test the effective value inside an affected repository. + +Do not configure `merge=ours` and claim Git will always keep the current file. A merge driver, attributes, and merge semantics are separate concerns; verify the actual driver and intended policy. + +## Ignored files + +Use `git check-ignore -v ` to explain why a path is ignored. Do not rely on invalid combinations of `ls-files` flags copied from older skill text. + +## Hooks + +`core.hooksPath` may redirect all hook lookup. Inspect it with `git config --get core.hooksPath` before assuming `.git/hooks` is authoritative; do not assume that path is correct. `git hook list ` (added in Git 2.54) prints the resolved hook chain for one named hook and requires that argument — it does not enumerate every hook. For a portable listing of what's actually installed, inspect the hooks directory directly: `ls -la "$(git rev-parse --git-path hooks)"`. + +Hook scripts must quote filenames, propagate failures, and avoid blanket stderr suppression. Stage explicit paths; never stage the entire repository indiscriminately in automation. + +## Credentials and URLs + +Use credential helpers and `GIT_ASKPASS`. `git credential` is plumbing for approved helpers. `git url-parse` provides current URL parsing where supported; treat new subcommands as version-gated. + +Never embed a token in a remote URL or persist one in a generated config file. + +## References + +The current `git refs` command provides consolidated ref operations such as list and exists. Use `git update-ref` for atomic scripted ref changes, including transactions. Do not edit files under `.git/refs` directly. + +## Maintenance + +Register background maintenance only when the host and repository lifecycle support it: + +```bash +git maintenance register +git maintenance start +git maintenance run --task=commit-graph +``` + +`geometric-repack` is the task; `geometric` is a repack strategy. It requires Git 2.50 or newer, so gate it by the installed Git version: + +```bash +if git version | awk '{ split($3, version, "."); exit !(version[1] > 2 || (version[1] == 2 && version[2] >= 50)) }'; then + git maintenance run --task=geometric-repack +fi +``` + +On Git versions older than 2.50, omit it and use only the maintenance tasks supported by that Git version. Use `git maintenance is-needed` on versions that support it. Do not assume incremental maintenance is always better; select tasks from repository size, fetch/write workload, and host scheduling. + +## Object health and collection + +`git fsck`, `git gc`, pack, multi-pack-index, commit-graph, and reflog expiry operate at different layers. Diagnose before collecting. Aggressive pruning can make reflog recovery impossible. + +`git gc --auto` uses repository thresholds. `git pack-objects --path-walk` and newer pack/MIDX strategies are specialized optimization tools, not universal local defaults. + +## Primary documentation + +- [git-config](https://git-scm.com/docs/git-config) +- [githooks](https://git-scm.com/docs/githooks) +- [git-hook](https://git-scm.com/docs/git-hook) +- [git-credential](https://git-scm.com/docs/git-credential) +- [git-refs](https://git-scm.com/docs/git-refs) +- [git-update-ref](https://git-scm.com/docs/git-update-ref) +- [git-maintenance](https://git-scm.com/docs/git-maintenance) +- [git-gc](https://git-scm.com/docs/git-gc) +- [git-fsck](https://git-scm.com/docs/git-fsck) +- [git-pack-objects](https://git-scm.com/docs/git-pack-objects) diff --git a/packages/dotfiles/dot_agents/skills/git-helper/references/history-and-recovery.md b/packages/dotfiles/dot_agents/skills/git-helper/references/history-and-recovery.md new file mode 100644 index 0000000000..e2400d3283 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/git-helper/references/history-and-recovery.md @@ -0,0 +1,68 @@ +# Git history and recovery + +Read this when rebasing, comparing patch series, bisecting, recovering commits, moving stashes, using sparse checkout, or creating bundles. + +## Range-diff + +Compare an old and a new series with two explicit ranges: + +```bash +git range-diff old-base..old-tip new-base..new-tip +``` + +Use `--remerge-diff` when reviewing conflict-resolution changes in merge commits. Range-diff is a review aid; patch pairing is heuristic and is not commit identity. + +## Bisect + +`git bisect run ` interprets exit codes as follows: + +- `0`: good +- `1` through `127`, except `125`: bad +- `125`: skip this commit +- other termination or signal states: abort/error conditions + +The test command must reliably distinguish good from bad. Do not append a blanket shell fallback that turns failures into a chosen result. + +## Reflog + +Reflogs record local reference movements. Inspect candidate commits and preserve one with a branch before changing the current ref. + +Default expiry is generally 90 days for reachable entries and 30 days for unreachable entries, subject to configuration. Do not reverse those defaults or change them without a recovery policy. + +## Stash interchange + +Current Git supports exporting stash entries to a commit chain and importing that chain into another repository. Follow the installed `git stash export` and `git stash import` syntax; do not invent file-based `--to-ref` or import flags from older drafts. + +A stash changes working-tree state. Agents should not use it to hide or move user work merely for investigation. + +## Sparse checkout + +Use `git sparse-checkout set` to initialize or change patterns; a separate `init` step is deprecated for most current workflows. Inspect tracked and untracked paths before `sparse-checkout clean`, because cleanup can remove paths outside the sparse specification. + +## Partial clones and backfill + +`git backfill` can retrieve missing blobs in blobless partial clones in batches. Treat it as an evolving command and confirm the installed documentation before scripting it. + +## Bundles and bundle URIs + +`git bundle` creates and verifies portable repository object/ref bundles. Bundle URI is a clone/fetch bootstrap mechanism; do not describe it as a generic stash or backup transport. + +```bash +git bundle create repository.bundle --all +git bundle verify repository.bundle +``` + +Verify prerequisites before treating a bundle as complete, and keep ordinary remote redundancy and backups separate from this transport artifact. + +## Primary documentation + +- [git-rebase](https://git-scm.com/docs/git-rebase) +- [git-range-diff](https://git-scm.com/docs/git-range-diff) +- [git-bisect](https://git-scm.com/docs/git-bisect) +- [git-reflog](https://git-scm.com/docs/git-reflog) +- [git-reset](https://git-scm.com/docs/git-reset) +- [git-restore](https://git-scm.com/docs/git-restore) +- [git-stash](https://git-scm.com/docs/git-stash) +- [git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout) +- [git-backfill](https://git-scm.com/docs/git-backfill) +- [git-bundle](https://git-scm.com/docs/git-bundle) diff --git a/packages/dotfiles/dot_agents/skills/git-helper/references/releases.md b/packages/dotfiles/dot_agents/skills/git-helper/references/releases.md new file mode 100644 index 0000000000..7187550ab6 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/git-helper/references/releases.md @@ -0,0 +1,68 @@ +# Git release lifecycle + +Read this when upgrading Git, adopting a recently added command, or evaluating a claim about Git 3 or future behavior. + +## Current version + +Git 2.55.0 is current as of the 2026-08-03 refresh. Verify the installed version and read every release note between it and the repository's pinned or deployed version. + +Recent operationally relevant additions include: + +- 2.46: `git config` subcommand interface, refs/reftable work, credential extensions. +- 2.48: `range-diff --remerge-diff`, remote HEAD following controls, published breaking-change tracking. +- 2.49: `backfill`, `clone --revision`, and object-management improvements. +- 2.50: ORT merge completion, `merge-tree --quiet`, and maintenance tasks. +- 2.51: stash interchange, `switch` / `restore` graduating from experimental status, pack improvements. +- 2.52: `last-modified`, `refs`, experimental repository information, and maintenance improvements. +- 2.53–2.55: consult the exact release notes for stabilization, corrections, deprecations, and newly introduced commands rather than extending an older chronology by inference. + +Do not promise a Git 3 date. Planned changes can move or be revised. Use the project's `BreakingChanges` document for current intent. + +## Research ledger + +The following 44 official pages were fetched and inspected for this refresh: + +1. [Git 2.55.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.55.0.adoc) +2. [Git 2.54.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.54.0.adoc) +3. [Git 2.53.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.53.0.adoc) +4. [Git 2.52.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.52.0.adoc) +5. [Git 2.51.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.51.0.adoc) +6. [Git 2.50.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.50.0.adoc) +7. [Git 2.49.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.49.0.adoc) +8. [Git 2.48.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.48.0.adoc) +9. [Git 2.47.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.47.0.adoc) +10. [Git 2.46.0 release notes](https://github.com/git/git/blob/master/Documentation/RelNotes/2.46.0.adoc) +11. [Planned breaking changes](https://github.com/git/git/blob/master/Documentation/BreakingChanges.adoc) +12. [git-config](https://git-scm.com/docs/git-config) +13. [git-worktree](https://git-scm.com/docs/git-worktree) +14. [git-switch](https://git-scm.com/docs/git-switch) +15. [git-restore](https://git-scm.com/docs/git-restore) +16. [git-stash](https://git-scm.com/docs/git-stash) +17. [git-maintenance](https://git-scm.com/docs/git-maintenance) +18. [git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout) +19. [git-clone](https://git-scm.com/docs/git-clone) +20. [git-fetch](https://git-scm.com/docs/git-fetch) +21. [git-push](https://git-scm.com/docs/git-push) +22. [git-rebase](https://git-scm.com/docs/git-rebase) +23. [git-merge-tree](https://git-scm.com/docs/git-merge-tree) +24. [git-refs](https://git-scm.com/docs/git-refs) +25. [git-repo](https://git-scm.com/docs/git-repo) +26. [git-last-modified](https://git-scm.com/docs/git-last-modified) +27. [git-history](https://git-scm.com/docs/git-history) +28. [git-format-rev](https://git-scm.com/docs/git-format-rev) +29. [git-url-parse](https://git-scm.com/docs/git-url-parse) +30. [git-backfill](https://git-scm.com/docs/git-backfill) +31. [git-pack-objects](https://git-scm.com/docs/git-pack-objects) +32. [git-gc](https://git-scm.com/docs/git-gc) +33. [git-fsck](https://git-scm.com/docs/git-fsck) +34. [git-reflog](https://git-scm.com/docs/git-reflog) +35. [git-bundle](https://git-scm.com/docs/git-bundle) +36. [git-credential](https://git-scm.com/docs/git-credential) +37. [git-hook](https://git-scm.com/docs/git-hook) +38. [githooks](https://git-scm.com/docs/githooks) +39. [git-update-ref](https://git-scm.com/docs/git-update-ref) +40. [git-range-diff](https://git-scm.com/docs/git-range-diff) +41. [git-init](https://git-scm.com/docs/git-init) +42. [git-whatchanged](https://git-scm.com/docs/git-whatchanged) +43. [git-bisect](https://git-scm.com/docs/git-bisect) +44. [git-reset](https://git-scm.com/docs/git-reset) diff --git a/packages/dotfiles/dot_agents/skills/python-helper/SKILL.md b/packages/dotfiles/dot_agents/skills/python-helper/SKILL.md index 85c53b198a..af441198c8 100644 --- a/packages/dotfiles/dot_agents/skills/python-helper/SKILL.md +++ b/packages/dotfiles/dot_agents/skills/python-helper/SKILL.md @@ -1,565 +1,178 @@ --- name: python-helper -description: | - Python development with modern patterns, type hints, testing, and tooling - When user works with .py files, mentions Python, pip, pytest, ruff, uv, or encounters Python errors +description: Current Python development guidance for versions, uv and pip, packaging, typing, asyncio, pytest, Ruff, security, and runtime boundaries. Use when writing or reviewing Python, pyproject.toml, Python CI, tests, dependency workflows, or Python upgrades. --- -# Python Helper Agent +# Python Helper -## What's New in Python (2023-2026) +Follow the project's declared Python version and dependency workflow. Validate runtime data, make async ownership explicit, and distinguish read-only checks, local mutations, and external publication. -- **Python 3.14** (Oct 2025): Template strings `t"Hello {name}"` (PEP 750), deferred annotation evaluation (PEP 649), bracketless `except TimeoutError, OSError:` (PEP 758), `concurrent.interpreters` module (PEP 734), `compression.zstd` module (PEP 784), free-threaded mode officially supported (PEP 779), zero-overhead external debugger (PEP 768), `pathlib.Path.copy()/move()`, remote pdb attach `python -m pdb -p PID`, `python -c` auto-dedent, incremental GC with reduced pause times -- **Python 3.13** (Oct 2024): New interactive REPL with multiline editing and color, experimental free-threaded mode (no GIL, `python3.13t`), experimental JIT compiler (PEP 744), `locals()` defined semantics (PEP 667), type parameter defaults `TypeVar('T', default=int)`, `TypeIs` for type narrowing (PEP 742), `ReadOnly` TypedDict fields (PEP 705), `copy.replace()`, `dbm.sqlite3` default backend, `warnings.deprecated()` decorator (PEP 702), iOS/Android tier 3 support, 19 dead-battery modules removed (cgi, telnetlib, etc.) -- **Python 3.12** (Oct 2023): Type parameter syntax `def max[T](args)` (PEP 695), `type` statement for aliases, f-string lifting (nested f-strings, quote reuse, backslashes, comments) (PEP 701), `@override` decorator (PEP 698), `TypedDict` for `**kwargs` via `Unpack` (PEP 692), per-interpreter GIL (PEP 684), comprehension inlining 2x faster (PEP 709), `sys.monitoring` low-impact API, `pathlib.Path.walk()`, `itertools.batched()`, `distutils` removed -- **Current stable**: Python 3.14.2 (Feb 2026) -- **Supported**: 3.10 (security), 3.11 (security), 3.12 (bugfix), 3.13 (bugfix), 3.14 (active) +## Current baseline -## Overview +Verified 2026-08-03: -This skill covers Python development using modern patterns (3.10+ features), type hints, dataclasses, structural pattern matching, async/await, and the modern tooling ecosystem: uv (package/project manager), ruff (linter+formatter), pytest (testing), mypy/pyright (type checking), and pyproject.toml-based project configuration. +| Line | Status | +| --- | --- | +| Python 3.14.6 | Current stable; 3.14 and 3.13 are bugfix branches | +| Python 3.12, 3.11, 3.10 | Security-only branches | +| Python 3.15.0b4 | Prerelease; RC1 was scheduled for 2026-08-04 and final for 2026-10-01 | -## CLI Commands - -### Auto-Approved Safe Commands +Python 3.14.7 was scheduled for the day after this verification. Check live status before repeating a patch number: ```bash -# Version and environment info python --version -python -c "import sys; print(sys.version)" -pip list -pip show - -# uv commands (fast pip replacement) -uv version -uv python list -uv pip list -uv pip show -uv tree - -# Ruff (linter + formatter) -ruff check . -ruff check --fix . -ruff format --check . -ruff format . - -# Pytest -pytest -pytest -v -pytest --co # collect-only, list tests -pytest -x # stop on first failure - -# Type checking -mypy . -pyright . +uv --version ``` -### Project Management with uv - -```bash -# Create new project -uv init my-project -uv init --lib my-library - -# Add dependencies -uv add requests httpx -uv add --dev pytest ruff mypy - -# Lock and sync -uv lock -uv sync +The project's `requires-python` is the syntax and standard-library ceiling. Do not use a current interpreter to justify APIs outside that declared range. -# Run commands in project env -uv run python script.py -uv run pytest -uv run ruff check . - -# Python version management -uv python install 3.14 -uv python pin 3.14 +Read [references/releases.md](references/releases.md) for the 48-page research ledger and 3.14/3.15 features. Read [references/tooling-and-packaging.md](references/tooling-and-packaging.md) for uv, pip, publishing, Ruff, and type-checkers. Read [references/testing-and-concurrency.md](references/testing-and-concurrency.md) for pytest, asyncio, TaskGroup, interpreters, and free threading. Read [references/types-and-security.md](references/types-and-security.md) for typed boundaries, deferred annotations, subprocesses, archives, and secrets. -# Run tools without installing -uvx ruff check . -uvx black --check . - -# Build and publish -uv build -uv publish -``` - -### Virtual Environments - -```bash -# Create venv (stdlib) -python -m venv .venv -source .venv/bin/activate # macOS/Linux -.venv/Scripts/activate # Windows - -# Create venv (uv, faster) -uv venv -uv venv --python 3.14 - -# pip in venv -pip install -r requirements.txt -pip install -e ".[dev]" -pip freeze > requirements.txt -``` +## Command authority -### Ruff Configuration +Read-only or check-oriented commands: ```bash -# Lint with specific rules -ruff check --select E,F,I,B,UP . -ruff check --fix --unsafe-fixes . - -# Format -ruff format . -ruff format --diff . - -# Show rule explanation -ruff rule E501 -``` - -Configure in `pyproject.toml`: - -```toml -[tool.ruff] -target-version = "py312" -line-length = 88 - -[tool.ruff.lint] -select = ["E", "F", "I", "B", "UP", "N", "SIM", "RUF"] -ignore = ["E501"] - -[tool.ruff.lint.isort] -known-first-party = ["mypackage"] - -[tool.ruff.format] -quote-style = "double" -indent-style = "space" +python --version +uv --version +uv python list +uv tree +ruff check . +ruff format --check . +pytest --collect-only +mypy . +pyright ``` -### Pytest Commands - -```bash -# Run all tests -pytest - -# Verbose with output capture disabled -pytest -v -s +Local mutations include `uv add`, `uv sync`, `uv python install`, `uv python pin`, `ruff check --fix`, and `ruff format`. Ruff unsafe fixes can change runtime behavior or remove comments; require an explicit diff review and focused tests. -# Run specific test file/function -pytest tests/test_api.py -pytest tests/test_api.py::test_create_user +`uv publish` mutates an external registry. Require explicit artifacts, registry, credentials, and publication authorization. -# Run by marker -pytest -m "not slow" -pytest -m "integration" +## uv projects -# Parallel execution (pytest-xdist) -pytest -n auto - -# Coverage -pytest --cov=src --cov-report=term-missing - -# Show local variables on failure -pytest -l - -# Re-run failed tests -pytest --lf -pytest --ff # failed first, then rest -``` - -### Type Checking +Use uv's project interface for a uv-managed application or package: ```bash -# mypy -mypy src/ -mypy --strict src/ -mypy --ignore-missing-imports src/ - -# pyright (faster, VS Code default) -pyright src/ -pyright --pythonversion 3.14 +uv sync --locked +uv run pytest +uv add httpx +uv add --dev pytest ruff ``` -## Essential Patterns Quick Reference +- Commit `uv.lock`; it is a universal exact-resolution lockfile. +- `--locked` checks that project metadata and lock agree. +- `--frozen` uses the lock without checking whether project metadata changed; it is not the stricter CI option. +- Development tools belong in `[dependency-groups].dev`. +- `[project.optional-dependencies]` defines consumer-installable extras. +- `uv pip` is the lower-level environment interface, not the default project/lock workflow. +- `uv --version` reports the uv binary. `uv version` reads or can update the project version. -### Type Hints (Modern Syntax) +## pip boundaries -```python -# Union types (3.10+) -def process(value: int | str) -> None: ... +Use `python -m pip` inside a verified virtual environment when a project uses pip. `pip freeze` is an environment snapshot, not a portable lock or secure supply-chain workflow. `pip lock` remains experimental and its `pylock.toml` is specific to the current Python/platform. -# Optional (3.10+) -def find(name: str) -> User | None: ... - -# Generic functions (3.12+) -def first[T](items: list[T]) -> T: - return items[0] - -# Type aliases (3.12+) -type Vector = list[float] -type Result[T] = T | None -type Handler[**P] = Callable[P, Awaitable[None]] -``` +Secure requirements installs need complete hashes and may disallow source distributions: -### Dataclasses - -```python -from dataclasses import dataclass, field - -@dataclass(frozen=True, slots=True) -class Point: - x: float - y: float - label: str = "origin" - tags: list[str] = field(default_factory=list) -``` - -### Structural Pattern Matching (3.10+) - -```python -match command: - case {"action": "move", "x": x, "y": y}: - move_to(x, y) - case {"action": "quit"}: - quit_game() - case str() as text if text.startswith("/"): - handle_command(text) - case _: - print("Unknown command") +```bash +python -m pip install --require-hashes --only-binary :all: -r requirements.txt ``` -### Async/Await - -```python -import asyncio - -async def fetch_all(urls: list[str]) -> list[str]: - async with httpx.AsyncClient() as client: - tasks = [client.get(url) for url in urls] - responses = await asyncio.gather(*tasks) - return [r.text for r in responses] - -asyncio.run(fetch_all(["https://example.com"])) -``` +## Type and validate boundaries -### Error Handling +Avoid bare `dict` and `list[dict]`; they introduce imprecise values. Use dataclasses, `TypedDict`, Pydantic models, or precise mappings according to whether runtime validation is needed. ```python -# Exception groups (3.11+) -try: - async with asyncio.TaskGroup() as tg: - tg.create_task(risky_op()) -except* ValueError as eg: - for exc in eg.exceptions: - print(f"ValueError: {exc}") -except* TypeError as eg: - handle_type_errors(eg) - -# Custom exceptions -class AppError(Exception): - def __init__(self, message: str, code: int = 500): - super().__init__(message) - self.code = code -``` +from pydantic import BaseModel, TypeAdapter -## pyproject.toml Quick Reference - -```toml -[project] -name = "my-package" -version = "0.1.0" -description = "My Python package" -requires-python = ">=3.12" -dependencies = [ - "httpx>=0.27", - "pydantic>=2.0", -] - -[project.optional-dependencies] -dev = ["pytest>=8.0", "ruff>=0.8", "mypy>=1.13"] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.pytest.ini_options] -testpaths = ["tests"] -addopts = "-ra -q" -markers = [ - "slow: marks tests as slow", - "integration: marks integration tests", -] - -[tool.mypy] -python_version = "3.14" -strict = true -warn_return_any = true - -[tool.ruff] -target-version = "py312" -line-length = 88 - -[tool.ruff.lint] -select = ["E", "F", "I", "B", "UP"] -``` -## Common Error Patterns and Solutions +class User(BaseModel): + id: int + email: str -### ModuleNotFoundError -```python -# Usually: wrong venv or missing dependency -# Check: which python, pip list -# Fix: uv add or pip install -# If script name shadows stdlib: rename your file (e.g., random.py -> my_random.py) +Users = TypeAdapter(list[User]) +users = Users.validate_python(response.json()) ``` -### ImportError with Circular Imports - -```python -# Move import inside function, or use TYPE_CHECKING guard -from __future__ import annotations # Defers all annotation evaluation (pre-3.14) -from typing import TYPE_CHECKING +Check HTTP status before parsing. A type annotation does not validate JSON, environment variables, database results, cache values, or deserialized files. -if TYPE_CHECKING: - from myapp.models import User # Only imported during type checking +Use `Any` only where disabling checking is intentional and isolated. Prefer `object` or `unknown`-equivalent validation patterns at boundaries. Use `Never` for exhaustiveness. -def process(user: User) -> None: # Works at runtime due to deferred eval - ... -``` +`TypeIs` narrows both branches and requires the narrowed type to be a subtype. `TypeGuard` remains necessary for some invariant-container or otherwise non-subtype narrowing. Neither is universally preferred. -### TypeError: unhashable type +## Async and concurrency -```python -# Mutable types (list, dict, set) can't be dict keys or set members -# Fix: use tuple instead of list, frozenset instead of set -# Or for dataclasses: @dataclass(frozen=True) -``` +Use `TaskGroup` when sibling cancellation and grouped failures are the intended semantics. `asyncio.gather()` does not cancel sibling awaitables merely because one fails. `return_exceptions=True` turns exceptions into results and must not silently normalize failure. -### asyncio.run() Cannot Be Called from Running Event Loop +Reuse clients so connection pooling works: ```python -# In Jupyter notebooks or nested async contexts: import asyncio -import nest_asyncio -nest_asyncio.apply() # Allows nested event loops - -# Or use await directly in Jupyter/IPython: -result = await fetch_data() -``` - -### Common Type Hint Mistakes - -```python -# Wrong: mutable default in function signature -def bad(items: list[int] = []) -> None: ... - -# Right: use None sentinel -def good(items: list[int] | None = None) -> None: - if items is None: - items = [] - -# Wrong: using dict where TypedDict is better -def process(config: dict) -> None: ... # No type safety on keys - -# Right: TypedDict for structured dicts -class Config(TypedDict): - host: str - port: int - -def process(config: Config) -> None: ... -``` - -## Popular Packages by Domain -### Web Frameworks +import httpx -- **FastAPI** - Async API framework with automatic OpenAPI docs, Pydantic validation -- **Django** - Full-stack framework with ORM, admin, auth, migrations -- **Flask** - Lightweight WSGI micro-framework -- **Starlette** - Async ASGI framework (FastAPI is built on it) -### HTTP Clients - -- **httpx** - Modern async/sync HTTP client (recommended, replaces requests for new code) -- **requests** - Simple sync HTTP client (most popular, sync-only) -- **aiohttp** - Async HTTP client/server - -### Data Validation - -- **Pydantic** (v2) - Data validation with Python type hints, used by FastAPI -- **attrs** - Classes without boilerplate (alternative to dataclasses, more features) -- **msgspec** - Fast serialization/validation - -### Data Science / ML - -- **pandas** - DataFrames and data analysis -- **polars** - Fast DataFrame library (Rust-based, often 10x faster than pandas) -- **numpy** - Numerical computing -- **scikit-learn** - Machine learning - -### Database - -- **SQLAlchemy** (v2) - SQL toolkit and ORM -- **SQLModel** - SQLAlchemy + Pydantic (by FastAPI creator) -- **asyncpg** - Fast async PostgreSQL driver -- **alembic** - Database migrations (SQLAlchemy) - -### CLI - -- **click** - Composable CLI framework -- **typer** - CLI framework built on click with type hints -- **argparse** - Standard library CLI parsing -- **rich** - Rich text, tables, progress bars in terminal - -### Testing - -- **pytest** - Testing framework (de facto standard) -- **hypothesis** - Property-based testing -- **pytest-asyncio** - Async test support -- **respx** / **pytest-httpx** - Mock httpx requests -- **factory-boy** - Test fixture factories - -## Project Structure Patterns - -### Application (Flat Layout) - -``` -my-app/ - my_app/ - __init__.py - main.py - config.py - models.py - services/ - __init__.py - user.py - tests/ - conftest.py - test_main.py - test_services/ - test_user.py - pyproject.toml - .python-version -``` +async def fetch_all(urls: list[str]) -> list[str]: + async with httpx.AsyncClient() as client: + async with asyncio.TaskGroup() as group: + tasks = [group.create_task(fetch_text(client, url)) for url in urls] + return [task.result() for task in tasks] -### Library (src Layout) -``` -my-lib/ - src/ - my_lib/ - __init__.py - py.typed # Marks package as typed (PEP 561) - core.py - _internal.py # Private module (underscore prefix) - tests/ - conftest.py - test_core.py - pyproject.toml - .python-version +async def fetch_text(client: httpx.AsyncClient, url: str) -> str: + response = await client.get(url) + response.raise_for_status() + return response.text ``` -### FastAPI Application +Own the event loop at the application boundary. Do not monkey-patch it with archived `nest_asyncio`; use top-level `await` in notebooks or refactor nested `asyncio.run()` calls. -``` -my-api/ - src/ - my_api/ - __init__.py - main.py # FastAPI app instance - config.py # Settings via pydantic-settings - models/ # Pydantic models - routes/ # API route handlers - services/ # Business logic - db/ # Database models and migrations - tests/ - conftest.py # TestClient fixture - test_routes/ - pyproject.toml - alembic.ini -``` +Subinterpreters have separate execution contexts and GILs but are not security boundaries. Free-threaded Python is supported in 3.14, yet extensions can re-enable the GIL and shared state still requires synchronization. -## Performance Tips +## Tests -```bash -# Profile first, optimize second -python -m cProfile -s cumulative script.py -python -m cProfile -o profile.pstats script.py - -# Line-by-line profiling -pip install line-profiler -kernprof -l -v script.py - -# Memory profiling -pip install memray -memray run script.py -memray flamegraph memray-output.bin -``` - -```python -# Use generators for large datasets -def process_large_file(path: str): - with open(path) as f: - for line in f: # Lazy iteration, constant memory - yield transform(line) - -# Use __slots__ for many instances -class Point: - __slots__ = ("x", "y") - def __init__(self, x: float, y: float) -> None: - self.x = x - self.y = y - -# Use collections for specialized data structures -from collections import defaultdict, Counter, deque -counts = Counter(words) -graph = defaultdict(list) -queue = deque(maxlen=100) - -# functools.cache for memoization -from functools import cache - -@cache -def fibonacci(n: int) -> int: - if n < 2: - return n - return fibonacci(n - 1) + fibonacci(n - 2) -``` +Current pytest documentation is 9.1.1. Avoid embedding volatile minimum-version pins unless compatibility requires them. -## Reference Links +- Use exact state, value, status, body, and side-effect assertions. +- Do not skip “not implemented” behavior or xfail known bugs to make the suite green. +- Use controlled servers or HTTP fakes instead of `example.com`. +- Prefer `--import-mode=importlib` for new projects and a src layout where appropriate. +- Use pytest-asyncio `auto` only when asyncio is the suite's sole async framework; use strict mode for plugin coexistence. +- `-n auto` is pytest-xdist, and coverage flags are pytest-cov, not core pytest. -- [Python Docs](https://docs.python.org/3/) - Official documentation -- [What's New in Python 3.14](https://docs.python.org/3/whatsnew/3.14.html) -- [What's New in Python 3.13](https://docs.python.org/3/whatsnew/3.13.html) -- [What's New in Python 3.12](https://docs.python.org/3/whatsnew/3.12.html) -- [typing module](https://docs.python.org/3/library/typing.html) - Type hints reference -- [uv docs](https://docs.astral.sh/uv/) - Package/project manager -- [Ruff docs](https://docs.astral.sh/ruff/) - Linter and formatter -- [pytest docs](https://docs.pytest.org/) - Testing framework -- [mypy docs](https://mypy.readthedocs.io/) - Type checker -- [Pydantic docs](https://docs.pydantic.dev/) - Data validation -- [FastAPI docs](https://fastapi.tiangolo.com/) - API framework -- [Real Python](https://realpython.com/) - Tutorials and guides +## Ruff and type-checkers -## When to Ask for Help +Let Ruff infer `target-version` from `requires-python` or set the true minimum. Formatting is best-effort against line length; E501 can still report a formatted line. Use safe fixes by default and note that Ruff is pre-1.0, so minor releases can contain breaking changes. -Ask the user for clarification when: +Choose mypy or Pyright from the repository's existing configuration, plugin needs, editor integration, and intended strictness. Do not suppress missing imports globally or claim one checker is categorically the “CI” or “fast” choice. -- Choice between sync and async is unclear -- Dependency management strategy (uv vs poetry vs pip) needs deciding -- Type annotation complexity (Protocol vs ABC vs duck typing) -- Testing strategy (unit vs integration vs property-based) -- Project structure decisions (src layout vs flat, monorepo vs separate) -- Performance optimization approach (profiling first) +## Security ---- +- Use `secrets`, not `random`, for tokens and credential material. +- Pass subprocess argument sequences; avoid `shell=True` with untrusted input. +- Never unpickle untrusted data. +- Keep tarfile's safer `data` filter and inspect untrusted archives even on Python 3.14. +- Audit with `uv run --with pip-audit pip-audit`, not the nonexistent + `pip audit` / `uv pip audit` commands. Use `uv run --with`, not `uvx`: `uvx + pip-audit` runs pip-audit in its own isolated tool environment and audits + pip-audit's dependencies, not the project's; `uv run --with pip-audit` + layers pip-audit onto the project's synced environment so it inspects the + project's actual installed packages. `uv audit` remains a preview-only + subcommand (behind `--preview`) and is absent entirely in older uv releases, + so don't rely on it as a stable, version-independent option. +- Fail on missing required environment configuration; do not silently switch databases. +- Prefer trusted publishing and pinned container artifacts over static tokens and `latest` tags. +- Open text files with an explicit encoding while Python 3.14 remains platform-sensitive by default. -See `references/` for detailed guides: +## Review checklist -- `modern-python.md` - Type hints, dataclasses, match statements, async/await, protocols, f-strings -- `tooling-packaging.md` - uv, ruff, pip, poetry, venv, pyproject.toml, mypy vs pyright, build backends -- `testing-patterns.md` - pytest fixtures, parametrize, markers, conftest, mocking, coverage, hypothesis +- Verify Python, uv, and the project's `requires-python` range. +- Separate read-only checks, local mutations, and external publication. +- Commit and enforce the lock with `--locked` in CI. +- Validate external data before returning domain types. +- Keep async failure and cancellation semantics explicit. +- Reuse network clients and check response status. +- Use exact tests without skips, weak assertions, or real public-network dependencies. +- Keep Ruff fixes safe unless an unsafe fix is explicitly reviewed. +- Avoid archived monkey patches and unqualified performance claims. +- Check security-sensitive serialization, subprocess, archive, secret, and publishing boundaries. diff --git a/packages/dotfiles/dot_agents/skills/python-helper/references/modern-python.md b/packages/dotfiles/dot_agents/skills/python-helper/references/modern-python.md deleted file mode 100644 index 7615bf27fb..0000000000 --- a/packages/dotfiles/dot_agents/skills/python-helper/references/modern-python.md +++ /dev/null @@ -1,954 +0,0 @@ -# Modern Python Patterns - -Comprehensive guide to modern Python features from 3.10 through 3.14: type hints, dataclasses, structural pattern matching, async/await, protocols, f-strings, exception groups, and more. - -## Type Hints (3.10+ Modern Syntax) - -### Union Types with `|` (3.10+) - -The pipe operator replaces `Union` and `Optional` from `typing`: - -```python -# Modern (3.10+, preferred) -def process(value: int | str) -> None: ... -def find(name: str) -> User | None: ... - -# Legacy (still works, needed for older Python) -from typing import Union, Optional -def process(value: Union[int, str]) -> None: ... -def find(name: str) -> Optional[User]: ... -``` - -Use `X | None` instead of `Optional[X]` everywhere in 3.10+ code. - -### Type Parameter Syntax (3.12+) - -PEP 695 introduced a compact syntax for generics: - -```python -# Modern (3.12+) -def first[T](items: list[T]) -> T: - return items[0] - -class Stack[T]: - def __init__(self) -> None: - self._items: list[T] = [] - - def push(self, item: T) -> None: - self._items.append(item) - - def pop(self) -> T: - return self._items.pop() - -# Bounded type variables -def longest[T: str](a: T, b: T) -> T: - return a if len(a) >= len(b) else b - -# Constrained type variables -def add[T: (int, float)](a: T, b: T) -> T: - return a + b - -# Legacy (pre-3.12) -from typing import TypeVar, Generic -T = TypeVar('T') -class Stack(Generic[T]): - ... -``` - -### Type Aliases with `type` Statement (3.12+) - -```python -# Modern (3.12+) -type Vector = list[float] -type Matrix = list[Vector] -type Result[T] = T | None -type Handler[**P] = Callable[P, Awaitable[None]] -type Pair[T, U] = tuple[T, U] - -# These support forward references naturally -type Tree[T] = T | list[Tree[T]] - -# Legacy -from typing import TypeAlias -Vector: TypeAlias = list[float] -``` - -### TypeGuard and TypeIs - -`TypeGuard` (3.10+) and `TypeIs` (3.13+) enable custom type narrowing: - -```python -from typing import TypeGuard, TypeIs - -# TypeGuard: narrowing assertion (output type may differ from input) -def is_str_list(val: list[object]) -> TypeGuard[list[str]]: - return all(isinstance(x, str) for x in val) - -def process(items: list[object]) -> None: - if is_str_list(items): - # items is now list[str] - print(" ".join(items)) - -# TypeIs (3.13+, preferred): narrowed type must be subtype of input -def is_positive_int(val: int | str) -> TypeIs[int]: - return isinstance(val, int) and val > 0 - -def handle(val: int | str) -> None: - if is_positive_int(val): - # val narrowed to int - print(val + 1) - else: - # val narrowed to str (the complement) - print(val.upper()) -``` - -`TypeIs` is preferred over `TypeGuard` in 3.13+ because it narrows both branches. - -### ParamSpec and Concatenate - -Preserve callable signatures in decorators: - -```python -from typing import ParamSpec, Concatenate -from collections.abc import Callable -from functools import wraps - -P = ParamSpec('P') - -def log_call[**P, R](func: Callable[P, R]) -> Callable[P, R]: - @wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - print(f"Calling {func.__name__}") - return func(*args, **kwargs) - return wrapper - -@log_call -def add(a: int, b: int) -> int: - return a + b - -# Concatenate: prepend parameters -def with_request[**P, R]( - func: Callable[Concatenate[Request, P], R] -) -> Callable[P, R]: - @wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - return func(get_request(), *args, **kwargs) - return wrapper -``` - -### Protocol (Structural Subtyping) - -Define interfaces without inheritance: - -```python -from typing import Protocol, runtime_checkable - -class Renderable(Protocol): - def render(self) -> str: ... - -class Widget: - def render(self) -> str: - return "" - -# Widget satisfies Renderable without inheriting it -def display(item: Renderable) -> None: - print(item.render()) - -display(Widget()) # Works - structural typing - -# runtime_checkable enables isinstance() checks -@runtime_checkable -class Closable(Protocol): - def close(self) -> None: ... - -import io -assert isinstance(io.StringIO(), Closable) -``` - -### Literal, Final, ClassVar - -```python -from typing import Literal, Final, ClassVar - -# Literal: restrict to specific values -type Direction = Literal["north", "south", "east", "west"] -type HttpMethod = Literal["GET", "POST", "PUT", "DELETE"] - -def move(direction: Direction) -> None: ... - -# Final: immutable binding -MAX_RETRIES: Final = 3 -MAX_RETRIES = 5 # Type error - -# ClassVar: class-level only -class Config: - default_timeout: ClassVar[int] = 30 # Class variable - name: str # Instance variable -``` - -### Annotated and Self - -```python -from typing import Annotated, Self - -# Annotated: attach metadata to types (used by Pydantic, FastAPI, etc.) -from pydantic import Field -class User: - name: Annotated[str, Field(min_length=1, max_length=100)] - age: Annotated[int, Field(ge=0, le=150)] - -# Self (3.11+): return type for fluent APIs and builders -class QueryBuilder: - def where(self, condition: str) -> Self: - self._conditions.append(condition) - return self - - def limit(self, n: int) -> Self: - self._limit = n - return self -``` - -### ReadOnly TypedDict (3.13+) - -```python -from typing import TypedDict, ReadOnly - -class Config(TypedDict): - name: ReadOnly[str] # Cannot be modified - debug: bool # Can be modified - -def update(config: Config) -> None: - config["debug"] = True # OK - config["name"] = "new" # Type error -``` - -### TypeVarTuple (Variadic Generics) - -```python -from typing import TypeVarTuple, Unpack - -Ts = TypeVarTuple('Ts') - -def head[T, *Ts](first: T, *rest: *Ts) -> T: - return first - -# TypedDict for **kwargs (3.12+) -from typing import TypedDict, Unpack - -class Options(TypedDict, total=False): - timeout: int - retries: int - -def fetch(url: str, **kwargs: Unpack[Options]) -> str: ... -``` - -## Dataclasses - -### Basic Usage - -```python -from dataclasses import dataclass, field - -@dataclass -class User: - name: str - email: str - age: int = 0 - tags: list[str] = field(default_factory=list) - -user = User("Alice", "alice@example.com", 30) -print(user) # User(name='Alice', email='alice@example.com', age=30, tags=[]) -``` - -### Frozen (Immutable) Dataclasses - -```python -@dataclass(frozen=True) -class Point: - x: float - y: float - -p = Point(1.0, 2.0) -p.x = 3.0 # Raises FrozenInstanceError -``` - -### Slots for Memory Efficiency - -```python -@dataclass(slots=True) -class Measurement: - timestamp: float - value: float - unit: str -``` - -`slots=True` (3.10+) generates `__slots__`, preventing arbitrary attribute assignment, reducing memory, and slightly improving access speed. - -### Keyword-Only Fields - -```python -from dataclasses import dataclass, KW_ONLY - -@dataclass -class Request: - url: str - _: KW_ONLY - method: str = "GET" - timeout: int = 30 - headers: dict[str, str] = field(default_factory=dict) - -# url is positional, rest are keyword-only -req = Request("https://api.example.com", method="POST", timeout=10) -``` - -### Post-Init Processing - -```python -@dataclass -class Rectangle: - width: float - height: float - area: float = field(init=False) - - def __post_init__(self) -> None: - self.area = self.width * self.height -``` - -### InitVar for Init-Only Parameters - -```python -from dataclasses import dataclass, InitVar - -@dataclass -class Connection: - host: str - port: int - password: InitVar[str] # Not stored as field - _authenticated: bool = field(init=False, default=False) - - def __post_init__(self, password: str) -> None: - self._authenticated = self._verify(password) -``` - -### Inheritance - -```python -@dataclass -class Animal: - name: str - sound: str = "..." - -@dataclass -class Dog(Animal): - breed: str = "unknown" - sound: str = "woof" # Override default - -dog = Dog("Rex", breed="Lab") -``` - -### Utility Functions - -```python -from dataclasses import asdict, astuple, replace, fields - -point = Point(1.0, 2.0) -d = asdict(point) # {"x": 1.0, "y": 2.0} -t = astuple(point) # (1.0, 2.0) -p2 = replace(point, x=3.0) # Point(x=3.0, y=2.0) (3.13+: also copy.replace) -for f in fields(point): - print(f.name, f.type) -``` - -## Structural Pattern Matching (3.10+) - -### Basic Patterns - -```python -match status_code: - case 200: - handle_success() - case 301 | 302: - handle_redirect() - case 404: - handle_not_found() - case int(code) if code >= 500: - handle_server_error(code) - case _: - handle_unknown() -``` - -### Sequence and Mapping Patterns - -```python -match command.split(): - case ["quit"]: - quit() - case ["go", direction]: - move(direction) - case ["drop", *objects]: - drop_items(objects) - -match event: - case {"type": "click", "x": x, "y": y}: - handle_click(x, y) - case {"type": "keypress", "key": str(key)}: - handle_key(key) -``` - -### Class Patterns - -```python -from dataclasses import dataclass - -@dataclass -class Point: - x: float - y: float - -def describe(point: Point) -> str: - match point: - case Point(x=0, y=0): - return "origin" - case Point(x=0, y=y): - return f"on y-axis at {y}" - case Point(x=x, y=0): - return f"on x-axis at {x}" - case Point(x=x, y=y) if x == y: - return f"on diagonal at {x}" - case _: - return f"at ({point.x}, {point.y})" -``` - -### Guard Clauses and OR Patterns - -```python -match value: - case str(s) if len(s) > 100: - truncated = s[:100] + "..." - case str(s): - truncated = s - case int() | float() as num: - truncated = str(num) -``` - -### Nested Patterns - -```python -match config: - case {"database": {"host": str(host), "port": int(port)}}: - connect(host, port) - case {"database": {"url": str(url)}}: - connect_url(url) -``` - -## Async/Await - -### Basic Coroutines - -```python -import asyncio - -async def fetch_data(url: str) -> dict: - async with httpx.AsyncClient() as client: - response = await client.get(url) - return response.json() - -# Run from sync code -result = asyncio.run(fetch_data("https://api.example.com")) -``` - -### Concurrent Execution - -```python -async def fetch_all(urls: list[str]) -> list[dict]: - async with httpx.AsyncClient() as client: - tasks = [client.get(url) for url in urls] - responses = await asyncio.gather(*tasks) - return [r.json() for r in responses] - -# With error handling - return_exceptions prevents one failure cancelling all -results = await asyncio.gather(*tasks, return_exceptions=True) -for result in results: - if isinstance(result, Exception): - print(f"Failed: {result}") -``` - -### TaskGroup (3.11+, Preferred Over gather) - -```python -async def process_urls(urls: list[str]) -> list[str]: - results = [] - async with asyncio.TaskGroup() as tg: - for url in urls: - tg.create_task(fetch_and_store(url, results)) - return results # All tasks completed or ExceptionGroup raised -``` - -`TaskGroup` is preferred over `gather()` for structured concurrency: if one task fails, all others are cancelled. - -### Async Generators and Context Managers - -```python -# Async generator -async def stream_lines(url: str): - async with httpx.AsyncClient() as client: - async with client.stream("GET", url) as response: - async for line in response.aiter_lines(): - yield line - -# Async context manager -from contextlib import asynccontextmanager - -@asynccontextmanager -async def managed_connection(url: str): - conn = await connect(url) - try: - yield conn - finally: - await conn.close() - -async with managed_connection("postgres://...") as conn: - await conn.execute("SELECT 1") -``` - -### Async Iteration - -```python -# Async for loop -async for message in websocket: - await handle(message) - -# Async comprehension -results = [await process(item) async for item in aiter] -filtered = [x async for x in stream if await is_valid(x)] -``` - -### Semaphore for Concurrency Limiting - -```python -async def fetch_limited(urls: list[str], max_concurrent: int = 10) -> list[str]: - semaphore = asyncio.Semaphore(max_concurrent) - - async def fetch_one(url: str) -> str: - async with semaphore: - async with httpx.AsyncClient() as client: - resp = await client.get(url) - return resp.text - - return await asyncio.gather(*[fetch_one(url) for url in urls]) -``` - -## Walrus Operator `:=` (3.8+) - -```python -# Assign and test in one expression -if (n := len(data)) > 10: - print(f"Too much data: {n} items") - -# In while loops -while chunk := file.read(8192): - process(chunk) - -# In comprehensions -results = [y for x in data if (y := expensive(x)) is not None] -``` - -## F-String Features - -### Quote Reuse and Nesting (3.12+) - -```python -# Quote reuse - same quotes inside and outside -names = ["Alice", "Bob"] -msg = f"Users: {", ".join(names)}" - -# Nested f-strings -f"{f"{value:.2f}":>10}" - -# Multiline with comments (3.12+) -query = f""" - SELECT {", ".join([ - "id", # primary key - "name", # user name - "email", # contact - ])} - FROM users -""" -``` - -### Formatting Tricks - -```python -# Number formatting -f"{value:,.2f}" # 1,234.56 -f"{value:>10}" # Right-align in 10 chars -f"{value:010}" # Zero-pad to 10 digits -f"{ratio:.1%}" # 85.0% -f"{num:#x}" # 0xff (hex with prefix) -f"{num:_}" # 1_000_000 (digit separator) - -# Debug format (3.8+) -x = 42 -f"{x=}" # "x=42" -f"{x=:.2f}" # "x=42.00" -f"{x + 1=}" # "x + 1=43" - -# Datetime -from datetime import datetime -now = datetime.now() -f"{now:%Y-%m-%d %H:%M}" # "2026-02-08 14:30" -``` - -## Template Strings (3.14+) - -```python -# t-strings return Template objects, not strings -from string.templatelib import Template, Interpolation - -name = "world" -template = t"Hello {name}!" -# Template object with parts: ["Hello ", Interpolation(...), "!"] - -# Use for custom processing (HTML escaping, SQL, i18n, etc.) -def html(template: Template) -> str: - parts = [] - for item in template: - if isinstance(item, str): - parts.append(item) - elif isinstance(item, Interpolation): - parts.append(html_escape(str(item.value))) - return "".join(parts) - -safe = html(t"

{user_input}

") -``` - -## Exception Groups (3.11+) - -```python -# Raise multiple exceptions -raise ExceptionGroup("errors", [ - ValueError("bad value"), - TypeError("wrong type"), - KeyError("missing key"), -]) - -# Catch with except* -try: - async with asyncio.TaskGroup() as tg: - tg.create_task(op1()) - tg.create_task(op2()) -except* ValueError as eg: - for exc in eg.exceptions: - log_value_error(exc) -except* TypeError as eg: - for exc in eg.exceptions: - log_type_error(exc) -``` - -`except*` can match multiple groups; unmatched exceptions propagate. - -## Slots in Regular Classes - -```python -class Point: - __slots__ = ("x", "y") - - def __init__(self, x: float, y: float) -> None: - self.x = x - self.y = y - -# Benefits: less memory, faster attribute access, prevents typos -p = Point(1, 2) -p.z = 3 # AttributeError - caught at runtime -``` - -## Context Managers - -```python -from contextlib import contextmanager, suppress - -# Custom context manager -@contextmanager -def timer(label: str): - import time - start = time.perf_counter() - try: - yield - finally: - elapsed = time.perf_counter() - start - print(f"{label}: {elapsed:.3f}s") - -with timer("operation"): - do_work() - -# Suppress specific exceptions -with suppress(FileNotFoundError): - os.remove("temp.txt") - -# Multiple context managers (parenthesized, 3.10+) -with ( - open("input.txt") as fin, - open("output.txt", "w") as fout, -): - fout.write(fin.read()) -``` - -## Deferred Annotations (3.14+) - -```python -# Annotations are no longer evaluated eagerly -# Forward references work without quotes -class Tree: - def __init__(self, children: list[Tree]) -> None: - self.children = children - -# Access annotations programmatically -from annotationlib import get_annotations, Format - -get_annotations(Tree, format=Format.VALUE) # Evaluates -get_annotations(Tree, format=Format.FORWARDREF) # Returns ForwardRef objects -get_annotations(Tree, format=Format.STRING) # Returns strings -``` - -## Enum Patterns - -```python -from enum import Enum, auto, StrEnum - -# String enum (3.11+) -class Color(StrEnum): - RED = auto() # "red" - GREEN = auto() # "green" - BLUE = auto() # "blue" - -# Works directly as string -print(f"Color is {Color.RED}") # "Color is red" - -# Classic enum with values -class Status(Enum): - PENDING = "pending" - ACTIVE = "active" - ARCHIVED = "archived" - - @property - def is_active(self) -> bool: - return self == Status.ACTIVE - -# IntEnum for numeric enums that work as ints -from enum import IntEnum - -class Priority(IntEnum): - LOW = 1 - MEDIUM = 2 - HIGH = 3 - -# Enum with methods and pattern matching -class Shape(Enum): - CIRCLE = "circle" - SQUARE = "square" - TRIANGLE = "triangle" - -match shape: - case Shape.CIRCLE: - area = math.pi * r**2 - case Shape.SQUARE: - area = side**2 -``` - -## Abstract Base Classes vs Protocols - -```python -from abc import ABC, abstractmethod -from typing import Protocol - -# ABC: Nominal typing - classes must explicitly inherit -class Animal(ABC): - @abstractmethod - def speak(self) -> str: ... - - @abstractmethod - def move(self) -> None: ... - -class Dog(Animal): # Must inherit Animal - def speak(self) -> str: - return "Woof" - - def move(self) -> None: - print("Running") - -# Protocol: Structural typing - no inheritance needed -class Speaker(Protocol): - def speak(self) -> str: ... - -class Cat: # No inheritance, but satisfies Speaker - def speak(self) -> str: - return "Meow" - -def announce(speaker: Speaker) -> None: - print(speaker.speak()) - -announce(Cat()) # Works - Cat has speak() method -announce(Dog()) # Also works -``` - -Use **Protocol** when you want duck typing with type safety. Use **ABC** when you want to enforce inheritance and shared implementation. - -## Itertools and Functools Patterns - -```python -import itertools -from functools import reduce, partial, cache, lru_cache - -# itertools.batched (3.12+) - split into fixed-size chunks -for batch in itertools.batched(range(10), 3): - print(batch) # (0, 1, 2), (3, 4, 5), (6, 7, 8), (9,) - -# itertools.chain - flatten iterables -combined = list(itertools.chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5] -flat = list(itertools.chain.from_iterable([[1, 2], [3, 4]])) # [1, 2, 3, 4] - -# itertools.groupby - group consecutive elements -from operator import itemgetter -data = [("a", 1), ("a", 2), ("b", 3), ("b", 4)] -for key, group in itertools.groupby(data, key=itemgetter(0)): - print(key, list(group)) - -# itertools.product - cartesian product -for x, y in itertools.product([1, 2], ["a", "b"]): - print(x, y) # (1, "a"), (1, "b"), (2, "a"), (2, "b") - -# functools.cache - unbounded memoization (3.9+) -@cache -def fibonacci(n: int) -> int: - if n < 2: - return n - return fibonacci(n - 1) + fibonacci(n - 2) - -# functools.lru_cache - bounded memoization -@lru_cache(maxsize=256) -def expensive(key: str) -> Result: - return compute(key) - -# functools.partial - pre-fill function arguments -from functools import partial -int_from_hex = partial(int, base=16) -int_from_hex("ff") # 255 - -# functools.reduce -total = reduce(lambda acc, x: acc + x, [1, 2, 3, 4], 0) # 10 -``` - -## Pathlib (Modern File Operations) - -```python -from pathlib import Path - -# Create paths -p = Path("src") / "mypackage" / "core.py" -home = Path.home() -cwd = Path.cwd() - -# Read and write -content = Path("config.json").read_text() -Path("output.txt").write_text("hello") -data = Path("image.png").read_bytes() - -# Inspect -p.exists() -p.is_file() -p.is_dir() -p.suffix # ".py" -p.stem # "core" -p.name # "core.py" -p.parent # Path("src/mypackage") -p.parts # ("src", "mypackage", "core.py") - -# Glob -for py_file in Path("src").rglob("*.py"): - print(py_file) - -# Walk directories (3.12+) -for dirpath, dirnames, filenames in Path("src").walk(): - for f in filenames: - print(dirpath / f) - -# Copy and move (3.14+) -Path("source.txt").copy("dest.txt") -Path("old.txt").move("new.txt") -Path("src_dir").copy("dst_dir") # Recursive directory copy - -# Resolve and relative -p.resolve() # Absolute path -p.relative_to(Path("src")) # Relative from base -``` - -## Descriptors and Properties - -```python -# Property - controlled attribute access -class Temperature: - def __init__(self, celsius: float) -> None: - self._celsius = celsius - - @property - def fahrenheit(self) -> float: - return self._celsius * 9 / 5 + 32 - - @fahrenheit.setter - def fahrenheit(self, value: float) -> None: - self._celsius = (value - 32) * 5 / 9 - - @property - def celsius(self) -> float: - return self._celsius - -t = Temperature(100) -print(t.fahrenheit) # 212.0 -t.fahrenheit = 32 -print(t.celsius) # 0.0 - -# Cached property (3.8+ functools, or 3.12+ no dependency) -from functools import cached_property - -class DataProcessor: - def __init__(self, path: str) -> None: - self.path = path - - @cached_property - def data(self) -> list[dict]: - """Computed once, then cached as instance attribute.""" - return load_expensive_data(self.path) -``` - -## Collections Patterns - -```python -from collections import defaultdict, Counter, deque, OrderedDict, namedtuple - -# Counter - count occurrences -words = ["apple", "banana", "apple", "cherry", "banana", "apple"] -counts = Counter(words) -counts.most_common(2) # [("apple", 3), ("banana", 2)] - -# defaultdict - auto-initialize missing keys -graph: defaultdict[str, list[str]] = defaultdict(list) -graph["a"].append("b") # No KeyError - -# deque - efficient double-ended queue -history: deque[str] = deque(maxlen=10) -history.append("page1") -history.appendleft("page0") - -# Named tuple (prefer dataclass for new code) -Point = namedtuple("Point", ["x", "y"]) -p = Point(1, 2) -print(p.x, p.y) - -# Typed named tuple (3.6+, better than namedtuple) -from typing import NamedTuple - -class Point(NamedTuple): - x: float - y: float - label: str = "origin" -``` diff --git a/packages/dotfiles/dot_agents/skills/python-helper/references/releases.md b/packages/dotfiles/dot_agents/skills/python-helper/references/releases.md new file mode 100644 index 0000000000..678e926e2a --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/python-helper/references/releases.md @@ -0,0 +1,66 @@ +# Python release lifecycle + +Read this when selecting a Python version, adopting a 3.14 feature, or evaluating a 3.15 prerelease capability. + +## Status + +Python 3.14.6 was current stable on 2026-08-03, with 3.14.7 scheduled for the following day. Record the verification date with patch claims. Python 3.15 remained beta, with final release scheduled for 2026-10-01. + +Python 3.14 includes deferred annotations, template strings, subinterpreters, Zstandard compression, external debugger attachment, and officially supported free threading. Each feature has distinct security and compatibility boundaries described in the other references. + +Python 3.15 prerelease documentation includes lazy imports, `frozendict`, sentinels, comprehension unpacking, UTF-8 default encoding, `TypeForm`, and a free-threaded stable ABI. Treat beta behavior as changeable until final. + +## Research ledger + +The following 48 authoritative pages were fetched and inspected: + +1. [Python downloads](https://www.python.org/downloads/) +2. [Python version status](https://devguide.python.org/versions/) +3. [What's New in Python 3.14](https://docs.python.org/3/whatsnew/3.14.html) +4. [What's New in Python 3.15](https://docs.python.org/3.15/whatsnew/3.15.html) +5. [Python 3.14 schedule](https://peps.python.org/pep-0745/) +6. [Python 3.15 schedule](https://peps.python.org/pep-0790/) +7. [annotationlib](https://docs.python.org/3/library/annotationlib.html) +8. [string.templatelib](https://docs.python.org/3/library/string.templatelib.html) +9. [concurrent.interpreters](https://docs.python.org/3/library/concurrent.interpreters.html) +10. [Free-threading HOWTO](https://docs.python.org/3/howto/free-threading-python.html) +11. [compression.zstd](https://docs.python.org/3/library/compression.zstd.html) +12. [pdb](https://docs.python.org/3/library/pdb.html) +13. [pathlib](https://docs.python.org/3/library/pathlib.html) +14. [asyncio tasks](https://docs.python.org/3/library/asyncio-task.html) +15. [typing](https://docs.python.org/3/library/typing.html) +16. [venv](https://docs.python.org/3/library/venv.html) +17. [Packaging Python projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/) +18. [Writing pyproject.toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/) +19. [pip install](https://pip.pypa.io/en/stable/cli/pip_install/) +20. [pip lock](https://pip.pypa.io/en/stable/cli/pip_lock/) +21. [Secure pip installs](https://pip.pypa.io/en/stable/topics/secure-installs/) +22. [uv projects](https://docs.astral.sh/uv/guides/projects/) +23. [uv project layout](https://docs.astral.sh/uv/concepts/projects/layout/) +24. [uv locking and syncing](https://docs.astral.sh/uv/concepts/projects/sync/) +25. [uv project configuration](https://docs.astral.sh/uv/concepts/projects/config/) +26. [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) +27. [uv packaging](https://docs.astral.sh/uv/guides/package/) +28. [uv publishing](https://docs.astral.sh/uv/guides/publish/) +29. [uv pip interface](https://docs.astral.sh/uv/pip/) +30. [uv CLI](https://docs.astral.sh/uv/reference/cli/) +31. [pytest fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html) +32. [pytest parametrization](https://docs.pytest.org/en/stable/how-to/parametrize.html) +33. [pytest integration practices](https://docs.pytest.org/en/stable/explanation/goodpractices.html) +34. [pytest changelog](https://docs.pytest.org/en/stable/changelog.html) +35. [Ruff configuration](https://docs.astral.sh/ruff/configuration/) +36. [Ruff formatter](https://docs.astral.sh/ruff/formatter/) +37. [Ruff linter](https://docs.astral.sh/ruff/linter/) +38. [Ruff versioning](https://docs.astral.sh/ruff/versioning/) +39. [Mypy release notes](https://mypy.readthedocs.io/en/stable/changelog.html) +40. [Pyright configuration](https://microsoft.github.io/pyright/#/configuration) +41. [pytest-asyncio concepts](https://pytest-asyncio.readthedocs.io/en/stable/concepts.html) +42. [pytest-xdist distribution](https://pytest-xdist.readthedocs.io/en/stable/distribution.html) +43. [pytest-cov configuration](https://pytest-cov.readthedocs.io/en/latest/config.html) +44. [pip-audit](https://pypi.org/project/pip-audit/) +45. [subprocess](https://docs.python.org/3/library/subprocess.html) +46. [secrets](https://docs.python.org/3/library/secrets.html) +47. [pickle](https://docs.python.org/3/library/pickle.html) +48. [tarfile](https://docs.python.org/3/library/tarfile.html) + +The archived [nest_asyncio repository](https://github.com/erdewit/nest_asyncio) was also inspected to verify that it should be removed from recommendations. diff --git a/packages/dotfiles/dot_agents/skills/python-helper/references/testing-and-concurrency.md b/packages/dotfiles/dot_agents/skills/python-helper/references/testing-and-concurrency.md new file mode 100644 index 0000000000..c76bdce456 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/python-helper/references/testing-and-concurrency.md @@ -0,0 +1,49 @@ +# Python testing and concurrency + +Read this when writing pytest suites, async tests, task groups, subinterpreters, free-threaded code, or profiling/debugging current Python. + +## pytest structure + +Yield fixtures are the clearest setup/teardown pattern. Teardown runs in reverse order; design fixtures so partial setup cannot leak a resource. + +Parametrize through fixtures, decorators, or collection hooks. Assert exact outcomes rather than only truthiness or object existence. + +For new projects, pytest recommends `--import-mode=importlib`. A src layout plus an editable install helps ensure tests import the intended package rather than a working-directory shadow. + +## Async tests + +pytest-asyncio defaults to strict mode for coexistence with other async-framework plugins. Auto mode is convenient only when asyncio owns the suite. + +Use a controlled HTTP server or transport fake. Do not call public example endpoints in unit or integration tests. + +## TaskGroup and gather + +`TaskGroup` cancels sibling tasks when a non-cancellation exception escapes and reports grouped failures. `gather()` preserves input result ordering but does not cancel siblings simply because one awaitable fails. `return_exceptions=True` converts failures into result values. + +Choose from required failure semantics; neither API is a universal replacement for the other. + +## Subinterpreters + +`concurrent.interpreters` provides isolated interpreter state and separate GILs. Interpreters are not a security boundary, and native extensions can violate isolation assumptions. + +## Free threading + +Official free-threaded builds are supported in Python 3.14. Extension import may re-enable the GIL. Built-in internal locking is not a future thread-safety contract, and single-thread overhead depends on workload. Protect shared invariants explicitly and test with the actual extension set. + +## Debugging + +Python 3.14 adds `pdb -p/--pid` attach and `set_trace_async()`. Attaching to a process blocked in I/O may wait until it executes bytecode or receives a signal. + +## Primary documentation + +- [pytest fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html) +- [pytest parametrization](https://docs.pytest.org/en/stable/how-to/parametrize.html) +- [pytest integration practices](https://docs.pytest.org/en/stable/explanation/goodpractices.html) +- [pytest changelog](https://docs.pytest.org/en/stable/changelog.html) +- [pytest-asyncio concepts](https://pytest-asyncio.readthedocs.io/en/stable/concepts.html) +- [pytest-xdist distribution](https://pytest-xdist.readthedocs.io/en/stable/distribution.html) +- [pytest-cov configuration](https://pytest-cov.readthedocs.io/en/latest/config.html) +- [asyncio tasks](https://docs.python.org/3/library/asyncio-task.html) +- [concurrent interpreters](https://docs.python.org/3/library/concurrent.interpreters.html) +- [Free-threading HOWTO](https://docs.python.org/3/howto/free-threading-python.html) +- [pdb](https://docs.python.org/3/library/pdb.html) diff --git a/packages/dotfiles/dot_agents/skills/python-helper/references/testing-patterns.md b/packages/dotfiles/dot_agents/skills/python-helper/references/testing-patterns.md deleted file mode 100644 index 8a453541f1..0000000000 --- a/packages/dotfiles/dot_agents/skills/python-helper/references/testing-patterns.md +++ /dev/null @@ -1,722 +0,0 @@ -# Python Testing Patterns - -Guide to testing Python code with pytest, unittest, mocking, coverage, hypothesis (property-based testing), and debugging with pdb/debugpy. - -## pytest - -### Basic Test Structure - -```python -# tests/test_math.py -def test_addition(): - assert 1 + 1 == 2 - -def test_string_contains(): - result = "hello world" - assert "world" in result - -def test_exception_raised(): - with pytest.raises(ValueError, match="invalid"): - int("not_a_number") - -def test_approximate(): - assert 0.1 + 0.2 == pytest.approx(0.3) - -class TestCalculator: - def test_add(self): - calc = Calculator() - assert calc.add(2, 3) == 5 - - def test_divide_by_zero(self): - calc = Calculator() - with pytest.raises(ZeroDivisionError): - calc.divide(1, 0) -``` - -### Fixtures - -Fixtures provide test dependencies. They are functions decorated with `@pytest.fixture` and injected by name. - -```python -import pytest - -@pytest.fixture -def sample_user(): - return User(name="Alice", email="alice@example.com") - -@pytest.fixture -def db_connection(): - conn = create_connection() - yield conn # Setup above, teardown below - conn.close() - -def test_user_name(sample_user): - assert sample_user.name == "Alice" - -def test_query(db_connection): - result = db_connection.execute("SELECT 1") - assert result == 1 -``` - -### Fixture Scopes - -```python -@pytest.fixture(scope="function") # Default: new per test -def per_test(): ... - -@pytest.fixture(scope="class") # Shared within test class -def per_class(): ... - -@pytest.fixture(scope="module") # Shared within test file -def per_module(): ... - -@pytest.fixture(scope="session") # Shared across entire run -def per_session(): ... -``` - -### Fixture Factories - -Return a callable for flexible test data creation: - -```python -@pytest.fixture -def make_user(): - created = [] - - def _make_user(name: str = "Test", **kwargs) -> User: - user = User(name=name, **kwargs) - created.append(user) - return user - - yield _make_user - - # Cleanup all created users - for user in created: - user.delete() - -def test_multiple_users(make_user): - alice = make_user("Alice", role="admin") - bob = make_user("Bob", role="viewer") - assert alice.role != bob.role -``` - -### Autouse Fixtures - -```python -@pytest.fixture(autouse=True) -def reset_environment(): - """Automatically runs for every test in this module.""" - os.environ["MODE"] = "test" - yield - os.environ.pop("MODE", None) -``` - -### Parametrized Fixtures - -```python -@pytest.fixture(params=["sqlite", "postgres", "mysql"]) -def db_backend(request): - backend = create_backend(request.param) - yield backend - backend.teardown() - -def test_insert(db_backend): - # Runs 3 times, once per backend - db_backend.insert({"key": "value"}) - assert db_backend.count() == 1 -``` - -### conftest.py - -Shared fixtures go in `conftest.py`. Pytest discovers them automatically. - -```python -# tests/conftest.py -import pytest - -@pytest.fixture(scope="session") -def app(): - """Create application instance for entire test session.""" - app = create_app(testing=True) - yield app - -@pytest.fixture -def client(app): - """Create test client for each test.""" - return app.test_client() - -@pytest.fixture(autouse=True) -def clean_db(app): - """Reset database before each test.""" - with app.app_context(): - db.create_all() - yield - db.session.rollback() - db.drop_all() -``` - -Multiple `conftest.py` files can exist at different directory levels. Inner ones override outer ones. - -### Parametrize - -```python -@pytest.mark.parametrize("input,expected", [ - ("hello", 5), - ("", 0), - ("world!", 6), -]) -def test_string_length(input, expected): - assert len(input) == expected - -# Multiple parametrize decorators create cartesian product -@pytest.mark.parametrize("x", [1, 2]) -@pytest.mark.parametrize("y", [10, 20]) -def test_multiply(x, y): - # Runs 4 times: (1,10), (1,20), (2,10), (2,20) - assert x * y > 0 - -# With IDs for readable output -@pytest.mark.parametrize("path,status", [ - pytest.param("/", 200, id="homepage"), - pytest.param("/missing", 404, id="not-found"), - pytest.param("/admin", 403, id="forbidden"), -]) -def test_routes(client, path, status): - response = client.get(path) - assert response.status_code == status - -# Skip/xfail specific parameters -@pytest.mark.parametrize("n", [ - 1, - 2, - pytest.param(3, marks=pytest.mark.skip(reason="not implemented")), - pytest.param(4, marks=pytest.mark.xfail(reason="known bug")), -]) -def test_process(n): - assert process(n) is not None -``` - -### Markers - -```python -# Define custom markers in pyproject.toml: -# [tool.pytest.ini_options] -# markers = [ -# "slow: marks tests as slow", -# "integration: marks integration tests", -# ] - -@pytest.mark.slow -def test_large_dataset(): - process_million_records() - -@pytest.mark.integration -def test_api_call(): - response = call_external_api() - assert response.ok - -# Built-in markers -@pytest.mark.skip(reason="Not implemented yet") -def test_future_feature(): ... - -@pytest.mark.skipif(sys.platform == "win32", reason="Unix only") -def test_unix_paths(): ... - -@pytest.mark.xfail(reason="Known bug #123", strict=True) -def test_known_bug(): ... - -@pytest.mark.timeout(5) # Requires pytest-timeout -def test_performance(): ... -``` - -```bash -# Run by marker -pytest -m slow -pytest -m "not slow" -pytest -m "integration and not slow" -``` - -### Async Testing (pytest-asyncio) - -```python -import pytest - -# With asyncio_mode = "auto" in pyproject.toml: -async def test_async_fetch(): - result = await fetch_data("https://example.com") - assert result is not None - -# Or explicit marker: -@pytest.mark.asyncio -async def test_async_operation(): - result = await async_process(data) - assert result.success - -# Async fixtures -@pytest.fixture -async def async_client(): - async with httpx.AsyncClient() as client: - yield client - -async def test_with_client(async_client): - response = await async_client.get("https://example.com") - assert response.status_code == 200 -``` - -### Temporary Files and Directories - -```python -def test_file_processing(tmp_path): - """tmp_path is a pathlib.Path to a temp directory.""" - input_file = tmp_path / "input.txt" - input_file.write_text("hello world") - - output_file = tmp_path / "output.txt" - process_file(input_file, output_file) - - assert output_file.read_text() == "HELLO WORLD" - -def test_config(tmp_path, monkeypatch): - """monkeypatch modifies environment/attributes safely.""" - config = tmp_path / "config.json" - config.write_text('{"debug": true}') - monkeypatch.setenv("CONFIG_PATH", str(config)) - - app = App() - assert app.debug is True -``` - -### Common pytest Plugins - -| Plugin | Purpose | -| ------------------ | ------------------------ | -| `pytest-cov` | Coverage reporting | -| `pytest-asyncio` | Async test support | -| `pytest-xdist` | Parallel test execution | -| `pytest-mock` | Simplified mocking | -| `pytest-timeout` | Test timeout enforcement | -| `pytest-randomly` | Randomize test order | -| `pytest-benchmark` | Performance benchmarks | -| `pytest-httpx` | Mock httpx requests | -| `pytest-freezegun` | Freeze time in tests | - -## unittest (Standard Library) - -```python -import unittest - -class TestStringMethods(unittest.TestCase): - def setUp(self): - self.data = "hello" - - def tearDown(self): - pass - - def test_upper(self): - self.assertEqual(self.data.upper(), "HELLO") - - def test_isupper(self): - self.assertTrue("HELLO".isupper()) - self.assertFalse("Hello".isupper()) - - def test_split(self): - self.assertEqual("a-b-c".split("-"), ["a", "b", "c"]) - with self.assertRaises(TypeError): - "hello".split(2) - -if __name__ == "__main__": - unittest.main() -``` - -pytest can run unittest-style tests without changes. - -## Mocking - -### unittest.mock - -```python -from unittest.mock import Mock, MagicMock, patch, AsyncMock - -# Basic mock -mock = Mock() -mock.method.return_value = 42 -assert mock.method() == 42 -mock.method.assert_called_once() - -# Patch a module attribute -@patch("myapp.services.requests.get") -def test_fetch(mock_get): - mock_get.return_value.json.return_value = {"key": "value"} - mock_get.return_value.status_code = 200 - - result = fetch_data("https://api.example.com") - assert result == {"key": "value"} - mock_get.assert_called_once_with("https://api.example.com") - -# Patch as context manager -def test_time(): - with patch("myapp.utils.time") as mock_time: - mock_time.time.return_value = 1000.0 - assert get_timestamp() == 1000.0 - -# Async mock (3.8+) -@patch("myapp.client.fetch", new_callable=AsyncMock) -async def test_async_fetch(mock_fetch): - mock_fetch.return_value = {"data": "result"} - result = await process() - assert result == {"data": "result"} -``` - -### pytest-mock (Simplified Interface) - -```python -def test_with_mocker(mocker): - # mocker is a fixture from pytest-mock - mock_send = mocker.patch("myapp.email.send_email") - mock_send.return_value = True - - result = register_user("alice@example.com") - - assert result.success - mock_send.assert_called_once_with( - to="alice@example.com", - subject="Welcome!", - ) - -def test_spy(mocker): - # Spy: call the real function but track calls - spy = mocker.spy(mymodule, "expensive_function") - result = mymodule.process() - assert spy.call_count == 1 -``` - -### Patching Best Practices - -```python -# Patch where it's USED, not where it's DEFINED -# If myapp.views imports requests: -@patch("myapp.views.requests.get") # Correct -@patch("requests.get") # Wrong - patches the original, not the import - -# Use spec to catch attribute errors -mock = Mock(spec=MyClass) -mock.nonexistent_method() # Raises AttributeError - -# Use autospec for even stricter checking -@patch("myapp.services.MyService", autospec=True) -def test_service(MockService): - instance = MockService.return_value - instance.process.return_value = "result" -``` - -## Coverage - -```bash -# Run with coverage -pytest --cov=src --cov-report=term-missing - -# HTML report -pytest --cov=src --cov-report=html -open htmlcov/index.html - -# Fail if coverage below threshold -pytest --cov=src --cov-fail-under=80 - -# Multiple report formats -pytest --cov=src --cov-report=term --cov-report=xml --cov-report=html -``` - -```toml -# pyproject.toml -[tool.coverage.run] -source = ["src"] -branch = true -omit = ["*/tests/*", "*/__main__.py"] - -[tool.coverage.report] -show_missing = true -skip_empty = true -fail_under = 80 -exclude_lines = [ - "pragma: no cover", - "if TYPE_CHECKING:", - "if __name__ == .__main__.", - "@overload", -] -``` - -### Coverage Pragmas - -```python -if TYPE_CHECKING: # pragma: no cover - from expensive_module import Type - -def debug_only(): # pragma: no cover - """Only runs in debug mode.""" - ... -``` - -## Hypothesis (Property-Based Testing) - -Hypothesis generates random test inputs to find edge cases you wouldn't think of. - -### Basic Usage - -```python -from hypothesis import given, assume, settings -from hypothesis import strategies as st - -@given(st.integers(), st.integers()) -def test_addition_commutative(a, b): - assert a + b == b + a - -@given(st.lists(st.integers())) -def test_sort_idempotent(lst): - assert sorted(sorted(lst)) == sorted(lst) - -@given(st.text()) -def test_roundtrip_encode_decode(s): - assert s.encode("utf-8").decode("utf-8") == s -``` - -### Strategies - -```python -# Primitive types -st.integers() # Any integer -st.integers(min_value=0, max_value=100) # Bounded -st.floats(allow_nan=False) # Floats without NaN -st.text(min_size=1, max_size=100) # Non-empty strings -st.booleans() # True/False -st.none() # None -st.binary() # bytes - -# Collections -st.lists(st.integers(), min_size=1) # Non-empty list of ints -st.tuples(st.integers(), st.text()) # (int, str) -st.dictionaries(st.text(), st.integers()) # {str: int} -st.frozensets(st.integers()) # frozenset[int] - -# Combining strategies -st.one_of(st.integers(), st.text()) # int | str -st.integers() | st.text() # Same as one_of - -# Filtered -st.integers().filter(lambda x: x % 2 == 0) # Even numbers - -# Mapped -st.integers(1, 100).map(str) # "1" through "100" - -# From regex -st.from_regex(r"[a-z]+@[a-z]+\.[a-z]{2,4}") -``` - -### Composite Strategies - -```python -from hypothesis import strategies as st -from hypothesis.strategies import composite - -@composite -def ordered_pair(draw): - a = draw(st.integers()) - b = draw(st.integers(min_value=a)) - return (a, b) - -@given(ordered_pair()) -def test_ordered(pair): - a, b = pair - assert a <= b - -# Build dataclass instances -@st.composite -def users(draw): - return User( - name=draw(st.text(min_size=1, max_size=50)), - age=draw(st.integers(min_value=0, max_value=150)), - email=draw(st.emails()), - ) - -@given(users()) -def test_user_valid(user): - assert user.name - assert 0 <= user.age <= 150 -``` - -### Settings and Assume - -```python -from hypothesis import given, assume, settings, HealthCheck - -@given(st.integers(), st.integers()) -def test_division(a, b): - assume(b != 0) # Skip inputs where b is 0 - result = a / b - assert result * b == pytest.approx(a) - -@settings( - max_examples=500, # More test cases (default 100) - deadline=None, # No time limit per example - suppress_health_check=[HealthCheck.too_slow], -) -@given(st.lists(st.integers(), min_size=1000)) -def test_large_sort(data): - assert sorted(data) == sorted(data) -``` - -### Stateful Testing - -```python -from hypothesis.stateful import RuleBasedStateMachine, rule, initialize - -class DatabaseStateMachine(RuleBasedStateMachine): - @initialize() - def setup(self): - self.db = Database() - self.model = {} - - @rule(key=st.text(), value=st.integers()) - def insert(self, key, value): - self.db.insert(key, value) - self.model[key] = value - - @rule(key=st.text()) - def query(self, key): - db_result = self.db.get(key) - model_result = self.model.get(key) - assert db_result == model_result - -TestDatabase = DatabaseStateMachine.TestCase -``` - -## Debugging - -### pdb (Built-in Debugger) - -```python -# Set breakpoint in code -breakpoint() # Drops into pdb (3.7+) - -# Or explicitly -import pdb; pdb.set_trace() - -# Common pdb commands: -# n (next) - Execute next line -# s (step) - Step into function -# c (continue) - Continue to next breakpoint -# p expr - Print expression -# pp expr - Pretty print -# l (list) - Show source code -# w (where) - Show stack trace -# u (up) - Go up in stack -# d (down) - Go down in stack -# b N - Set breakpoint at line N -# cl N - Clear breakpoint at line N -# q (quit) - Quit debugger -``` - -```bash -# Run script under debugger -python -m pdb script.py - -# Pytest drops into pdb on failure -pytest --pdb - -# Break on first failure -pytest -x --pdb - -# Remote debugging (3.14+) -python -m pdb -p # Attach to running process -``` - -### Async Debugging (3.14+) - -```python -# Set async-aware breakpoint -import pdb; pdb.set_trace_async() - -# In pdb, access current async task -# (Pdb) $_asynctask -``` - -### debugpy (VS Code Debugger) - -```python -# Attach VS Code debugger to running process -import debugpy -debugpy.listen(5678) -debugpy.wait_for_client() # Pause until VS Code connects - -# In VS Code launch.json: -# { -# "type": "debugpy", -# "request": "attach", -# "connect": {"host": "localhost", "port": 5678} -# } -``` - -### Pytest Debug Helpers - -```bash -# Show local variables on failure -pytest -l - -# Verbose traceback -pytest --tb=long -pytest --tb=short -pytest --tb=no - -# Drop into pdb on first failure -pytest --pdb -x - -# Drop into debugger at start of each test -pytest --trace - -# Show print output even for passing tests -pytest -s -pytest --capture=no -``` - -## Test Organization Best Practices - -``` -project/ - src/ - mypackage/ - __init__.py - core.py - utils.py - tests/ - conftest.py # Shared fixtures - test_core.py # Unit tests for core.py - test_utils.py # Unit tests for utils.py - integration/ - conftest.py # Integration-specific fixtures - test_api.py - test_database.py - e2e/ - test_workflow.py -``` - -### Naming Conventions - -- Test files: `test_.py` or `_test.py` -- Test functions: `test_` -- Test classes: `Test` -- Fixtures: descriptive nouns (`sample_user`, `db_connection`) - -### Test Isolation - -```python -# Use monkeypatch for environment changes -def test_config(monkeypatch): - monkeypatch.setenv("DATABASE_URL", "sqlite:///test.db") - monkeypatch.setattr(settings, "DEBUG", True) - monkeypatch.delenv("SECRET_KEY", raising=False) - -# Use tmp_path for file operations -def test_export(tmp_path): - output = tmp_path / "export.csv" - export_data(output) - assert output.exists() -``` diff --git a/packages/dotfiles/dot_agents/skills/python-helper/references/tooling-and-packaging.md b/packages/dotfiles/dot_agents/skills/python-helper/references/tooling-and-packaging.md new file mode 100644 index 0000000000..eb09e48ab1 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/python-helper/references/tooling-and-packaging.md @@ -0,0 +1,79 @@ +# Python tooling and packaging + +Read this when configuring uv, pip, pyproject.toml, publishing, Ruff, mypy, or Pyright. + +## uv project workflow + +The project interface manages environments, lockfiles, synchronization, dependencies, and commands. Commit `uv.lock`. + +```bash +uv sync --locked +uv run pytest +uv add +uv add --dev +``` + +`--locked` verifies freshness. `--frozen` trusts the existing lock without checking project metadata. Use workspaces only when members can share one lock, environment, and combined `requires-python` range. + +## Dependency categories + +`[dependency-groups].dev` is for development dependencies. `[project.optional-dependencies]` defines extras that consumers can install from the built package. + +The `uv pip` interface directly manages an environment and is intentionally not completely pip-compatible. Use it for low-level or migration workflows, not as a synonym for uv projects. + +## Build and publish + +Build published packages without local workspace source overrides: + +```bash +uv build --no-sources +``` + +Prefer trusted publishing. If credentials are necessary, use the documented environment variable or stdin path rather than expanding a token into a command argument. Publication needs explicit authorization. + +## pip + +Requirements select installations; constraints limit versions without requesting installation. `pip freeze` records one environment. `pip lock` is experimental and platform/Python-specific. + +Hash checking is all-or-nothing: every requirement and dependency needs a hash. `--only-binary :all:` removes source-build risk at the cost of requiring compatible wheels. + +## Ruff + +Ruff can infer Python target version from `requires-python`. Safe fixes intend to preserve behavior; unsafe fixes may change behavior or remove comments. Preview behavior has weaker stability, and Ruff's pre-1.0 versioning permits breaking minor releases. + +Remove obsolete rules such as `ANN101` from copied configurations. Review every enabled rule set against the pinned Ruff version. + +## Type-checkers + +Mypy 2.3 documentation notes that its native parser is not yet the default. Pyright defaults to standard mode and supports strict mode and per-environment Python/platform settings. + +Use the repository's chosen checker and configuration. Do not add `--ignore-missing-imports` as a blanket escape hatch. + +## Auditing and images + +Use `uv run --with pip-audit pip-audit` to audit the project's synced environment for the current interpreter and platform; it works regardless of uv version. This audits only that one active environment, not every locked platform/dependency combination in the lockfile — pass `-r ` to additionally audit a specific requirements file. Use `uv run --with`, not `uvx` — `uvx pip-audit` runs in its own isolated tool environment and audits pip-audit's dependencies rather than the project's, while `uv run --with pip-audit` layers pip-audit onto the project's synced environment. `uv audit` remains a preview-only subcommand (behind `--preview`) and is absent entirely in older uv releases, so do not depend on it unconditionally. Vulnerability findings require reachability and remediation review; the command must remain a failing gate when policy requires it. + +Pin container tools by reviewed version or immutable digest. Do not copy `latest` into a reproducible build. + +## Primary documentation + +- [Packaging Python projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/) +- [Writing pyproject.toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/) +- [pip install](https://pip.pypa.io/en/stable/cli/pip_install/) +- [pip lock](https://pip.pypa.io/en/stable/cli/pip_lock/) +- [Secure pip installs](https://pip.pypa.io/en/stable/topics/secure-installs/) +- [uv projects](https://docs.astral.sh/uv/guides/projects/) +- [uv project layout](https://docs.astral.sh/uv/concepts/projects/layout/) +- [uv locking and syncing](https://docs.astral.sh/uv/concepts/projects/sync/) +- [uv configuration](https://docs.astral.sh/uv/concepts/projects/config/) +- [uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) +- [uv packaging](https://docs.astral.sh/uv/guides/package/) +- [uv publishing](https://docs.astral.sh/uv/guides/publish/) +- [uv pip interface](https://docs.astral.sh/uv/pip/) +- [uv CLI](https://docs.astral.sh/uv/reference/cli/) +- [Ruff configuration](https://docs.astral.sh/ruff/configuration/) +- [Ruff formatter](https://docs.astral.sh/ruff/formatter/) +- [Ruff linter](https://docs.astral.sh/ruff/linter/) +- [Ruff versioning](https://docs.astral.sh/ruff/versioning/) +- [Mypy release notes](https://mypy.readthedocs.io/en/stable/changelog.html) +- [Pyright configuration](https://microsoft.github.io/pyright/#/configuration) diff --git a/packages/dotfiles/dot_agents/skills/python-helper/references/tooling-packaging.md b/packages/dotfiles/dot_agents/skills/python-helper/references/tooling-packaging.md deleted file mode 100644 index 9ce3d33a91..0000000000 --- a/packages/dotfiles/dot_agents/skills/python-helper/references/tooling-packaging.md +++ /dev/null @@ -1,845 +0,0 @@ -# Python Tooling and Packaging - -Guide to modern Python tooling: uv (package/project manager), ruff (linter+formatter), pip, poetry, venv, pyproject.toml, dependency management, mypy vs pyright, and build backends. - -## uv (Fast Python Package and Project Manager) - -uv is an extremely fast Python package and project manager written in Rust by Astral (same team as Ruff). It replaces pip, pip-tools, pipx, poetry, pyenv, virtualenv, and more in a single tool. It is 10-100x faster than pip. - -### Project Management - -```bash -# Initialize a new project -uv init my-project # Application (no src/ layout) -uv init --lib my-library # Library (src/ layout with py.typed) -uv init --package my-pkg # Installable package -uv init --script script.py # Single-file script with inline metadata - -# Add/remove dependencies -uv add requests httpx pydantic -uv add "fastapi>=0.110" -uv add --dev pytest ruff mypy coverage -uv add --optional docs mkdocs mkdocs-material -uv remove requests - -# Lock and sync -uv lock # Generate/update uv.lock -uv sync # Install from lockfile -uv sync --frozen # Error if lockfile out of date -uv sync --no-dev # Production only - -# Run commands in project environment -uv run python script.py -uv run pytest -v -uv run ruff check . -uv run -- python -m http.server 8000 - -# Build and publish -uv build # Build sdist and wheel -uv publish # Publish to PyPI -uv publish --token $PYPI_TOKEN -``` - -### Python Version Management - -```bash -# Install Python versions -uv python install 3.14 # Install specific version -uv python install 3.12 3.13 # Install multiple -uv python install --upgrade # Upgrade to latest patch - -# Pin project Python version -uv python pin 3.14 # Creates .python-version file - -# List available/installed versions -uv python list -uv python list --only-installed - -# Use specific version -uv run --python 3.13 script.py -uv venv --python 3.14 -``` - -### Tool Execution - -```bash -# Run tools without installing (like npx) -uvx ruff check . -uvx black --check . -uvx mypy src/ -uvx pytest -uvx cowsay "hello" - -# Install tools globally -uv tool install ruff -uv tool install "httpie>=3.0" - -# List/update/remove tools -uv tool list -uv tool upgrade ruff -uv tool uninstall ruff -``` - -### Virtual Environments - -```bash -# Create venv (uses uv's fast resolver) -uv venv # Creates .venv/ -uv venv --python 3.14 # Specific Python version -uv venv my-env # Custom name - -# pip-compatible interface -uv pip install requests -uv pip install -r requirements.txt -uv pip install -e ".[dev]" -uv pip compile requirements.in -o requirements.txt -uv pip sync requirements.txt -uv pip list -uv pip show requests -uv pip freeze -uv pip uninstall requests -``` - -### Workspaces - -```bash -# List workspace members -uv workspace list -uv workspace dir - -# Add workspace member -# In root pyproject.toml: -# [tool.uv.workspace] -# members = ["packages/*"] -``` - -### Inline Script Metadata - -```python -# script.py - dependencies declared inline -# /// script -# requires-python = ">=3.12" -# dependencies = [ -# "requests>=2.31", -# "rich>=13.0", -# ] -# /// - -import requests -from rich import print - -response = requests.get("https://api.example.com") -print(response.json()) -``` - -```bash -uv run script.py # Automatically creates ephemeral env with deps -``` - -## Ruff (Linter + Formatter) - -Ruff is an extremely fast Python linter and formatter written in Rust. It replaces Flake8, Black, isort, pydocstyle, pyupgrade, autoflake, and more. Over 800 built-in rules. - -### Commands - -```bash -# Linting -ruff check . # Check all files -ruff check src/ tests/ # Check specific directories -ruff check --fix . # Auto-fix safe fixes -ruff check --fix --unsafe-fixes . # Include unsafe fixes -ruff check --select E,F,I . # Only specific rules -ruff check --diff . # Show diff of fixes -ruff check --watch . # Watch mode - -# Formatting -ruff format . # Format all files -ruff format --check . # Check without changing -ruff format --diff . # Show diff -ruff format src/main.py # Format specific file - -# Information -ruff rule E501 # Explain a rule -ruff linter # List available linters -ruff version # Show version -``` - -### Configuration (pyproject.toml) - -```toml -[tool.ruff] -target-version = "py312" -line-length = 88 -# Exclude patterns -exclude = [ - ".venv", - "migrations", - "__pycache__", -] - -[tool.ruff.lint] -# Rule selection -select = [ - "E", # pycodestyle errors - "F", # Pyflakes - "I", # isort - "B", # flake8-bugbear - "UP", # pyupgrade - "N", # pep8-naming - "SIM", # flake8-simplify - "RUF", # Ruff-specific rules - "S", # flake8-bandit (security) - "PTH", # flake8-use-pathlib - "T20", # flake8-print (catch print statements) - "ANN", # flake8-annotations - "C4", # flake8-comprehensions - "DTZ", # flake8-datetimez - "PIE", # flake8-pie - "RET", # flake8-return - "TCH", # flake8-type-checking -] -ignore = [ - "E501", # Line too long (formatter handles this) - "ANN101", # Missing type annotation for self -] - -# Allow autofix for specific rules -fixable = ["ALL"] -unfixable = [] - -[tool.ruff.lint.per-file-ignores] -"tests/**/*.py" = [ - "S101", # Allow assert in tests - "ANN", # Don't require annotations in tests -] -"__init__.py" = ["F401"] # Allow unused imports in __init__ - -[tool.ruff.lint.isort] -known-first-party = ["mypackage"] -force-single-line = false -lines-after-imports = 2 - -[tool.ruff.lint.pydocstyle] -convention = "google" # or "numpy", "pep257" - -[tool.ruff.format] -quote-style = "double" -indent-style = "space" -skip-magic-trailing-comma = false -line-ending = "auto" -docstring-code-format = true -``` - -### Common Rule Categories - -| Code | Plugin | Purpose | -| ---- | --------------------- | ------------------------------------------------ | -| E/W | pycodestyle | Style errors and warnings | -| F | Pyflakes | Logical errors (unused imports, undefined names) | -| I | isort | Import sorting | -| B | flake8-bugbear | Common bugs and design problems | -| UP | pyupgrade | Modernize syntax for target Python version | -| N | pep8-naming | Naming conventions | -| S | flake8-bandit | Security issues | -| SIM | flake8-simplify | Simplifiable code | -| RUF | Ruff-specific | Ruff's own rules | -| PTH | flake8-use-pathlib | Prefer pathlib over os.path | -| T20 | flake8-print | Detect print() calls | -| C4 | flake8-comprehensions | Simplify comprehensions | -| TCH | flake8-type-checking | Move imports to TYPE_CHECKING | - -### Inline Suppressions - -```python -x = 1 # noqa: F841 -x = 1 # noqa: F841, E501 - -# Disable for block -# ruff: noqa: E501 - -# File-level -# ruff: noqa -``` - -## pip (Standard Package Installer) - -```bash -# Install packages -pip install requests -pip install "requests>=2.31,<3" -pip install -r requirements.txt -pip install -e ".[dev]" # Editable install with extras - -# List and inspect -pip list -pip list --outdated -pip show requests -pip freeze > requirements.txt - -# Uninstall -pip uninstall requests - -# Upgrade -pip install --upgrade requests -pip install --upgrade pip -``` - -## Poetry (Alternative Project Manager) - -```bash -# Create/init project -poetry new my-project -poetry init # In existing directory - -# Dependencies -poetry add requests -poetry add --group dev pytest ruff -poetry remove requests - -# Lock and install -poetry lock -poetry install -poetry install --no-dev - -# Run -poetry run python script.py -poetry run pytest - -# Build and publish -poetry build -poetry publish -``` - -Poetry 2.0+ (Jan 2025) uses standard `[project]` table in pyproject.toml instead of `[tool.poetry]`. - -## Virtual Environments - -### stdlib venv - -```bash -python -m venv .venv -source .venv/bin/activate # macOS/Linux (bash/zsh) -source .venv/bin/activate.fish # Fish shell -.venv/Scripts/activate # Windows - -deactivate # Exit venv - -# Recreate from scratch -rm -rf .venv && python -m venv .venv -``` - -### Why Virtual Environments Matter - -- Isolate project dependencies from system Python -- Prevent version conflicts between projects -- Reproducible environments via lockfiles -- uv/poetry handle venvs automatically - -## pyproject.toml - -The central configuration file for modern Python projects. Replaces setup.py, setup.cfg, requirements.txt, and tool-specific config files. - -### Full Example - -```toml -[project] -name = "my-awesome-package" -version = "1.0.0" -description = "A short description" -readme = "README.md" -license = "MIT" -requires-python = ">=3.12" -authors = [ - { name = "Your Name", email = "you@example.com" }, -] -keywords = ["python", "example"] -classifiers = [ - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", -] -dependencies = [ - "httpx>=0.27", - "pydantic>=2.0", - "click>=8.0", -] - -[project.optional-dependencies] -dev = [ - "pytest>=8.0", - "pytest-cov>=5.0", - "pytest-asyncio>=0.24", - "ruff>=0.8", - "mypy>=1.13", -] -docs = [ - "mkdocs>=1.6", - "mkdocs-material>=9.0", -] - -[project.scripts] -my-cli = "my_package.cli:main" - -[project.urls] -Homepage = "https://github.com/user/project" -Documentation = "https://project.readthedocs.io" -Repository = "https://github.com/user/project" - -# Build backend -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -# Tool configurations -[tool.pytest.ini_options] -testpaths = ["tests"] -addopts = "-ra -q --strict-markers" -asyncio_mode = "auto" -markers = [ - "slow: marks tests as slow (deselect with '-m \"not slow\"')", - "integration: marks integration tests", -] - -[tool.mypy] -python_version = "3.14" -strict = true -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = true - -[[tool.mypy.overrides]] -module = "tests.*" -disallow_untyped_defs = false - -[tool.coverage.run] -source = ["src"] -branch = true - -[tool.coverage.report] -show_missing = true -skip_empty = true -fail_under = 80 - -[tool.ruff] -target-version = "py312" -line-length = 88 - -[tool.ruff.lint] -select = ["E", "F", "I", "B", "UP"] -``` - -### Build Backends - -| Backend | Use Case | Config | -| --------------- | -------------------------------- | --------------------------------- | -| **hatchling** | Modern default, fast, extensible | `requires = ["hatchling"]` | -| **setuptools** | Legacy, most common historically | `requires = ["setuptools>=61.0"]` | -| **flit-core** | Simple pure-Python packages | `requires = ["flit_core>=3.4"]` | -| **pdm-backend** | PDM projects | `requires = ["pdm-backend"]` | -| **maturin** | Rust+Python packages | `requires = ["maturin>=1.0"]` | - -Hatchling is recommended for new projects. Setuptools if you need compatibility with older tooling. - -### Project Layouts - -``` -# src layout (recommended for libraries) -my-project/ - src/ - my_package/ - __init__.py - core.py - tests/ - test_core.py - pyproject.toml - -# flat layout (simpler, fine for applications) -my-project/ - my_package/ - __init__.py - core.py - tests/ - test_core.py - pyproject.toml -``` - -## mypy vs pyright - -### mypy (Python Foundation) - -```bash -mypy src/ -mypy --strict src/ -mypy --show-error-codes src/ -``` - -```toml -[tool.mypy] -python_version = "3.14" -strict = true -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = true -check_untyped_defs = true -no_implicit_reexport = true - -[[tool.mypy.overrides]] -module = "third_party_lib.*" -ignore_missing_imports = true -``` - -### pyright (Microsoft, used by VS Code/Pylance) - -```bash -pyright src/ -pyright --pythonversion 3.14 -``` - -```json -// pyrightconfig.json -{ - "pythonVersion": "3.14", - "typeCheckingMode": "strict", - "reportMissingImports": true, - "reportMissingTypeStubs": false, - "include": ["src"], - "exclude": ["**/__pycache__", ".venv"] -} -``` - -Or in pyproject.toml: - -```toml -[tool.pyright] -pythonVersion = "3.14" -typeCheckingMode = "strict" -include = ["src"] -``` - -### Comparison - -| Feature | mypy | pyright | -| ---------------- | ------------------- | --------------------- | -| Speed | Slower (Python) | Faster (Node.js) | -| VS Code | Via extension | Built into Pylance | -| Plugins | mypy plugins API | Limited | -| Protocol support | Good | Excellent | -| Error messages | Detailed | Detailed | -| Incremental | Yes | Yes | -| Daemon mode | mypy daemon (dmypy) | Built-in | -| Strictness | Configurable | Basic/Standard/Strict | - -Use **pyright** for VS Code integration and speed. Use **mypy** for CI, plugins, or specific features. Both can be used together. - -## Dependency Management Best Practices - -1. **Use uv** for new projects - fastest, most modern -2. **Lock dependencies** - `uv lock` or `poetry lock` for reproducible builds -3. **Pin direct deps loosely** - `httpx>=0.27` not `httpx==0.27.2` -4. **Separate dev deps** - `[project.optional-dependencies]` or `[dependency-groups]` -5. **Use extras** for optional features - `pip install "pkg[postgres]"` -6. **Keep lockfile in version control** - `uv.lock` or `poetry.lock` -7. **Audit regularly** - `pip audit` or `uv pip audit` for vulnerabilities - -## Common Project Scripts - -```toml -# In pyproject.toml with hatchling -[project.scripts] -my-cli = "my_package.cli:main" - -# Or entry points for plugins -[project.entry-points."my_app.plugins"] -my_plugin = "my_package.plugins:MyPlugin" -``` - -```bash -# Using uv run for dev tasks -uv run pytest -v -uv run ruff check --fix . -uv run ruff format . -uv run mypy src/ -uv run python -m my_package -``` - -## Docker Patterns for Python - -```dockerfile -# Multi-stage build with uv -FROM python:3.14-slim AS builder - -# Install uv -COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv - -WORKDIR /app -COPY pyproject.toml uv.lock ./ - -# Install dependencies (cached layer) -RUN uv sync --frozen --no-dev --no-editable - -COPY src/ src/ - -FROM python:3.14-slim AS runtime -WORKDIR /app -COPY --from=builder /app/.venv /app/.venv -COPY --from=builder /app/src /app/src - -ENV PATH="/app/.venv/bin:$PATH" -CMD ["python", "-m", "my_package"] -``` - -```dockerfile -# Simple Dockerfile with pip -FROM python:3.14-slim - -WORKDIR /app - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -CMD ["python", "main.py"] -``` - -### .dockerignore - -``` -.venv/ -__pycache__/ -*.pyc -.git/ -.ruff_cache/ -.mypy_cache/ -.pytest_cache/ -dist/ -build/ -*.egg-info/ -``` - -## CI/CD Patterns - -### GitHub Actions with uv - -```yaml -name: CI -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.12", "3.13", "3.14"] - steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v5 - - - name: Set up Python - run: uv python install ${{ matrix.python-version }} - - - name: Install dependencies - run: uv sync --frozen - - - name: Lint - run: uv run ruff check . - - - name: Format check - run: uv run ruff format --check . - - - name: Type check - run: uv run mypy src/ - - - name: Test - run: uv run pytest --cov=src --cov-report=xml - - - name: Upload coverage - uses: codecov/codecov-action@v4 - with: - file: coverage.xml -``` - -### Pre-commit Hooks - -```yaml -# .pre-commit-config.yaml -repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.6 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.1 - hooks: - - id: mypy - additional_dependencies: [pydantic] -``` - -```bash -# Install pre-commit -uv tool install pre-commit -pre-commit install -pre-commit run --all-files -``` - -## Environment Variables and Configuration - -### pydantic-settings (Recommended for Apps) - -```python -from pydantic_settings import BaseSettings -from pydantic import Field - -class Settings(BaseSettings): - model_config = {"env_prefix": "APP_", "env_file": ".env"} - - database_url: str - redis_url: str = "redis://localhost:6379" - debug: bool = False - secret_key: str = Field(min_length=32) - allowed_hosts: list[str] = ["localhost"] - -settings = Settings() # Reads from APP_DATABASE_URL, APP_DEBUG, etc. -``` - -### .env Files - -```bash -# .env (never commit this) -APP_DATABASE_URL=postgresql://user:pass@localhost/db -APP_SECRET_KEY=your-secret-key-here -APP_DEBUG=true -``` - -```python -# Load manually if not using pydantic-settings -from dotenv import load_dotenv -import os - -load_dotenv() -db_url = os.getenv("DATABASE_URL", "sqlite:///default.db") -``` - -## Logging Configuration - -```python -import logging -import logging.config - -# Basic setup -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", -) - -logger = logging.getLogger(__name__) -logger.info("Application started") -logger.warning("Low memory", extra={"available_mb": 100}) - -# Structured logging with structlog (recommended) -import structlog - -structlog.configure( - processors=[ - structlog.contextvars.merge_contextvars, - structlog.processors.add_log_level, - structlog.processors.TimeStamper(fmt="iso"), - structlog.dev.ConsoleRenderer(), # Or JSONRenderer() for production - ], -) - -log = structlog.get_logger() -log.info("user.login", user_id=42, ip="192.168.1.1") -``` - -## Makefile / Task Runner Patterns - -```makefile -# Makefile - common Python project tasks -.PHONY: install test lint format check clean - -install: - uv sync - -test: - uv run pytest -v --cov=src - -lint: - uv run ruff check . - -format: - uv run ruff format . - uv run ruff check --fix . - -typecheck: - uv run mypy src/ - -check: lint typecheck test - -clean: - rm -rf .pytest_cache .mypy_cache .ruff_cache htmlcov dist build - find . -type d -name __pycache__ -exec rm -rf {} + -``` - -Or use `[project.scripts]` in pyproject.toml for project-specific commands: - -```toml -[project.scripts] -serve = "my_package.main:serve" -migrate = "my_package.db:run_migrations" -``` - -## Version Specifiers (PEP 440) - -``` -# Exact -requests==2.31.0 - -# Minimum -requests>=2.31.0 - -# Compatible release (>=2.31, <3.0) -requests~=2.31 - -# Range -requests>=2.28,<3.0 - -# Exclude -requests!=2.30.0 - -# Pre-release -requests>=2.32.0rc1 - -# Extras -httpx[http2]>=0.27 -``` - -## py.typed and Type Stubs - -For libraries that ship type information: - -``` -# Add py.typed marker file (PEP 561) -my_package/ - __init__.py - py.typed # Empty file, marks package as typed - core.py -``` - -For third-party packages without types: - -```bash -# Install type stubs -uv add --dev types-requests types-pyyaml - -# Or create custom stubs -# my_package/stubs/third_party.pyi -def some_function(arg: str) -> int: ... -``` diff --git a/packages/dotfiles/dot_agents/skills/python-helper/references/types-and-security.md b/packages/dotfiles/dot_agents/skills/python-helper/references/types-and-security.md new file mode 100644 index 0000000000..36bb60a8a4 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/python-helper/references/types-and-security.md @@ -0,0 +1,69 @@ +# Python types and security + +Read this when designing typed boundaries, introspecting annotations, processing t-strings, invoking subprocesses, handling archives or pickle, or generating secrets. + +## Deferred annotations + +Python 3.14 uses deferred annotation evaluation. This is not the same model as `from __future__ import annotations` stringization. `annotationlib` exposes value, forward-reference, and string formats. + +Annotation introspection can execute arbitrary code. Do not evaluate annotations from untrusted modules as harmless metadata. + +## T-strings + +Template strings return a `Template`, not a safe output string. A processor must deliberately handle interpolation values, conversions, and format specifications. T-strings are not automatically HTML-escaped or SQL-parameterized. + +## Runtime models + +Pydantic `Field` metadata on a plain class does not validate anything. Use `BaseModel`, Pydantic dataclass integration, or `TypeAdapter`. + +Prefer precise structures: + +- `TypedDict` for statically typed mapping shapes without runtime validation, +- dataclasses for in-process value objects, +- Pydantic or explicit parsers for external values, +- `Mapping[K, V]` when mutation is not required. + +## File and path handling + +Use explicit text encoding on Python 3.14. Python 3.15 plans UTF-8 by default, but prerelease plans are not a reason to omit encoding today. + +Python 3.14 adds `pathlib.Path.copy`, `copy_into`, `move`, and `move_into`. Metadata preservation is an explicit copying choice. + +## Subprocesses + +Argument sequences avoid shell parsing: + +```python +import subprocess + +subprocess.run(["git", "status", "--short"], check=True) +``` + +With `shell=True`, the application owns correct escaping. Do not pass untrusted input through a shell. + +## Serialization and archives + +Unpickling untrusted data can execute arbitrary code. Use a data-only format plus validation. + +Python 3.14 makes tarfile's `data` extraction filter the default. That reduces path and device-file risk but does not make an arbitrary archive trusted; inspect type, path, size, and resource limits. + +## Secrets + +Use the `secrets` module for tokens and password-reset material. `random` is for simulation and non-security randomness. + +## Caching and slots + +An unbounded `functools.cache` can retain every distinct key. Use it only with a bounded key space or choose a bounded LRU policy. + +`__slots__` can reduce per-instance memory and restrict attributes, but speed and layout benefits are workload-dependent. Measure before adopting it as an optimization. + +## Primary documentation + +- [annotationlib](https://docs.python.org/3/library/annotationlib.html) +- [string.templatelib](https://docs.python.org/3/library/string.templatelib.html) +- [typing](https://docs.python.org/3/library/typing.html) +- [pathlib](https://docs.python.org/3/library/pathlib.html) +- [subprocess security](https://docs.python.org/3/library/subprocess.html) +- [secrets](https://docs.python.org/3/library/secrets.html) +- [pickle](https://docs.python.org/3/library/pickle.html) +- [tarfile](https://docs.python.org/3/library/tarfile.html) diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/SKILL.md b/packages/dotfiles/dot_agents/skills/rust-helper/SKILL.md index bd9c651bff..a523ed321d 100644 --- a/packages/dotfiles/dot_agents/skills/rust-helper/SKILL.md +++ b/packages/dotfiles/dot_agents/skills/rust-helper/SKILL.md @@ -1,408 +1,169 @@ --- name: rust-helper -description: | - Rust development with cargo, clippy, rustfmt, testing, and common patterns - When user works with .rs files, mentions Rust, cargo, clippy, rustfmt, or encounters Rust compiler errors +description: Current Rust and Cargo development guidance for workspaces, editions, MSRV, testing, Clippy, rustfmt, unsafe code, debugging, and toolchains. Use when writing or reviewing Rust, Cargo manifests, Rust CI, edition migrations, or unsafe boundaries. --- -# Rust Helper Agent +# Rust Helper -## What's New in Rust (2024-2026) +Use Rust's type system to express invariants, keep unsafe boundaries small and documented, and run the repository's real Cargo workflow. Separate the stable toolchain from optional ecosystem tools. -- **Rust 2024 Edition** (1.85, Feb 2025): Largest edition yet. Async closures `async || {}`, RPIT lifetime capture changes, `unsafe extern` blocks, `unsafe_op_in_unsafe_fn` warning by default, `gen` keyword reserved, `expr` fragment matches `const` and `_` -- **Trait Upcasting** (1.86): Coerce `&dyn Trait` to `&dyn Supertrait` directly -- **Disjoint Mutable Indexing** (1.86): `slice.get_disjoint_mut([i, j])` for multiple mutable refs -- **Let Chains** (1.88, edition 2024): `if let Some(x) = a && x > 5 { ... }` -- **Naked Functions** (1.88): `#[naked]` for functions with no compiler-generated prologue/epilogue -- **Inline ASM Jumps** (1.87): Assembly can jump to Rust code labels -- **LLD Default Linker** (1.90): `lld` is default on `x86_64-unknown-linux-gnu` for faster linking -- **C-style Variadic Functions** (1.91): Stabilized for sysv64, win64, efiapi, aapcs ABIs -- **Explicitly Inferred Const Args** (1.92): Deny-by-default never_type_fallback lints -- **Conditional ASM Lines** (1.93): Individual `asm!` statements support `cfg` attributes -- **String/Vec `into_raw_parts`** (1.93): `String::into_raw_parts()`, `Vec::into_raw_parts()` -- **Current stable**: 1.93.0 (Jan 2026) +## Current baseline -## Overview - -This skill covers Rust development using cargo, clippy, rustfmt, testing frameworks (cargo test, nextest), background checking (bacon), compilation caching (sccache), and undefined behavior detection (miri). It includes ownership/borrowing patterns, error handling, async/await, and the cargo ecosystem. - -## CLI Commands - -### Auto-Approved Safe Commands +Verified against Rust 1.97.1 on 2026-08-03: ```bash -# Check compilation without producing binaries -cargo check - -# Run clippy lints -cargo clippy - -# Format code -cargo fmt -- --check - -# Run tests -cargo test - -# Run nextest -cargo nextest run - -# Build (debug) -cargo build - -# Show documentation -cargo doc --open - -# List dependencies -cargo tree - -# Check for outdated deps -cargo outdated - -# Expand macros -cargo expand - -# Show current toolchain -rustup show - -# Run bacon (background checker) -bacon +rustc --version +cargo --version +rustup show active-toolchain ``` -### Build and Run +Rust 1.97.1 is a patch release with a compiler miscompilation fix. Since Rust 1.93, relevant additions include Cargo TOML 1.1 and config inclusion, `array_windows`, `cfg_select!`, match if-let guards, `assert_matches!`, Cargo registry security fixes, symbol mangling v0 by default, and cache-friendly Cargo warning controls. -```bash -# Debug build -cargo build +Rust 2024 stabilized in Rust 1.85. A package's `rust-version` is its minimum supported Rust version, not the current stable release; do not mechanically update MSRV to 1.97.1. -# Release build (optimized) -cargo build --release +Read [references/releases.md](references/releases.md) for the 36-page research ledger and release constraints. Read [references/cargo-and-toolchains.md](references/cargo-and-toolchains.md) for workspaces, dependencies, toolchains, Clippy, rustfmt, and optional tools. Read [references/testing-and-debugging.md](references/testing-and-debugging.md) for tests, doctests, Nextest, Miri, profiling, and debugging. Read [references/safety-and-patterns.md](references/safety-and-patterns.md) for unsafe-code obligations and corrected implementation patterns. -# Build specific package in workspace -cargo build -p my-crate +## Built-in and optional tools -# Build with specific features -cargo build --features "feature1,feature2" -cargo build --all-features -cargo build --no-default-features +Built into or distributed as Rust toolchain components: -# Run binary -cargo run -cargo run --release -cargo run -- --arg1 value1 +- `cargo`, `rustc`, and `rustdoc` +- `rustfmt`, Clippy, and rust-analyzer when the component is available for the selected toolchain +- Miri, generally through a compatible nightly toolchain -# Run specific binary in multi-bin project -cargo run --bin my-binary +Optional external tools include cargo-nextest, Bacon, cargo-outdated, cargo-expand, and sccache. Check installation explicitly when the repository requires one; do not silently skip it or present it as a built-in Cargo command. -# Cross-compile -rustup target add aarch64-unknown-linux-gnu -cargo build --target aarch64-unknown-linux-gnu -``` +## Focused verification -### Clippy +Adapt features and targets to the project, but keep each result meaningful: ```bash -# Run clippy -cargo clippy - -# Clippy with all targets (tests, benches, examples) -cargo clippy --all-targets - -# Deny warnings (useful in CI) -cargo clippy -- -D warnings - -# Enable pedantic lints -cargo clippy -- -W clippy::pedantic - -# Fix automatically -cargo clippy --fix - -# Clippy for specific package -cargo clippy -p my-crate +cargo check --workspace --all-targets --all-features +cargo fmt --all -- --check +cargo clippy --workspace --all-targets --all-features -- -D warnings +cargo test --workspace --all-features +cargo test --workspace --doc ``` -Configure in `clippy.toml` or via attributes: +`cargo fmt -- --emit diff` is not supported by current stable rustfmt. `cargo fmt --all -- --check` prints differences and exits non-zero. -```rust -// Allow specific lint -#[allow(clippy::needless_return)] +Pin the toolchain before making all warnings fatal in reproducible CI. On Rust 1.97+, `CARGO_BUILD_WARNINGS=deny cargo check` denies build warnings without invalidating the build cache. -// Project-wide in lib.rs or main.rs -#![warn(clippy::all, clippy::pedantic)] -#![allow(clippy::module_name_repetitions)] -``` +## Toolchain and MSRV -### Rustfmt +Commit `rust-toolchain.toml` when the project requires a reproducible compiler and components: -```bash -# Format all files -cargo fmt - -# Check formatting without changing files -cargo fmt -- --check - -# Format specific file -rustfmt src/main.rs - -# Show diff of formatting changes -cargo fmt -- --emit diff +```toml +[toolchain] +channel = "1.97.1" +components = ["clippy", "rustfmt"] +profile = "minimal" ``` -Configure in `rustfmt.toml`: +Use `cargo +nightly` for a one-off nightly command. Avoid a hidden directory-local rustup override for a project requirement. + +Declare the real MSRV in each package: ```toml +[package] edition = "2024" -max_width = 100 -tab_spaces = 4 -use_field_init_shorthand = true +rust-version = "1.85" ``` -### Toolchain Management - -```bash -# Install/update stable -rustup update stable - -# Install nightly -rustup toolchain install nightly +With `rust-version`, `cargo add` selects the newest dependency compatible with that MSRV. It does not blindly choose the latest published version. -# Use nightly for current project -rustup override set nightly +## Workspaces -# Add component -rustup component add clippy rustfmt rust-analyzer +Rust 2024 uses resolver 3. A virtual workspace has no package edition from which to infer it, so specify it explicitly: -# Add compilation target -rustup target add wasm32-unknown-unknown +```toml +[workspace] +resolver = "3" +members = ["crates/core", "crates/cli", "crates/server"] -# Show installed toolchains -rustup show +[workspace.package] +edition = "2024" +rust-version = "1.85" ``` -## Essential Patterns Quick Reference - -### Ownership and Borrowing - -```rust -// Move semantics (non-Copy types) -let s1 = String::from("hello"); -let s2 = s1; // s1 is moved, no longer usable +Workspace members share one lockfile and target directory. Profiles and `[patch]` belong at the workspace root. -// Borrowing (immutable reference) -let s = String::from("hello"); -let len = calculate_length(&s); // s is borrowed, still usable +## Edition migration -// Mutable borrowing (one at a time) -let mut s = String::from("hello"); -change(&mut s); +`cargo fix` edits source based on machine-applicable diagnostics and only sees enabled features and selected targets. Commit or otherwise preserve the current state first, then run the migration through every meaningful feature and target combination. -// Slice borrowing -let s = String::from("hello world"); -let hello = &s[0..5]; +```bash +cargo fix --edition --workspace --all-features +cargo check --workspace --all-targets --all-features ``` -### Error Handling +After changing each package's edition, rerun formatting, Clippy, tests, and doctests. Rust 2024 warns by default when unsafe operations inside an `unsafe fn` are not enclosed in an explicit `unsafe` block. -```rust -// Result with ? operator -fn read_config(path: &str) -> Result> { - let contents = std::fs::read_to_string(path)?; - let config: Config = serde_json::from_str(&contents)?; - Ok(config) -} +## Errors and assertions -// Option with ? in functions returning Option -fn first_even(numbers: &[i32]) -> Option<&i32> { - numbers.iter().find(|&&n| n % 2 == 0) -} +Propagate errors when a test or helper can return `Result`. Assert the actual value or invariant; `assert!(result.is_ok())` discards useful failure context. -// Pattern matching on Result/Option -match result { - Ok(value) => println!("Got: {value}"), - Err(e) => eprintln!("Error: {e}"), -} - -// if let for single variant -if let Some(value) = optional { - println!("Got: {value}"); +```rust +#[tokio::test] +async fn fetches_expected_body() -> Result<(), reqwest::Error> { + let server = wiremock::MockServer::start().await; + wiremock::Mock::given(wiremock::matchers::method("GET")) + .respond_with(wiremock::ResponseTemplate::new(200).set_body_string("ok")) + .mount(&server) + .await; + + let body = fetch_data(&server.uri()).await?; + assert_eq!(body, "ok"); + Ok(()) } ``` -### Iterators +Use a local mock server (`wiremock`) or injected transport, not a live third-party +endpoint — a real request makes the suite depend on DNS, outbound-network +availability, and an external response. -```rust -// Chain iterator adapters -let result: Vec = items.iter() - .filter(|x| x.is_valid()) - .map(|x| x.value * 2) - .collect(); - -// Enumerate -for (i, item) in items.iter().enumerate() { - println!("{i}: {item}"); -} +Check cleanup and writer errors when they can change the outcome. Do not ignore `Read`, `Write`, database close, trace write, or process exit errors. -// fold / reduce -let sum: i32 = numbers.iter().fold(0, |acc, &x| acc + x); +## Ownership and borrowing -// flat_map -let words: Vec<&str> = lines.iter() - .flat_map(|line| line.split_whitespace()) - .collect(); -``` +Prefer borrowing when a function need not own a value. The beginner rule “one mutable borrow at a time” is incomplete: Rust permits simultaneous mutable borrows when the compiler or an API proves they are disjoint. Do not contort code around a simplified slogan; model the actual aliasing invariant. -### Struct and Enum Patterns +For text, remember that string indexes are byte offsets. Never slice an arbitrary byte range that may split UTF-8 or exceed the string length: ```rust -// Struct with derive macros -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -struct Config { - name: String, - port: u16, - #[serde(default)] - verbose: bool, -} - -// Enum with data -enum Command { - Quit, - Echo(String), - Move { x: i32, y: i32 }, - Color(u8, u8, u8), -} - -// impl block -impl Config { - fn new(name: impl Into) -> Self { - Self { - name: name.into(), - port: 8080, - verbose: false, - } - } +fn preview(summary: &str) -> String { + let prefix: String = summary.chars().take(20).collect(); + format!("{prefix}...") } ``` -## Bacon (Background Checker) - -```bash -# Start bacon (watches for changes, shows errors) -bacon - -# Keyboard shortcuts inside bacon: -# c - show clippy warnings -# t - run tests -# d - open documentation -# f - focus on failing test (when test fails) -# Esc - back to all tests -``` - -Configure in `bacon.toml`: - -```toml -[jobs.check] -command = ["cargo", "check", "--all-targets"] - -[jobs.clippy] -command = ["cargo", "clippy", "--all-targets"] - -[jobs.test] -command = ["cargo", "test"] - -[jobs.nextest] -command = ["cargo", "nextest", "run"] -``` - -## sccache (Compilation Cache) - -Set up sccache to speed up repeated compilations: - -```bash -# Install -cargo install sccache - -# Configure in ~/.cargo/config.toml -# [build] -# rustc-wrapper = "sccache" - -# Or via environment variable -export RUSTC_WRAPPER=sccache - -# Check stats -sccache --show-stats - -# Clear cache -sccache --zero-stats -``` - -## Miri (Undefined Behavior Detection) - -```bash -# Install miri (requires nightly) -rustup +nightly component add miri - -# Run program under miri -cargo +nightly miri run +## Unsafe code -# Run tests under miri -cargo +nightly miri test +Unsafe permits a small set of operations; it does not disable Rust's other checks. Every unsafe boundary transfers a proof obligation to the author: -# Run specific test -cargo +nightly miri test test_name +- minimize the unsafe region, +- use an explicit `unsafe {}` block even inside `unsafe fn`, +- document caller obligations in a `# Safety` section, +- add a `// SAFETY:` explanation at each block, +- test the invariant with focused tests and, where useful, Miri, property tests, or fuzzing. -# Set isolation (disable for tests needing system access) -MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test -``` +A clean Miri run checks only the executions observed and does not prove soundness. Miri's aliasing models are experimental, and its isolation is not a security sandbox. Disabling isolation exposes real host APIs. -Miri detects: uninitialized memory reads, out-of-bounds access, use-after-free, data races, invalid pointer provenance, type invariant violations. +## Lints and formatting -## Cargo.toml Quick Reference +Use the repository's selected lint groups. Do not enable all of `clippy::pedantic` as a universal default. When a lint is deliberately inapplicable, keep any allowance narrow and explain the invariant. Clippy configuration is explicitly unstable, so verify options against the selected toolchain. -```toml -[package] -name = "my-project" -version = "0.1.0" -edition = "2024" -rust-version = "1.85" - -[dependencies] -serde = { version = "1", features = ["derive"] } -tokio = { version = "1", features = ["full"] } -anyhow = "1" -thiserror = "2" -clap = { version = "4", features = ["derive"] } -tracing = "0.1" - -[dev-dependencies] -tokio = { version = "1", features = ["test-util"] } - -[profile.release] -lto = true -codegen-units = 1 -strip = true - -[profile.dev] -opt-level = 0 # Fast compile -debug = true # Full debug info - -[profile.dev.package."*"] -opt-level = 2 # Optimize dependencies even in dev -``` - -## When to Ask for Help - -Ask the user for clarification when: - -- Lifetime annotations are ambiguous or complex -- Choice between async and sync is unclear -- Error handling strategy (anyhow vs thiserror vs custom) needs deciding -- Workspace structure decisions are needed -- Unsafe code review is required -- Performance vs readability tradeoffs exist - ---- +Rustdoc runs Rust code blocks as tests by default. Keep `cargo test --doc` even when cargo-nextest is used, because Nextest does not run doctests. -See `references/` for detailed guides: +## Review checklist -- `patterns.md` - Ownership, traits, generics, lifetimes, async/await, iterators -- `cargo-ecosystem.md` - Cargo commands, workspaces, popular crates, features, build scripts -- `testing-debugging.md` - Testing with nextest, bacon, debugging with lldb, profiling, miri +- Verify the rustc, Cargo, rustup, and component versions in use. +- Keep MSRV separate from current stable. +- Use resolver 3 explicitly for virtual Rust 2024 workspaces. +- Run formatting, Clippy, tests, and doctests through the actual workspace. +- Treat `cargo fix` and dependency updates as mutations requiring review. +- Distinguish built-in commands from optional tools. +- Propagate meaningful errors and use strong assertions. +- Keep unsafe small, explicit, documented, and tested. +- Treat Miri findings as evidence of a bug and clean runs as bounded evidence only. +- Measure cache, linker, retry, and performance claims on the actual workload. diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/references/cargo-and-toolchains.md b/packages/dotfiles/dot_agents/skills/rust-helper/references/cargo-and-toolchains.md new file mode 100644 index 0000000000..c843c45518 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/rust-helper/references/cargo-and-toolchains.md @@ -0,0 +1,92 @@ +# Cargo and toolchains + +Read this when configuring a workspace, toolchain, MSRV, dependency, Cargo config, Clippy, rustfmt, or optional Rust tool. + +## Dependency resolution + +Resolver 3 is the Rust 2024 default. Specify it in a virtual workspace. `rust-version` declares MSRV and affects compatible dependency selection. + +Use these diagnostics before changing dependency constraints: + +```bash +cargo tree -d +cargo tree -e features +cargo report future-incompatibilities +``` + +`cargo report future-incompatibilities` identifies dependencies that future compilers will reject. It does not replace current tests. + +Avoid a static catalog of “best crates” and pinned example versions in a generic skill. Select libraries from maintained official documentation, MSRV compatibility, security posture, API fit, and the repository's existing ecosystem. + +## Publishing + +Current Cargo supports workspace publishing. Perform a dry run and review the exact package set: + +```bash +cargo publish --workspace --dry-run +``` + +Publishing is an external mutation; do not remove `--dry-run` without explicit authorization and release ownership. + +## Configuration + +Cargo configuration tables may appear only once per file. Merge build settings: + +```toml +[build] +rustc-wrapper = "sccache" +jobs = 8 +target-dir = "target" +target = "x86_64-unknown-linux-gnu" +``` + +This is a structural example, not a universal recommendation. sccache value depends on inputs and CI architecture. Incremental compilation is often disabled in CI; measure the combination instead of setting both globally. + +## rustup precedence + +Toolchain selection precedence includes an explicit `+toolchain`, `RUSTUP_TOOLCHAIN`, directory or toolchain-file overrides, and the default toolchain. Prefer committed `rust-toolchain.toml` for project requirements and explicit `cargo +nightly` for one-off commands. + +Component availability varies by toolchain. Fail clearly when a required component such as Clippy or rustfmt is absent. + +## Clippy + +`clippy::all` is the default group. Add other lint groups because the repository wants them, not because a generic checklist says to enable every pedantic lint. Pin the toolchain before turning all warnings into errors. + +Clippy officially permits narrowly scoped allowances where a lint is intentionally wrong for an invariant. This repository discourages suppressions, so require a documented reason and the smallest possible scope. + +`clippy.toml` and `.clippy.toml` are unstable configuration surfaces. Verify keys on every toolchain upgrade. + +## rustfmt + +Use stable, supported checks: + +```bash +cargo fmt --all -- --check +``` + +Rustfmt's own version does not numerically track rustc. Consult the versioned configuration reference before adding an option, especially during the rustfmt 2.0 transition. + +## Optional tools + +- cargo-nextest: process-per-test runner; does not run doctests. +- Bacon: background checker configured through project files; avoid duplicate TOML tables. +- cargo-outdated: third-party dependency report. +- cargo-expand: expansion inspection; often requires nightly-compatible internals. +- sccache: compiler cache whose benefit depends on workload and environment. +- Miri: usually a nightly component for interpreter-based undefined-behavior detection. + +Check each tool's installed version and project configuration. Never silently skip a required check. + +## Primary documentation + +- [Cargo workspaces](https://doc.rust-lang.org/cargo/reference/workspaces.html) +- [Dependency resolution](https://doc.rust-lang.org/cargo/reference/resolver.html) +- [Rust version](https://doc.rust-lang.org/cargo/reference/rust-version.html) +- [Future incompatibility reports](https://doc.rust-lang.org/cargo/reference/future-incompat-report.html) +- [cargo test](https://doc.rust-lang.org/cargo/commands/cargo-test.html) +- [cargo fix](https://doc.rust-lang.org/cargo/commands/cargo-fix.html) +- [Clippy usage](https://doc.rust-lang.org/clippy/usage.html) +- [Clippy configuration](https://doc.rust-lang.org/clippy/configuration.html) +- [rustfmt](https://rust-lang.github.io/rustfmt/) +- [rustup overrides](https://rust-lang.github.io/rustup/overrides.html) +- [rustup components](https://rust-lang.github.io/rustup/concepts/components.html) diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/references/cargo-ecosystem.md b/packages/dotfiles/dot_agents/skills/rust-helper/references/cargo-ecosystem.md deleted file mode 100644 index bfa7c28a30..0000000000 --- a/packages/dotfiles/dot_agents/skills/rust-helper/references/cargo-ecosystem.md +++ /dev/null @@ -1,582 +0,0 @@ -# Cargo and the Rust Ecosystem - -Comprehensive reference for Cargo commands, workspace patterns, popular crates, dependency management, features, and build configuration. - -## Cargo Commands Reference - -### Project Management - -```bash -# Create new project -cargo new my-project # Binary -cargo new my-lib --lib # Library -cargo init # Initialize in current directory -cargo init --lib # Initialize as library - -# Build -cargo build # Debug build -cargo build --release # Release build (optimized) -cargo build --target x86_64-unknown-linux-musl # Cross-compile - -# Run -cargo run # Build and run -cargo run --release # Run release build -cargo run -- arg1 arg2 # Pass arguments to binary -cargo run --bin specific-bin # Run specific binary -cargo run --example my_example # Run an example - -# Check (faster than build, no codegen) -cargo check # Check compilation -cargo check --all-targets # Check including tests, benches, examples - -# Clean -cargo clean # Remove target directory -cargo clean -p my-crate # Clean specific package - -# Update dependencies -cargo update # Update all within semver bounds -cargo update -p serde # Update specific package -``` - -### Documentation - -```bash -# Generate and open docs -cargo doc --open # Build docs for project + deps -cargo doc --no-deps --open # Just project docs -cargo doc -p my-crate --open # Specific crate docs - -# Rustdoc specific -cargo rustdoc -- --cfg docsrs # Build with docs.rs cfg -``` - -### Dependency Management - -```bash -# Add dependencies -cargo add serde # Latest version -cargo add serde@1.0.200 # Specific version -cargo add serde --features derive # With features -cargo add tokio --features full # All features -cargo add my-crate --path ../my-crate # Local path -cargo add my-crate --git https://github.com/user/repo # Git - -# Add as dev dependency -cargo add tokio --dev --features test-util - -# Add as build dependency -cargo add cc --build - -# Remove dependency -cargo remove serde - -# View dependency tree -cargo tree # Full tree -cargo tree -d # Duplicates only -cargo tree -i serde # Inverse (who depends on serde) -cargo tree --depth 1 # Top-level only -cargo tree -e features # Show feature flags - -# Check for outdated dependencies -cargo install cargo-outdated -cargo outdated - -# Audit dependencies for security -cargo install cargo-audit -cargo audit -``` - -### Publishing - -```bash -# Package for publishing -cargo package # Create .crate file -cargo package --list # List files that would be included - -# Publish to crates.io -cargo publish -cargo publish --dry-run # Check without publishing - -# Yank (remove from dependency resolution, still downloadable) -cargo yank --version 1.0.0 -cargo yank --version 1.0.0 --undo -``` - -### Workspace Publishing (Rust 1.90+) - -```bash -# Publish all workspace members in dependency order -cargo publish --workspace - -# Publish specific packages -cargo publish -p crate-a -p crate-b -``` - -## Cargo.toml Configuration - -### Basic Structure - -```toml -[package] -name = "my-project" -version = "0.1.0" -edition = "2024" -rust-version = "1.85" -description = "A brief description" -license = "MIT OR Apache-2.0" -repository = "https://github.com/user/repo" -keywords = ["keyword1", "keyword2"] -categories = ["category"] -exclude = ["tests/fixtures/*"] - -[dependencies] -# Version requirements -serde = "1" # >=1.0.0, <2.0.0 -serde = "=1.0.200" # Exactly 1.0.200 -serde = ">=1.0, <1.5" # Range -serde = { version = "1", features = ["derive"] } - -# Path dependency (local) -my-lib = { path = "../my-lib" } - -# Git dependency -my-lib = { git = "https://github.com/user/repo", branch = "main" } -my-lib = { git = "https://github.com/user/repo", tag = "v1.0" } -my-lib = { git = "https://github.com/user/repo", rev = "abc123" } - -# Optional dependency (becomes a feature) -extra-feature = { version = "1", optional = true } - -[dev-dependencies] -criterion = { version = "0.5", features = ["html_reports"] } -tempfile = "3" -proptest = "1" - -[build-dependencies] -cc = "1" -``` - -### Features - -```toml -[features] -default = ["json"] -json = ["dep:serde_json"] # Enable optional dep -full = ["json", "xml", "yaml"] # Combine features -xml = ["dep:quick-xml"] -yaml = ["dep:serde_yaml"] - -# Feature enables feature in dependency -async = ["tokio/full"] -``` - -Use features in code: - -```rust -#[cfg(feature = "json")] -pub mod json { - use serde_json; - // ... -} - -#[cfg(feature = "async")] -pub async fn fetch() { /* ... */ } -``` - -### Build Profiles - -```toml -# Development profile (cargo build) -[profile.dev] -opt-level = 0 # No optimization -debug = true # Full debug info -debug-assertions = true -overflow-checks = true -incremental = true -codegen-units = 256 # Faster compile, slower code - -# Release profile (cargo build --release) -[profile.release] -opt-level = 3 # Maximum optimization -debug = false # No debug info -lto = true # Link-time optimization (slower build, faster binary) -codegen-units = 1 # Slower compile, faster code -strip = true # Strip symbols -panic = "abort" # Smaller binary, no unwinding - -# Optimize deps in dev mode (faster runtime, same compile speed for your code) -[profile.dev.package."*"] -opt-level = 2 - -# Custom profile -[profile.profiling] -inherits = "release" -debug = true # Debug info for profiling tools -strip = false -``` - -### Target-Specific Configuration - -```toml -# Platform-specific dependencies -[target.'cfg(unix)'.dependencies] -nix = "0.29" - -[target.'cfg(windows)'.dependencies] -windows = "0.58" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "0.2" -``` - -## Workspace Patterns - -### Basic Workspace - -```toml -# Root Cargo.toml -[workspace] -members = [ - "crates/core", - "crates/cli", - "crates/server", -] -resolver = "2" - -# Shared dependencies -[workspace.dependencies] -serde = { version = "1", features = ["derive"] } -tokio = { version = "1", features = ["full"] } -anyhow = "1" -tracing = "0.1" - -# Shared package metadata -[workspace.package] -edition = "2024" -rust-version = "1.85" -license = "MIT" -repository = "https://github.com/user/repo" -``` - -### Member Cargo.toml - -```toml -[package] -name = "my-cli" -version = "0.1.0" -edition.workspace = true -rust-version.workspace = true -license.workspace = true - -[dependencies] -# Inherit from workspace -serde.workspace = true -tokio.workspace = true -anyhow.workspace = true - -# Additional crate-specific deps -clap = { version = "4", features = ["derive"] } - -# Depend on sibling crate -my-core = { path = "../core" } -``` - -### Workspace Commands - -```bash -# Build all members -cargo build --workspace - -# Test all members -cargo test --workspace - -# Run clippy on all members -cargo clippy --workspace --all-targets - -# Build specific member -cargo build -p my-cli - -# Run specific member's tests -cargo test -p my-core - -# Check all members -cargo check --workspace -``` - -## Popular Crates Reference - -### Serialization - -| Crate | Purpose | Usage | -| ------------ | ----------------------- | ----------------------------------- | -| `serde` | Serialization framework | `#[derive(Serialize, Deserialize)]` | -| `serde_json` | JSON support | `serde_json::to_string(&val)?` | -| `serde_yaml` | YAML support | `serde_yaml::from_str(s)?` | -| `toml` | TOML support | `toml::from_str(s)?` | -| `bincode` | Binary encoding | Fast, compact binary format | -| `csv` | CSV parsing | Streaming CSV read/write | - -```rust -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -struct Config { - name: String, - port: u16, - #[serde(default)] - debug: bool, - #[serde(rename = "api_key")] - key: String, - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, -} -``` - -### Async Runtime and Networking - -| Crate | Purpose | -| --------- | --------------------------------- | -| `tokio` | Async runtime (de facto standard) | -| `reqwest` | HTTP client (async-first) | -| `axum` | Web framework (tokio ecosystem) | -| `tonic` | gRPC framework | -| `tower` | Service abstraction / middleware | -| `hyper` | Low-level HTTP | - -```rust -// Axum web server example -use axum::{routing::get, Router, Json}; - -#[tokio::main] -async fn main() { - let app = Router::new() - .route("/", get(root)) - .route("/users", get(list_users)); - - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); - axum::serve(listener, app).await.unwrap(); -} - -async fn root() -> &'static str { - "Hello, World!" -} - -async fn list_users() -> Json> { - Json(vec![User { name: "Alice".into() }]) -} -``` - -### CLI Tools - -| Crate | Purpose | -| ----------- | ------------------------------------ | -| `clap` | Argument parsing (derive or builder) | -| `dialoguer` | Interactive prompts | -| `indicatif` | Progress bars | -| `console` | Terminal colors and formatting | -| `colored` | Simple terminal coloring | - -```rust -use clap::Parser; - -/// A simple CLI tool -#[derive(Parser, Debug)] -#[command(version, about)] -struct Args { - /// Name of the person to greet - #[arg(short, long)] - name: String, - - /// Number of times to greet - #[arg(short, long, default_value_t = 1)] - count: u8, - - /// Enable verbose output - #[arg(short, long)] - verbose: bool, -} - -fn main() { - let args = Args::parse(); - for _ in 0..args.count { - println!("Hello {}!", args.name); - } -} -``` - -### Database - -| Crate | Purpose | -| ---------- | ----------------------------------------- | -| `sqlx` | Async SQL (compile-time checked queries) | -| `sea-orm` | ORM built on sqlx | -| `diesel` | Sync ORM with compile-time query checking | -| `rusqlite` | SQLite bindings | - -### Error Handling - -| Crate | Purpose | Use When | -| ----------- | -------------------------------------- | --------------------- | -| `thiserror` | Derive custom error types | Library code | -| `anyhow` | Flexible error aggregation | Application code | -| `eyre` | Enhanced error reporting (like anyhow) | Alternative to anyhow | -| `miette` | Fancy diagnostic errors | CLI tools | - -### Logging and Tracing - -| Crate | Purpose | -| -------------------- | -------------------------------- | -| `tracing` | Structured, async-aware logging | -| `tracing-subscriber` | Configure tracing output | -| `log` | Simple logging facade | -| `env_logger` | Configure log via `RUST_LOG` env | - -```rust -use tracing::{info, warn, error, instrument}; -use tracing_subscriber; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - info!("Starting application"); -} - -#[instrument(skip(password))] -async fn login(username: &str, password: &str) -> Result { - info!(username, "Login attempt"); - // ... -} -``` - -### Testing and Development - -| Crate | Purpose | -| ------------ | ------------------------- | -| `criterion` | Benchmarking framework | -| `proptest` | Property-based testing | -| `mockall` | Mock generation | -| `wiremock` | HTTP mocking | -| `tempfile` | Temporary files for tests | -| `insta` | Snapshot testing | -| `assert_cmd` | CLI integration testing | -| `predicates` | Assertion helpers | - -### Other Essential Crates - -| Crate | Purpose | -| ----------- | --------------------------------------- | -| `regex` | Regular expressions | -| `chrono` | Date and time | -| `uuid` | UUID generation | -| `rand` | Random number generation | -| `rayon` | Data parallelism (parallel iterators) | -| `crossbeam` | Concurrent data structures | -| `dashmap` | Concurrent HashMap | -| `bytes` | Efficient byte buffer | -| `once_cell` | Lazy initialization (now partly in std) | -| `itertools` | Extended iterator methods | -| `strum` | Enum string conversions | - -## Build Scripts - -### build.rs - -```rust -// build.rs - runs before compilation -fn main() { - // Tell cargo to rerun if this file changes - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=src/proto/service.proto"); - - // Set environment variable accessible via env!() - println!("cargo:rustc-env=BUILD_TIME={}", chrono::Utc::now()); - - // Enable cfg flag - println!("cargo:rustc-cfg=has_feature_x"); - - // Link native library - println!("cargo:rustc-link-lib=sqlite3"); - - // Compile protobuf - tonic_build::compile_protos("src/proto/service.proto").unwrap(); -} -``` - -### Accessing Build Script Output - -```rust -// In your Rust code -const BUILD_TIME: &str = env!("BUILD_TIME"); - -#[cfg(has_feature_x)] -fn feature_x() { /* ... */ } -``` - -## Cargo Configuration - -### .cargo/config.toml - -```toml -# Build configuration -[build] -rustc-wrapper = "sccache" # Use sccache -jobs = 8 # Parallel jobs -target-dir = "target" # Custom target dir - -# Default target -[build] -target = "x86_64-unknown-linux-gnu" - -# Linker configuration -[target.x86_64-unknown-linux-gnu] -linker = "clang" -rustflags = ["-C", "link-arg=-fuse-ld=lld"] - -# macOS specific -[target.aarch64-apple-darwin] -rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"] - -# Aliases -[alias] -t = "test" -c = "check" -cl = "clippy --all-targets" -b = "build" -r = "run" -``` - -## Environment Variables - -| Variable | Purpose | -| ------------------- | --------------------------------------- | -| `CARGO_HOME` | Cargo installation directory (~/.cargo) | -| `RUSTC_WRAPPER` | Compiler wrapper (e.g., sccache) | -| `RUST_LOG` | Log level for env_logger/tracing | -| `RUST_BACKTRACE` | Enable backtraces (`1` or `full`) | -| `RUSTFLAGS` | Additional compiler flags | -| `CARGO_TARGET_DIR` | Override target directory | -| `CARGO_INCREMENTAL` | Enable/disable incremental compilation | - -```bash -# Common development environment -export RUST_LOG=debug -export RUST_BACKTRACE=1 -export RUSTC_WRAPPER=sccache -export CARGO_INCREMENTAL=1 -``` - -## Cargo Install (Developer Tools) - -```bash -# Essential tools -cargo install cargo-nextest # Better test runner -cargo install cargo-watch # Watch and rebuild -cargo install cargo-outdated # Check outdated deps -cargo install cargo-audit # Security audit -cargo install cargo-expand # Expand macros -cargo install cargo-flamegraph # CPU profiling -cargo install cargo-bloat # Binary size analysis -cargo install cargo-deny # Lint dependencies -cargo install bacon # Background checker -cargo install sccache # Compilation cache -``` diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/references/patterns.md b/packages/dotfiles/dot_agents/skills/rust-helper/references/patterns.md deleted file mode 100644 index 97fc4e41e5..0000000000 --- a/packages/dotfiles/dot_agents/skills/rust-helper/references/patterns.md +++ /dev/null @@ -1,757 +0,0 @@ -# Rust Patterns and Idioms - -Common Rust patterns covering ownership, borrowing, traits, generics, lifetimes, async/await, error handling, and iterators. - -## Ownership and Borrowing - -### Move Semantics - -Types that do not implement `Copy` are moved when assigned or passed to functions. After a move, the original binding is invalid. - -```rust -let s1 = String::from("hello"); -let s2 = s1; // s1 is moved into s2 -// println!("{s1}"); // Compile error: s1 has been moved - -// Function parameters take ownership -fn take_ownership(s: String) { - println!("{s}"); -} // s is dropped here - -let s = String::from("hello"); -take_ownership(s); -// s is no longer valid here -``` - -### Copy Types - -Primitive types (`i32`, `f64`, `bool`, `char`) and tuples of Copy types implement `Copy`. Assignment copies instead of moves. - -```rust -let x = 42; -let y = x; // x is copied, both are valid -println!("{x} {y}"); // Works fine -``` - -### Borrowing Rules - -1. Any number of immutable references `&T` can coexist -2. Only one mutable reference `&mut T` at a time -3. Cannot have `&T` and `&mut T` simultaneously -4. References must always be valid (no dangling refs) - -```rust -let mut data = vec![1, 2, 3]; - -// Multiple immutable borrows - OK -let a = &data; -let b = &data; -println!("{a:?} {b:?}"); - -// Mutable borrow - OK after immutable borrows are done -data.push(4); - -// Mutable borrow -let c = &mut data; -c.push(5); -// Cannot use `data` directly while `c` is alive -``` - -### Borrowing in Structs - -```rust -// Struct that borrows data needs lifetime annotations -struct Excerpt<'a> { - text: &'a str, -} - -// Struct that owns data needs no lifetimes -struct OwnedExcerpt { - text: String, -} - -// Prefer owned data in structs unless there is a clear performance reason -// to borrow. Owned structs are easier to work with (no lifetime propagation). -``` - -### Interior Mutability - -When mutation is needed behind a shared reference: - -```rust -use std::cell::RefCell; -use std::sync::{Arc, Mutex}; - -// Single-threaded: RefCell -let data = RefCell::new(vec![1, 2, 3]); -data.borrow_mut().push(4); - -// Multi-threaded: Mutex or RwLock -let data = Arc::new(Mutex::new(vec![1, 2, 3])); -let clone = Arc::clone(&data); -std::thread::spawn(move || { - clone.lock().unwrap().push(4); -}); -``` - -## Traits - -### Defining and Implementing Traits - -```rust -trait Summary { - // Required method - fn summarize(&self) -> String; - - // Default implementation - fn preview(&self) -> String { - format!("{}...", &self.summarize()[..20]) - } -} - -struct Article { - title: String, - content: String, -} - -impl Summary for Article { - fn summarize(&self) -> String { - format!("{}: {}", self.title, self.content) - } -} -``` - -### Trait Bounds - -```rust -// impl Trait syntax (preferred for simple cases) -fn notify(item: &impl Summary) { - println!("Breaking: {}", item.summarize()); -} - -// Trait bound syntax (needed for complex bounds) -fn notify(item: &T) { - println!("{item}: {}", item.summarize()); -} - -// where clause (for readability with many bounds) -fn process(t: &T, u: &U) -> String -where - T: Summary + Clone, - U: Display + Debug, -{ - format!("{}: {u:?}", t.summarize()) -} - -// Returning impl Trait -fn make_summary() -> impl Summary { - Article { - title: String::from("Title"), - content: String::from("Content"), - } -} -``` - -### Trait Objects (Dynamic Dispatch) - -Use `dyn Trait` when the concrete type is not known at compile time. - -```rust -// Box for owned trait objects -fn create_handler(kind: &str) -> Box { - match kind { - "file" => Box::new(FileHandler), - "network" => Box::new(NetworkHandler), - _ => Box::new(DefaultHandler), - } -} - -// &dyn Trait for borrowed trait objects -fn process(handler: &dyn Handler) { - handler.handle(); -} - -// Vec of trait objects -let handlers: Vec> = vec![ - Box::new(FileHandler), - Box::new(NetworkHandler), -]; -``` - -Trade-offs: - -- Generics (`impl Trait`): monomorphized, zero-cost, larger binary, known at compile time -- Trait objects (`dyn Trait`): vtable dispatch, smaller binary, runtime polymorphism - -### Trait Upcasting (Rust 1.86+) - -```rust -trait Base { - fn base_method(&self); -} - -trait Extended: Base { - fn extended_method(&self); -} - -// Coerce &dyn Extended to &dyn Base directly -fn use_base(obj: &dyn Extended) { - let base: &dyn Base = obj; // Trait upcasting, no workaround needed - base.base_method(); -} -``` - -### Common Standard Library Traits - -| Trait | Purpose | Derive? | -| -------------------- | --------------------------------------- | ------------------------ | -| `Debug` | Debug formatting `{:?}` | Yes | -| `Clone` | Explicit duplication | Yes | -| `Copy` | Implicit copy on assign | Yes (if all fields Copy) | -| `PartialEq` / `Eq` | Equality comparison | Yes | -| `PartialOrd` / `Ord` | Ordering comparison | Yes | -| `Hash` | Hashing for HashMap/HashSet | Yes | -| `Default` | Default value | Yes | -| `Display` | User-facing formatting | No (implement manually) | -| `From` / `Into` | Type conversion | No | -| `Iterator` | Iteration protocol | No | -| `Drop` | Custom destructor logic | No | -| `Send` | Safe to transfer across threads | Auto | -| `Sync` | Safe to share references across threads | Auto | - -## Generics - -### Generic Functions - -```rust -fn largest(list: &[T]) -> &T { - let mut largest = &list[0]; - for item in &list[1..] { - if item > largest { - largest = item; - } - } - largest -} -``` - -### Generic Structs - -```rust -struct Point { - x: T, - y: T, -} - -impl Point { - fn new(x: T, y: T) -> Self { - Self { x, y } - } -} - -// Constrained impl block -impl + Copy> Point { - fn sum(&self) -> T { - self.x + self.y - } -} - -// Impl for specific type -impl Point { - fn distance_from_origin(&self) -> f64 { - (self.x.powi(2) + self.y.powi(2)).sqrt() - } -} -``` - -### Generic Enums - -```rust -// The standard library's Result and Option are generic enums -enum MyResult { - Ok(T), - Err(E), -} - -// Newtype pattern with generics -struct Meters(T); -struct Seconds(T); -``` - -### Const Generics - -```rust -// Array with compile-time known size -struct Matrix { - data: [[f64; COLS]; ROWS], -} - -impl Matrix { - fn new() -> Self { - Self { - data: [[0.0; COLS]; ROWS], - } - } -} - -let m: Matrix<3, 4> = Matrix::new(); -``` - -## Lifetimes - -### Lifetime Elision Rules - -The compiler infers lifetimes following these rules: - -1. Each input reference gets a distinct lifetime -2. If there is exactly one input lifetime, it applies to all output references -3. If one input is `&self` or `&mut self`, its lifetime applies to all output references - -```rust -// Elided (compiler infers lifetimes) -fn first_word(s: &str) -> &str { ... } - -// Equivalent explicit form -fn first_word<'a>(s: &'a str) -> &'a str { ... } -``` - -### When Explicit Lifetimes Are Needed - -```rust -// Multiple input references - compiler cannot determine which output borrows from -fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { - if x.len() > y.len() { x } else { y } -} - -// Different lifetimes for different inputs -fn first_of<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str { - x -} -``` - -### Lifetime Bounds on Structs - -```rust -struct ImportantExcerpt<'a> { - part: &'a str, -} - -impl<'a> ImportantExcerpt<'a> { - // Lifetime elision rule 3: &self lifetime applies to output - fn level(&self) -> &str { - self.part - } -} -``` - -### Static Lifetime - -```rust -// 'static means the reference lives for the entire program -let s: &'static str = "I live forever"; - -// Trait bounds with 'static -fn spawn_task(task: impl FnOnce() + Send + 'static) { - std::thread::spawn(task); -} -``` - -## Error Handling - -### The `?` Operator - -```rust -use std::fs; -use std::io; - -fn read_username() -> Result { - let mut username = fs::read_to_string("username.txt")?; - username.truncate(username.trim_end().len()); - Ok(username) -} -``` - -### Custom Error Types with thiserror - -```rust -use thiserror::Error; - -#[derive(Error, Debug)] -enum AppError { - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - - #[error("Parse error: {0}")] - Parse(#[from] serde_json::Error), - - #[error("Not found: {0}")] - NotFound(String), - - #[error("Validation failed: {field} - {message}")] - Validation { field: String, message: String }, -} -``` - -### Application-Level Errors with anyhow - -```rust -use anyhow::{Context, Result, bail, ensure}; - -fn process_config(path: &str) -> Result { - let content = std::fs::read_to_string(path) - .context("Failed to read config file")?; - - let config: Config = serde_json::from_str(&content) - .context("Failed to parse config")?; - - ensure!(config.port > 0, "Port must be positive, got {}", config.port); - - if config.name.is_empty() { - bail!("Config name cannot be empty"); - } - - Ok(config) -} -``` - -### When to Use Each - -- **thiserror**: Library code. Define specific error variants callers can match on. -- **anyhow**: Application code. Aggregate errors with context for logging/display. -- **Custom enum without thiserror**: When no external dependency is desired. - -### Converting Between Error Types - -```rust -// From trait implementations (thiserror does this with #[from]) -impl From for AppError { - fn from(err: std::io::Error) -> Self { - AppError::Io(err) - } -} - -// map_err for manual conversion -let value = some_result.map_err(|e| AppError::NotFound(e.to_string()))?; -``` - -## Async/Await - -### Basic Async Pattern - -```rust -use tokio; - -#[tokio::main] -async fn main() { - let result = fetch_data("https://api.example.com").await; - println!("{result:?}"); -} - -async fn fetch_data(url: &str) -> Result { - let response = reqwest::get(url).await?; - let body = response.text().await?; - Ok(body) -} -``` - -### Spawning Tasks - -```rust -use tokio::task; - -// Spawn concurrent tasks -let handle1 = task::spawn(async { fetch_users().await }); -let handle2 = task::spawn(async { fetch_orders().await }); - -// Await both -let (users, orders) = tokio::join!(handle1, handle2); - -// select! for first-to-complete -tokio::select! { - result = fetch_fast() => println!("Fast: {result:?}"), - result = fetch_slow() => println!("Slow: {result:?}"), -} -``` - -### Async Closures (Rust 1.85+) - -```rust -// New in 2024 edition: async closures -let fetch = async |url: &str| { - reqwest::get(url).await?.text().await -}; - -let body = fetch("https://example.com").await?; -``` - -### Channels - -```rust -use tokio::sync::mpsc; - -let (tx, mut rx) = mpsc::channel(100); - -tokio::spawn(async move { - tx.send("hello").await.unwrap(); -}); - -while let Some(msg) = rx.recv().await { - println!("Got: {msg}"); -} -``` - -### Handling Blocking Code in Async Context - -```rust -// Move blocking work to a dedicated thread pool -let result = tokio::task::spawn_blocking(|| { - // CPU-intensive or blocking I/O work here - expensive_computation() -}).await?; -``` - -### Async Testing - -```rust -#[tokio::test] -async fn test_fetch() { - let result = fetch_data("https://httpbin.org/get").await; - assert!(result.is_ok()); -} - -// With timeout -#[tokio::test] -async fn test_with_timeout() { - let result = tokio::time::timeout( - std::time::Duration::from_secs(5), - fetch_data("https://example.com"), - ).await; - assert!(result.is_ok()); -} -``` - -## Iterator Patterns - -### Creating Iterators - -```rust -// From collections -let v = vec![1, 2, 3]; -let iter = v.iter(); // yields &i32 -let iter = v.iter_mut(); // yields &mut i32 -let iter = v.into_iter(); // yields i32 (consumes vec) - -// Range -let range = 0..10; // 0 to 9 -let inclusive = 0..=10; // 0 to 10 -``` - -### Common Adapters - -```rust -let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - -// filter + map -let even_doubled: Vec = numbers.iter() - .filter(|&&n| n % 2 == 0) - .map(|&n| n * 2) - .collect(); -// [4, 8, 12, 16, 20] - -// filter_map (filter + map combined) -let parsed: Vec = ["1", "two", "3", "four", "5"] - .iter() - .filter_map(|s| s.parse().ok()) - .collect(); -// [1, 3, 5] - -// flat_map -let words: Vec<&str> = vec!["hello world", "foo bar"] - .iter() - .flat_map(|s| s.split_whitespace()) - .collect(); -// ["hello", "world", "foo", "bar"] - -// take, skip, chain -let first_three: Vec<&i32> = numbers.iter().take(3).collect(); -let after_five: Vec<&i32> = numbers.iter().skip(5).collect(); -let combined: Vec = (0..3).chain(7..10).collect(); - -// enumerate, zip -for (i, val) in numbers.iter().enumerate() { - println!("{i}: {val}"); -} - -let keys = vec!["a", "b", "c"]; -let vals = vec![1, 2, 3]; -let pairs: Vec<_> = keys.iter().zip(vals.iter()).collect(); - -// windows, chunks -for window in numbers.windows(3) { - println!("{window:?}"); -} -for chunk in numbers.chunks(3) { - println!("{chunk:?}"); -} -``` - -### Consuming Iterators - -```rust -// collect into various types -let vec: Vec = (0..5).collect(); -let set: HashSet = (0..5).collect(); -let map: HashMap<&str, i32> = vec![("a", 1), ("b", 2)].into_iter().collect(); -let string: String = vec!['h', 'e', 'l', 'l', 'o'].into_iter().collect(); - -// fold (reduce with initial value) -let sum = numbers.iter().fold(0, |acc, &x| acc + x); - -// sum, product, min, max, count -let sum: i32 = numbers.iter().sum(); -let product: i32 = numbers.iter().product(); -let min = numbers.iter().min(); -let max = numbers.iter().max(); -let count = numbers.iter().count(); - -// any, all, find, position -let has_even = numbers.iter().any(|&n| n % 2 == 0); -let all_positive = numbers.iter().all(|&n| n > 0); -let first_even = numbers.iter().find(|&&n| n % 2 == 0); -let pos = numbers.iter().position(|&n| n == 5); -``` - -### Implementing Iterator - -```rust -struct Counter { - count: u32, - max: u32, -} - -impl Counter { - fn new(max: u32) -> Self { - Self { count: 0, max } - } -} - -impl Iterator for Counter { - type Item = u32; - - fn next(&mut self) -> Option { - if self.count < self.max { - self.count += 1; - Some(self.count) - } else { - None - } - } -} - -// Use it -let sum: u32 = Counter::new(5).sum(); // 1+2+3+4+5 = 15 -``` - -## Pattern Matching - -### Match Expressions - -```rust -// Match on enums -match command { - Command::Quit => println!("Quitting"), - Command::Echo(msg) => println!("{msg}"), - Command::Move { x, y } => println!("Moving to ({x}, {y})"), - Command::Color(r, g, b) => println!("Color: ({r}, {g}, {b})"), -} - -// Match with guards -match number { - n if n < 0 => println!("Negative"), - 0 => println!("Zero"), - n if n % 2 == 0 => println!("Positive even"), - _ => println!("Positive odd"), -} - -// Match on tuples -match (x, y) { - (0, 0) => println!("Origin"), - (x, 0) | (0, x) => println!("On axis: {x}"), - (x, y) => println!("Point: ({x}, {y})"), -} - -// Destructuring in match -match &person { - Person { name, age } if *age >= 18 => println!("{name} is an adult"), - Person { name, .. } => println!("{name} is a minor"), -} -``` - -### Let Chains (Rust 2024 Edition) - -```rust -// Combine let bindings with boolean conditions -if let Some(x) = opt_x && let Some(y) = opt_y && x > y { - println!("x ({x}) is greater than y ({y})"); -} - -// In while loops -while let Some(item) = iter.next() && item.is_valid() { - process(item); -} -``` - -## Smart Pointers - -```rust -// Box - heap allocation -let boxed = Box::new(5); - -// Rc - reference counted (single-threaded) -use std::rc::Rc; -let shared = Rc::new(vec![1, 2, 3]); -let clone = Rc::clone(&shared); // Increments ref count - -// Arc - atomic reference counted (multi-threaded) -use std::sync::Arc; -let shared = Arc::new(Mutex::new(HashMap::new())); - -// Cow - clone-on-write -use std::borrow::Cow; -fn process(input: &str) -> Cow { - if input.contains(' ') { - Cow::Owned(input.replace(' ', "_")) - } else { - Cow::Borrowed(input) - } -} -``` - -## Type Conversions - -```rust -// From/Into -impl From for String { - fn from(config: Config) -> Self { - format!("{}:{}", config.name, config.port) - } -} - -let s: String = config.into(); - -// TryFrom/TryInto for fallible conversions -impl TryFrom<&str> for Config { - type Error = ParseError; - - fn try_from(s: &str) -> Result { - // parse the string into Config - todo!() - } -} - -// AsRef / AsMut for cheap reference conversions -fn process(path: impl AsRef) { - let path = path.as_ref(); - // works with &str, String, PathBuf, &Path -} -``` diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/references/releases.md b/packages/dotfiles/dot_agents/skills/rust-helper/references/releases.md new file mode 100644 index 0000000000..c469d8008f --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/rust-helper/references/releases.md @@ -0,0 +1,57 @@ +# Rust release lifecycle + +Read this when upgrading Rust, selecting an edition, evaluating an API stabilization, or interpreting a compiler/tool performance claim. + +## Current stable + +Rust 1.97.1 is current as of 2026-08-03. Patch releases can contain regression and miscompilation fixes, so record the complete patch version in a reproducible toolchain. Keep `rust-version` at the actual minimum compiler the crate supports. + +Rust 2024 was released with Rust 1.85.0. Resolver 3 is its Cargo resolver, with explicit configuration needed for virtual workspaces. + +## Research ledger + +The following 36 official or project-primary pages were fetched and inspected: + +1. [Rust 1.97.1](https://blog.rust-lang.org/releases/1.97.1/) +2. [Rust 1.97.0](https://blog.rust-lang.org/releases/1.97.0/) +3. [Rust 1.96.1](https://blog.rust-lang.org/releases/1.96.1/) +4. [Rust 1.96.0](https://blog.rust-lang.org/releases/1.96.0/) +5. [Rust 1.95.0](https://blog.rust-lang.org/releases/1.95.0/) +6. [Rust 1.94.1](https://blog.rust-lang.org/releases/1.94.1/) +7. [Rust 1.94.0](https://blog.rust-lang.org/releases/1.94.0/) +8. [Rust 1.93.1](https://blog.rust-lang.org/releases/1.93.1/) +9. [Rust 1.93.0](https://blog.rust-lang.org/releases/1.93.0/) +10. [Rust 1.92.0](https://blog.rust-lang.org/releases/1.92.0/) +11. [Rust 1.91.0](https://blog.rust-lang.org/releases/1.91.0/) +12. [Rust 1.90.0](https://blog.rust-lang.org/releases/1.90.0/) +13. [Rust 1.88.0](https://blog.rust-lang.org/releases/1.88.0/) +14. [Rust 1.86.0](https://blog.rust-lang.org/releases/1.86.0/) +15. [Rust 1.85.0 and Rust 2024](https://blog.rust-lang.org/releases/1.85.0/) +16. [Rust 2024 Edition Guide](https://doc.rust-lang.org/edition-guide/rust-2024/index.html) +17. [Rust Reference: unsafety](https://doc.rust-lang.org/reference/unsafety.html) +18. [Rustonomicon: what unsafe can do](https://doc.rust-lang.org/nomicon/what-unsafe-does.html) +19. [cargo test](https://doc.rust-lang.org/cargo/commands/cargo-test.html) +20. [Cargo workspaces](https://doc.rust-lang.org/cargo/reference/workspaces.html) +21. [Dependency resolution](https://doc.rust-lang.org/cargo/reference/resolver.html) +22. [Rust version](https://doc.rust-lang.org/cargo/reference/rust-version.html) +23. [Future incompatibility reports](https://doc.rust-lang.org/cargo/reference/future-incompat-report.html) +24. [Clippy usage](https://doc.rust-lang.org/clippy/usage.html) +25. [Clippy configuration](https://doc.rust-lang.org/clippy/configuration.html) +26. [rustfmt](https://rust-lang.github.io/rustfmt/) +27. [rustup overrides](https://rust-lang.github.io/rustup/overrides.html) +28. [rustup components](https://rust-lang.github.io/rustup/concepts/components.html) +29. [rustc lint levels](https://doc.rust-lang.org/rustc/lints/levels.html) +30. [Rustdoc documentation tests](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html) +31. [The Book: unsafe Rust](https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html) +32. [Advanced edition migrations](https://doc.rust-lang.org/edition-guide/editions/advanced-migrations.html) +33. [Rust 2024 unsafe operations](https://doc.rust-lang.org/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html) +34. [cargo fix](https://doc.rust-lang.org/cargo/commands/cargo-fix.html) +35. [rustc tests](https://doc.rust-lang.org/rustc/tests/index.html) +36. [Miri README](https://github.com/rust-lang/miri/blob/master/README.md) + +## Qualification rules + +- Stabilization is version-specific; check the crate's MSRV before using an API. +- Performance, linker, cache, and test-speed figures are workload- and platform-dependent. +- LLD became the default only for specified targets such as `x86_64-unknown-linux-gnu`, not every Rust target. +- Rustfmt, Clippy, rust-analyzer, and Miri component versions and availability do not match rustc numerically. diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/references/safety-and-patterns.md b/packages/dotfiles/dot_agents/skills/rust-helper/references/safety-and-patterns.md new file mode 100644 index 0000000000..d921e7e9ba --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/rust-helper/references/safety-and-patterns.md @@ -0,0 +1,61 @@ +# Rust safety and implementation patterns + +Read this when reviewing unsafe code, ownership/borrowing, text slicing, async joins, cleanup, or assertions. + +## Unsafe obligations + +Unsafe Rust permits a limited set of operations that can cause undefined behavior if their contracts are violated. Other static checks remain active. + +For each unsafe function: + +- document caller obligations under `# Safety`, +- keep unsafe operations in explicit blocks, +- explain why each block satisfies pointer validity, initialization, alignment, aliasing, lifetime, and concurrency requirements, +- expose a safe abstraction only when it can uphold the invariant for every safe caller. + +Rust 2024's `unsafe_op_in_unsafe_fn` warning makes the explicit-block boundary visible even inside an unsafe function. + +## Disjoint mutable access + +Multiple mutable references are valid when proven disjoint. Prefer APIs such as slice splitting or stabilized disjoint indexing rather than unsafe pointer arithmetic. + +## UTF-8 previews + +Rust strings are UTF-8 and ranges use bytes. A safe character-limited preview is: + +```rust +fn preview(summary: &str) -> String { + let prefix: String = summary.chars().take(20).collect(); + format!("{prefix}...") +} +``` + +Decide whether grapheme clusters rather than Unicode scalar values are the product requirement; `.chars()` can split a user-perceived grapheme. + +## Async joins + +Joining `JoinHandle`s returns each handle's `Result`. Name and unwrap or propagate both layers accurately; do not label the raw joined values as completed domain objects. + +Async closures stabilized in Rust 1.85. They are not restricted to the Rust 2024 edition. + +## Cleanup + +Check errors from operations whose failure can invalidate results, including writers, trace output, database shutdown, and child-process exit. Drop-based cleanup cannot report an error, so use an explicit close/finish method where the API exposes meaningful failure. + +## Assertions + +Prefer assertions that show the actual mismatch: + +```rust +let value = parse("42")?; +assert_eq!(value, 42); +``` + +When success itself is the subject, use `expect` with useful context or return `Result` from the test. Avoid `assert!(result.is_ok())` because it hides the error. + +## Primary documentation + +- [Rust Reference: unsafety](https://doc.rust-lang.org/reference/unsafety.html) +- [Rustonomicon: what unsafe can do](https://doc.rust-lang.org/nomicon/what-unsafe-does.html) +- [The Book: unsafe Rust](https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html) +- [Rust 2024 unsafe operations](https://doc.rust-lang.org/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html) diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/references/testing-and-debugging.md b/packages/dotfiles/dot_agents/skills/rust-helper/references/testing-and-debugging.md new file mode 100644 index 0000000000..7c8003ea15 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/rust-helper/references/testing-and-debugging.md @@ -0,0 +1,65 @@ +# Rust testing and debugging + +Read this when writing tests, configuring Nextest or Bacon, running doctests, using Miri, or diagnosing runtime behavior. + +## Cargo and libtest arguments + +Arguments before `--` belong to Cargo. Arguments after it go to the test binary/libtest: + +```bash +cargo test --workspace --all-features -- --nocapture +``` + +Tests may return a type implementing `Termination`; a panic or non-zero termination fails the test. Prefer concrete assertions or `Result` propagation over `assert!(result.is_ok())`. + +## Doctests + +Rustdoc executes Rust code blocks as tests by default. Nextest does not run doctests, so retain: + +```bash +cargo test --workspace --doc +``` + +Mark examples `no_run`, `compile_fail`, or `ignore` only when that behavior is part of the documentation contract, not to hide a broken example. + +## Nextest + +Nextest runs tests in separate processes and provides filtering, archives, and reporting. It is optional and has no universal speed percentage. + +Keep retries at zero by default. A retry can mask a flake; enable it narrowly only with explicit reporting and a remediation owner. Do not present retrying as the normal CI profile. + +## Bacon + +TOML tables cannot be duplicated. A valid focused job combines its fields: + +```toml +[jobs.check] +command = ["cargo", "check", "--all-targets", "--color", "always"] +watch = ["src", "tests", "Cargo.toml"] +``` + +Treat Bacon as a local feedback tool, not evidence that the repository's final CI graph passed. + +## Miri + +Miri detects certain undefined behavior in executed paths. It cannot prove a library sound, and its Stacked Borrows and Tree Borrows models remain experimental. + +Miri isolation is deterministic behavior control, not a security sandbox. `-Zmiri-disable-isolation` exposes real host APIs and changes semantics; use it only with a understood test requirement. + +Most FFI and platform APIs are unsupported, though Miri emulates a limited subset. Do not state that all C calls are impossible, and do not promise a fixed slowdown range. + +## Edition and unsafe tests + +Run `cargo fix --edition` across relevant feature and target combinations because it only observes selected code. For unsafe code, combine focused invariant tests with Miri and, where appropriate, fuzz or property testing. + +## Debuggers + +Use the platform debugger through rust-analyzer or direct LLDB/GDB integration. LLDB's `po` command is Objective-C-oriented and is not a reliable generic Rust Debug/Display renderer. + +## Primary documentation + +- [cargo test](https://doc.rust-lang.org/cargo/commands/cargo-test.html) +- [rustc tests](https://doc.rust-lang.org/rustc/tests/index.html) +- [Rustdoc documentation tests](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html) +- [Miri README](https://github.com/rust-lang/miri/blob/master/README.md) +- [Advanced edition migrations](https://doc.rust-lang.org/edition-guide/editions/advanced-migrations.html) diff --git a/packages/dotfiles/dot_agents/skills/rust-helper/references/testing-debugging.md b/packages/dotfiles/dot_agents/skills/rust-helper/references/testing-debugging.md deleted file mode 100644 index 8560dde080..0000000000 --- a/packages/dotfiles/dot_agents/skills/rust-helper/references/testing-debugging.md +++ /dev/null @@ -1,716 +0,0 @@ -# Testing and Debugging Rust - -Guide to testing with cargo test and nextest, background checking with bacon, debugging with lldb/gdb, profiling, sccache, and miri. - -## Testing with cargo test - -### Basic Usage - -```bash -# Run all tests -cargo test - -# Run tests matching a name pattern -cargo test test_name -cargo test tests::module_name - -# Run tests in specific package -cargo test -p my-crate - -# Run specific test binary -cargo test --bin my-binary -cargo test --lib # Only library tests -cargo test --doc # Only doc tests - -# Show output from passing tests too -cargo test -- --show-output - -# Run ignored tests -cargo test -- --ignored - -# Run all tests including ignored -cargo test -- --include-ignored - -# Run tests single-threaded -cargo test -- --test-threads=1 - -# List tests without running -cargo test -- --list -``` - -### Test Organization - -```rust -// Unit tests - in the same file as the code -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic() { - assert_eq!(add(2, 3), 5); - } - - #[test] - fn test_with_result() -> Result<(), Box> { - let result = parse_config("valid_config")?; - assert_eq!(result.port, 8080); - Ok(()) - } - - #[test] - #[should_panic(expected = "index out of bounds")] - fn test_panic() { - let v = vec![1, 2, 3]; - let _ = v[99]; - } - - #[test] - #[ignore] // Skip by default, run with --ignored - fn expensive_test() { - // Long-running test - } -} -``` - -### Integration Tests - -Place files in `tests/` directory at the project root: - -```rust -// tests/integration_test.rs -use my_crate::Config; - -#[test] -fn test_full_workflow() { - let config = Config::new("test"); - assert!(config.validate().is_ok()); -} - -// tests/common/mod.rs - shared test utilities -pub fn setup() -> TestContext { - TestContext::new() -} -``` - -### Doc Tests - -````rust -/// Adds two numbers together. -/// -/// # Examples -/// -/// ``` -/// let result = my_crate::add(2, 3); -/// assert_eq!(result, 5); -/// ``` -/// -/// ```should_panic -/// my_crate::divide(1, 0); -/// ``` -/// -/// ```no_run -/// // Compiles but doesn't run in tests -/// my_crate::start_server(); -/// ``` -pub fn add(a: i32, b: i32) -> i32 { - a + b -} -```` - -### Test Fixtures and Setup - -```rust -use std::sync::Once; - -static INIT: Once = Once::new(); - -fn setup() { - INIT.call_once(|| { - // One-time initialization - env_logger::init(); - }); -} - -#[test] -fn test_with_setup() { - setup(); - // ... -} - -// Using tempfile for filesystem tests -#[test] -fn test_file_operations() -> Result<(), Box> { - let dir = tempfile::tempdir()?; - let file_path = dir.path().join("test.txt"); - std::fs::write(&file_path, "hello")?; - assert_eq!(std::fs::read_to_string(&file_path)?, "hello"); - Ok(()) // dir is cleaned up on drop -} -``` - -### Async Tests - -```rust -#[tokio::test] -async fn test_async_operation() { - let result = fetch_data().await; - assert!(result.is_ok()); -} - -// With multi-threaded runtime -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_concurrent() { - let (a, b) = tokio::join!(task_a(), task_b()); - assert!(a.is_ok()); - assert!(b.is_ok()); -} -``` - -## Testing with cargo-nextest - -### Installation - -```bash -cargo install cargo-nextest -``` - -### Basic Usage - -```bash -# Run all tests -cargo nextest run - -# Filter by test name -cargo nextest run test_pattern - -# Filter by package -cargo nextest run -p my-crate - -# Run with specific number of threads -cargo nextest run -j 4 - -# Run with retries (useful for flaky tests) -cargo nextest run --retries 2 - -# Run ignored tests -cargo nextest run --run-ignored ignored-only - -# List tests -cargo nextest list - -# Archive tests for running on another machine -cargo nextest archive --archive-file tests.tar.zst -cargo nextest run --archive-file tests.tar.zst -``` - -### Nextest Configuration - -Create `.config/nextest.toml` in the project root: - -```toml -[profile.default] -retries = 0 -test-threads = "num-cpus" -fail-fast = true -slow-timeout = { period = "60s", terminate-after = 2 } -status-level = "pass" -final-status-level = "flaky" - -[profile.ci] -retries = 2 -fail-fast = false -slow-timeout = { period = "120s", terminate-after = 3 } - -# Override settings per test -[[profile.default.overrides]] -filter = "test(test_slow)" -slow-timeout = { period = "120s" } -threads-required = 1 -``` - -### Nextest Filter Expressions - -```bash -# Run tests matching name -cargo nextest run -E 'test(test_parse)' - -# Run tests in specific package -cargo nextest run -E 'package(my-crate)' - -# Run tests in specific binary -cargo nextest run -E 'binary(my-crate::bin/my-binary)' - -# Combine filters -cargo nextest run -E 'package(core) & test(parse)' - -# Exclude tests -cargo nextest run -E 'not test(slow)' - -# Platform-specific -cargo nextest run -E 'platform(target) & test(unix)' -``` - -### Advantages Over cargo test - -- Runs each test in a separate process (better isolation) -- Up to 60% faster for large test suites -- Better output formatting and progress reporting -- Built-in retry support for flaky tests -- Filter expressions for precise test selection -- Archive support for CI/CD pipelines -- Slow test detection and termination - -## Bacon (Background Checker) - -### Installation and Usage - -```bash -cargo install bacon - -# Start bacon (defaults to `cargo check`) -bacon - -# Start with specific job -bacon clippy -bacon test -bacon doc -``` - -### Keyboard Shortcuts - -| Key | Action | -| ----- | ------------------------------ | -| `c` | Switch to clippy job | -| `t` | Switch to test job | -| `d` | Switch to doc job | -| `r` | Rerun current job | -| `f` | Focus on failing test | -| `w` | Toggle wrapping | -| `s` | Toggle summary mode | -| `Esc` | Back to all tests / quit focus | -| `q` | Quit bacon | - -### Configuration - -Create `bacon.toml` in the project root: - -```toml -default_job = "check" - -[jobs.check] -command = ["cargo", "check", "--all-targets", "--color", "always"] - -[jobs.clippy] -command = ["cargo", "clippy", "--all-targets", "--color", "always"] - -[jobs.test] -command = ["cargo", "test", "--color", "always"] -need_stdout = true - -[jobs.nextest] -command = ["cargo", "nextest", "run", "--color", "always"] -need_stdout = true - -[jobs.doc] -command = ["cargo", "doc", "--no-deps", "--color", "always"] - -[jobs.run] -command = ["cargo", "run", "--color", "always"] -need_stdout = true -allow_warnings = true -background = true - -# Watch additional directories -[jobs.check] -watch = ["src", "tests", "Cargo.toml"] -``` - -## Debugging with LLDB and GDB - -### Setup - -Compile with debug info (default in dev profile): - -```bash -# Debug build (has debug symbols) -cargo build - -# Release build with debug info -# Add to Cargo.toml: -# [profile.release] -# debug = true -cargo build --release -``` - -### LLDB (macOS default, Linux) - -```bash -# Start LLDB with binary -rust-lldb target/debug/my-binary - -# Or with arguments -rust-lldb -- target/debug/my-binary arg1 arg2 -``` - -Common LLDB commands: - -``` -# Set breakpoints -b main # Break at main -b src/parser.rs:42 # Break at file:line -b my_crate::parse_config # Break at function -br set -n parse_config -c "input.len() > 100" # Conditional breakpoint - -# Run and control -r # Run -n # Step over (next line) -s # Step into -finish # Step out (finish current function) -c # Continue to next breakpoint - -# Inspect -p variable_name # Print variable -p *pointer # Dereference pointer -po my_vec # Pretty-print (calls Debug/Display) -frame variable # All local variables -bt # Backtrace -bt all # All thread backtraces - -# Watchpoints -w set var my_variable # Break when variable changes -w set expr -w write -- &data[0] # Watch memory address - -# Thread inspection -thread list # List all threads -thread select 2 # Switch to thread 2 -``` - -### GDB (Linux preferred) - -```bash -# Start GDB -rust-gdb target/debug/my-binary -``` - -Common GDB commands: - -``` -# Breakpoints -break main -break src/parser.rs:42 -break parse_config if input.len() > 100 - -# Run and control -run -next # Step over -step # Step into -finish # Step out -continue # Continue - -# Inspect -print variable -print *pointer -info locals -backtrace -info threads -thread 2 - -# TUI mode -tui enable # Show source in terminal -layout src # Source layout -layout split # Source + assembly -``` - -### VS Code Debugging - -Install the CodeLLDB extension. Add `.vscode/launch.json`: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug binary", - "cargo": { - "args": ["build", "--bin=my-binary"], - "filter": { - "name": "my-binary", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests", - "cargo": { - "args": ["test", "--no-run", "--lib"], - "filter": { - "name": "my-crate", - "kind": "lib" - } - }, - "args": ["--test-threads=1"], - "cwd": "${workspaceFolder}" - } - ] -} -``` - -## Profiling - -### CPU Profiling with flamegraph - -```bash -cargo install flamegraph - -# Generate flamegraph -cargo flamegraph # Profile default binary -cargo flamegraph --bin my-binary # Specific binary -cargo flamegraph -- --arg1 value # With arguments - -# On macOS, may need: -cargo flamegraph --root -# Or use dtrace permissions: -sudo cargo flamegraph -``` - -### Binary Size Analysis - -```bash -cargo install cargo-bloat - -# Show largest functions -cargo bloat --release - -# Show largest crates -cargo bloat --release --crates - -# Show top N -cargo bloat --release -n 20 -``` - -### Compile Time Analysis - -```bash -# Show time spent in each compilation step -cargo build --timings - -# Output HTML report -cargo build --timings=html -``` - -## sccache (Shared Compilation Cache) - -### Setup - -```bash -# Install -cargo install sccache - -# Configure globally in ~/.cargo/config.toml -[build] -rustc-wrapper = "sccache" - -# Or set environment variable -export RUSTC_WRAPPER=sccache -``` - -### Usage - -```bash -# Check cache statistics -sccache --show-stats - -# Reset statistics -sccache --zero-stats - -# Stop the sccache server -sccache --stop-server - -# Start with specific cache size -SCCACHE_CACHE_SIZE="10G" sccache --start-server -``` - -### Remote Cache Backends - -```bash -# S3 backend -export SCCACHE_BUCKET=my-sccache-bucket -export AWS_ACCESS_KEY_ID=... -export AWS_SECRET_ACCESS_KEY=... - -# Redis backend -export SCCACHE_REDIS=redis://localhost:6379 - -# Local disk (default) -export SCCACHE_DIR=/path/to/cache -export SCCACHE_CACHE_SIZE=10G -``` - -## Miri (Undefined Behavior Detection) - -### Setup and Usage - -```bash -# Install miri (nightly only) -rustup +nightly component add miri - -# Run program under miri -cargo +nightly miri run - -# Run tests under miri -cargo +nightly miri test - -# Run specific test -cargo +nightly miri test test_name - -# Run with specific flags -MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test -MIRIFLAGS="-Zmiri-symbolic-alignment-check" cargo +nightly miri test -``` - -### What Miri Detects - -- Out-of-bounds memory accesses -- Use-after-free -- Use of uninitialized data -- Invalid use of primitives (e.g., bool that is not 0 or 1) -- Violation of aliasing rules (Stacked Borrows / Tree Borrows) -- Data races in concurrent code -- Memory leaks -- Invalid pointer arithmetic / provenance violations -- Deadlocks - -### Common Miri Flags - -| Flag | Purpose | -| --------------------------------- | --------------------------------------- | -| `-Zmiri-disable-isolation` | Allow system calls (file I/O, env vars) | -| `-Zmiri-symbolic-alignment-check` | Stricter alignment checking | -| `-Zmiri-tree-borrows` | Use Tree Borrows model (experimental) | -| `-Zmiri-seed=N` | Set random seed for reproducibility | -| `-Zmiri-ignore-leaks` | Do not report memory leaks | - -### Miri Limitations - -- Cannot run code that calls into C libraries (FFI) -- Significantly slower than normal execution (10-100x) -- Requires nightly toolchain -- Does not detect all forms of undefined behavior -- Cannot check hardware-specific behavior - -### When to Use Miri - -Run miri on code that: - -- Uses `unsafe` blocks -- Performs raw pointer manipulation -- Implements data structures with manual memory management -- Uses transmute or other low-level operations -- Implements concurrent algorithms with atomics - -## Benchmarking - -### Criterion - -```rust -// benches/my_benchmark.rs -use criterion::{criterion_group, criterion_main, Criterion, black_box}; - -fn fibonacci(n: u64) -> u64 { - match n { - 0 => 1, - 1 => 1, - n => fibonacci(n - 1) + fibonacci(n - 2), - } -} - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20)))); - - // Benchmark group for comparison - let mut group = c.benchmark_group("fibonacci"); - for size in [10, 15, 20].iter() { - group.bench_with_input( - format!("fib_{size}"), - size, - |b, &size| b.iter(|| fibonacci(black_box(size))), - ); - } - group.finish(); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); -``` - -```toml -# Cargo.toml -[[bench]] -name = "my_benchmark" -harness = false - -[dev-dependencies] -criterion = { version = "0.5", features = ["html_reports"] } -``` - -```bash -# Run benchmarks -cargo bench - -# Run specific benchmark -cargo bench -- fibonacci - -# Compare against baseline -cargo bench -- --save-baseline before -# Make changes... -cargo bench -- --baseline before -``` - -## CI/CD Patterns - -### GitHub Actions Example - -```yaml -name: CI -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - - name: Check - run: cargo check --workspace --all-targets - - - name: Clippy - run: cargo clippy --workspace --all-targets -- -D warnings - - - name: Format - run: cargo fmt -- --check - - - name: Test - run: cargo nextest run --workspace - - - name: Doc tests - run: cargo test --doc - - miri: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - components: miri - - name: Miri - run: cargo +nightly miri test -``` diff --git a/packages/dotfiles/dot_agents/skills/typescript-helper/SKILL.md b/packages/dotfiles/dot_agents/skills/typescript-helper/SKILL.md index 31289969d0..8caecb6b04 100644 --- a/packages/dotfiles/dot_agents/skills/typescript-helper/SKILL.md +++ b/packages/dotfiles/dot_agents/skills/typescript-helper/SKILL.md @@ -1,671 +1,182 @@ --- name: typescript-helper -description: | - TypeScript development guidance for type systems and tooling - When user works with .ts or .tsx files, mentions TypeScript, or encounters type errors +description: Current TypeScript configuration, migration, module, runtime, typing, and tooling guidance. Use when writing or reviewing TypeScript, selecting tsconfig settings, migrating TypeScript versions, resolving module/runtime mismatches, or designing safe typed boundaries. --- -# TypeScript Helper Agent +# TypeScript Helper -## What's New in TypeScript 5.7 & 2025 +Use TypeScript to make program contracts explicit, then validate data where static types end. Choose compiler and module settings from the actual runtime or bundler instead of applying one universal configuration. -- **Never-Initialized Variables**: Detects variables that are never assigned in nested scopes -- **Path Rewriting**: `--rewriteRelativeImportExtensions` auto-converts .ts → .js imports -- **ES2024 Support**: `Object.groupBy()`, `Map.groupBy()`, `Promise.withResolvers()` -- **V8 Compile Caching**: `module.enableCompileCache()` = ~2.5x faster startup (Node 22+) -- **TypeScript 7.0 Preview**: 10x speedup, multi-threaded builds coming soon -- **Direct Execution**: ts-node, tsx, and Node 23.x `--experimental-strip-types` +## Current status -## Overview +Verified 2026-08-03. -This agent helps you work with TypeScript for type-safe development, including type system usage, configuration, error resolution, and tooling integration. +| Line | Status | Important boundary | +| --- | --- | --- | +| TypeScript 7.0 | Current stable native compiler and language server | No programmatic API in 7.0; tools that import or embed TypeScript may still require 6.x | +| TypeScript 6.x | JavaScript-codebase compatibility bridge | Use it to expose migration issues and stabilize type ordering before 7.0 | +| `typescript@next` | Nightly builds | Preview only; do not silently use it in production | -## CLI Commands +TypeScript reports typical 8–12x full-build improvements for the native 7.0 compiler. Treat that as an official benchmark range, not a universal guarantee. The experimental `--checkers` and `--builders` controls trade memory for concurrency; do not make them default without measuring the project. -### TypeScript Compiler +Always inspect the selected toolchain. A bare `tsc` on `PATH` can resolve to the +6.x compatibility bridge even when a package also has the native compiler +installed; use the explicit native-compiler path when both are present (this +monorepo's `scripts/compliance-check.ts` enforces this same +`PATH=node_modules/@typescript/native/bin:$PATH tsc` form): ```bash -# Compile TypeScript files -tsc - -# Watch mode -tsc --watch - -# Compile specific file -tsc app.ts - -# Check types without emitting -tsc --noEmit - -# Show compiler version -tsc --version - -# Initialize tsconfig.json -tsc --init +PATH=node_modules/@typescript/native/bin:$PATH tsc --version +PATH=node_modules/@typescript/native/bin:$PATH tsc --showConfig ``` -### Type Checking - -```bash -# Type check entire project -tsc --noEmit - -# Type check with specific config -tsc --project tsconfig.build.json --noEmit +Read [references/releases-and-migration.md](references/releases-and-migration.md) before a TypeScript 6 or 7 upgrade. Read [references/configuration.md](references/configuration.md) when creating or reviewing a tsconfig. Read [references/modules-and-runtimes.md](references/modules-and-runtimes.md) for Node, Bun, bundler, ESM, and CommonJS decisions. Read [references/type-safety.md](references/type-safety.md) for narrowing, generics, validation, and exhaustiveness. Read [references/tooling.md](references/tooling.md) for ESLint and execution tooling. -# Type check single file -tsc --noEmit file.ts -``` +## Project-safe commands -### Running TypeScript +Type-check through the project configuration or build graph: ```bash -# Using ts-node -ts-node app.ts - -# Using tsx (faster, recommended) -tsx app.ts - -# Using bun (fastest for most workloads) -bun run app.ts - -# Node.js 23+ with experimental type stripping (no transpilation!) -node --experimental-strip-types app.ts - -# With V8 compile caching for 2.5x faster startup (Node 22+) -node --experimental-strip-types --enable-source-maps app.ts +tsc -p tsconfig.json --noEmit +tsc -b ``` -### Modern TypeScript 5.7+ Features - -**Path rewriting for imports**: +Passing source filenames to `tsc` bypasses project configuration historically. TypeScript 7 errors when a config is present unless that bypass is made explicit with `--ignoreConfig`. Do not use `tsc file.ts` as the normal project check. -```typescript -// tsconfig.json -{ - "compilerOptions": { - "rewriteRelativeImportExtensions": true - } -} - -// You write: -import { foo } from "./utils.ts"; - -// TypeScript rewrites to: -import { foo } from "./utils.js"; - -// Enables direct .ts imports that work in Node.js ESM -``` - -**ES2024 features now available**: - -```typescript -// Object.groupBy() -const people = [ - { name: "Alice", age: 30 }, - { name: "Bob", age: 25 }, - { name: "Charlie", age: 30 }, -]; - -const byAge = Object.groupBy(people, (person) => person.age); -// { 25: [{name: "Bob", ...}], 30: [{name: "Alice", ...}, {name: "Charlie", ...}] } - -// Map.groupBy() -const grouped = Map.groupBy(people, (person) => person.age); -// Map { 25 => [{...}], 30 => [{...}, {...}] } - -// Promise.withResolvers() -const { promise, resolve, reject } = Promise.withResolvers(); -setTimeout(() => resolve(42), 1000); -await promise; // 42 -``` - -**V8 compile caching (Node 22+)**: - -```typescript -// Enable at app entry point for ~2.5x faster startup -import { enableCompileCache } from "node:module"; - -enableCompileCache(); - -// All subsequent module loads use V8's code cache -``` - -## Common TypeScript Patterns - -### Type Annotations - -```typescript -// Basic types -let name: string = "Alice"; -let age: number = 30; -let active: boolean = true; -let items: string[] = ["a", "b", "c"]; -let tuple: [string, number] = ["hello", 42]; - -// Objects -interface User { - id: number; - name: string; - email?: string; // Optional property - readonly createdAt: Date; // Readonly -} - -const user: User = { - id: 1, - name: "Alice", - createdAt: new Date(), -}; - -// Functions -function greet(name: string): string { - return `Hello, ${name}`; -} - -const add = (a: number, b: number): number => a + b; - -// Async functions -async function fetchData(): Promise { - const response = await fetch("/api/user"); - return response.json(); -} -``` +In this monorepo, prefer its focused Turbo task over invoking a different compiler configuration: -### Interfaces vs Types - -```typescript -// Interface -interface Point { - x: number; - y: number; -} - -// Type alias -type Point2D = { - x: number; - y: number; -}; - -// Type alias for union -type Status = "pending" | "approved" | "rejected"; - -// Extending interface -interface Point3D extends Point { - z: number; -} - -// Intersection type -type ColoredPoint = Point & { - color: string; -}; -``` - -### Generics - -```typescript -// Generic function -function identity(value: T): T { - return value; -} - -// Generic interface -interface Container { - value: T; - getValue(): T; -} - -// Generic with constraints -function getProperty(obj: T, key: K): T[K] { - return obj[key]; -} - -// Generic with default -interface Response { - data: T; - status: number; -} -``` - -### Utility Types - -```typescript -// Partial - all properties optional -type PartialUser = Partial; - -// Required - all properties required -type RequiredUser = Required; - -// Pick - select specific properties -type UserBasic = Pick; - -// Omit - exclude specific properties -type UserWithoutEmail = Omit; - -// Record - create object type -type UserRoles = Record; - -// Exclude/Extract -type Status = "pending" | "approved" | "rejected"; -type ApprovedStatus = Extract; -type NotPending = Exclude; - -// ReturnType -type AddResult = ReturnType; // number - -// Parameters -type AddParams = Parameters; // [number, number] +```bash +bunx turbo run typecheck --filter= ``` -### Advanced Patterns - -```typescript -// Discriminated unions -type Shape = - | { kind: "circle"; radius: number } - | { kind: "rectangle"; width: number; height: number }; - -function area(shape: Shape): number { - switch (shape.kind) { - case "circle": - return Math.PI * shape.radius ** 2; - case "rectangle": - return shape.width * shape.height; - } -} - -// Branded types -type UserId = string & { readonly __brand: "UserId" }; -type Email = string & { readonly __brand: "Email" }; +## Model the runtime -function createUserId(id: string): UserId { - return id as UserId; -} +| Host | Compiler model | Execution boundary | +| --- | --- | --- | +| Modern Node | `module: "nodenext"` | Node's built-in TypeScript ignores tsconfig and directly runs only erasable syntax | +| Node 20 contract | `module: "node20"` | Fixed Node 20 behavior; use only when deliberately targeting it | +| Bundler or Bun application | `module: "preserve"` or `"esnext"`, `moduleResolution: "bundler"` | The runtime/bundler resolves and transforms modules; type-check separately | +| Multi-project library | Project references and `tsc -b` | Referenced projects require `composite` | -// Type guards -function isString(value: unknown): value is string { - return typeof value === "string"; -} +Do not rank Node, Bun, `tsx`, or `ts-node` as universally fastest. Select based on runtime semantics, transformation support, type-checking needs, and repository conventions. -// Assertion functions -function assertString(value: unknown): asserts value is string { - if (typeof value !== "string") { - throw new Error("Not a string"); - } -} -``` - -## tsconfig.json Configuration +## Strict baseline -### Basic Configuration +Start with `strict`; add checks that reflect the project's boundary and indexing risks. The exact set enabled by `strict` can grow between releases. ```json { "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "lib": ["ES2020", "DOM"], - "outDir": "./dist", - "rootDir": "./src", "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} -``` - -### Strict Mode Options - -```json -{ - "compilerOptions": { - "strict": true, // Enables all below - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "alwaysStrict": true - } -} -``` - -### Additional Checks - -```json -{ - "compilerOptions": { - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, - "noImplicitOverride": true + "exactOptionalPropertyTypes": true, + "noUncheckedSideEffectImports": true, + "types": [] } } ``` -## Common Type Errors and Fixes - -### Error: Type 'X' is not assignable to type 'Y' - -```typescript -// Problem -let num: number = "5"; // Error - -// Fix: Correct the type -let num: number = 5; +- `noUncheckedIndexedAccess` adds `undefined` to undeclared index-signature lookups. +- `exactOptionalPropertyTypes` distinguishes absence from a present `undefined` value. +- An explicit `types` list prevents accidental global-type inclusion and is required during the TypeScript 7 migration because 7.0 defaults it to `[]`. +- `skipLibCheck` is a deliberate compatibility and performance trade-off, not a correctness baseline. Do not use it to hide an upgrade failure. -// Or parse if from string -let num: number = parseInt("5"); -``` +## Validate external data -### Error: Object is possibly 'null' or 'undefined' +Static annotations do not validate JSON, environment variables, HTTP bodies, database rows, cache values, or deserialized files. Keep these values unknown until parsed. ```typescript -// Problem -function greet(name: string | null) { - return name.toUpperCase(); // Error -} - -// Fix 1: Type guard -function greet(name: string | null) { - if (name === null) return ""; - return name.toUpperCase(); -} - -// Fix 2: Non-null assertion (use cautiously) -function greet(name: string | null) { - return name!.toUpperCase(); -} +import { z } from "zod"; -// Fix 3: Optional chaining -function greet(name: string | null) { - return name?.toUpperCase() ?? ""; -} -``` - -### Error: Property 'X' does not exist on type 'Y' - -```typescript -// Problem -const obj: { name: string } = { name: "Alice", age: 30 }; // Error - -// Fix: Add property to type -interface Person { - name: string; - age: number; -} - -const obj: Person = { name: "Alice", age: 30 }; -``` - -### Error: Argument of type 'X' is not assignable to parameter of type 'Y' - -```typescript -// Problem -function greet(name: string) { - console.log(name); -} -greet(123); // Error - -// Fix: Pass correct type -greet("Alice"); - -// Or convert -greet(String(123)); -``` - -## Integration with Build Tools - -### Vite - -```typescript -// vite.config.ts -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [react()], - resolve: { - alias: { - "@": "/src", - }, - }, +const User = z.object({ + id: z.string().uuid(), + email: z.string().email(), }); -``` -### Webpack - -```javascript -// webpack.config.js -module.exports = { - entry: "./src/index.ts", - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - resolve: { - extensions: [".tsx", ".ts", ".js"], - }, - output: { - filename: "bundle.js", - path: path.resolve(__dirname, "dist"), - }, -}; -``` +type User = z.infer; -### ESLint - -```javascript -// .eslintrc.js -module.exports = { - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint"], - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - parserOptions: { - project: "./tsconfig.json", - }, -}; +async function fetchUser(id: string): Promise { + const response = await fetch(`/api/users/${encodeURIComponent(id)}`); + if (!response.ok) { + throw new Error(`User request failed: ${response.status}`); + } + return User.parse(await response.json()); +} ``` -## Testing with TypeScript - -### Jest +Do not assign `response.json()` directly to a domain type. Do not use a type assertion to manufacture a branded ID; validate and construct it through a schema or omit the brand. -```typescript -// jest.config.js -module.exports = { - preset: "ts-jest", - testEnvironment: "node", -}; - -// __tests__/user.test.ts -import { createUser } from "../src/user"; - -describe("User", () => { - it("creates user with valid data", () => { - const user = createUser({ name: "Alice", age: 30 }); - expect(user.name).toBe("Alice"); - }); -}); -``` +## Narrow instead of asserting -### Vitest +Use control-flow checks, discriminants, `typeof`, `instanceof`, `Array.isArray`, property checks, or a runtime schema. Avoid non-null assertions and unchecked casts. ```typescript -// vitest.config.ts -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - globals: true, - environment: "node", - }, -}); - -// src/user.test.ts -import { describe, it, expect } from "vitest"; -import { createUser } from "./user"; - -describe("createUser", () => { - it("creates user", () => { - const user = createUser({ name: "Alice" }); - expect(user.name).toBe("Alice"); - }); -}); -``` - -## Best Practices - -1. **Enable Strict Mode**: Always use `"strict": true` in tsconfig.json -2. **Avoid `any`**: Use `unknown` or proper types instead -3. **Use Type Inference**: Let TypeScript infer types when obvious -4. **Prefer Interfaces for Objects**: Use interfaces for object shapes -5. **Use Readonly**: Mark properties readonly when they shouldn't change -6. **Discriminated Unions**: Use for variant types -7. **Type Guards**: Write type guards for runtime type checking -8. **Utility Types**: Leverage built-in utility types - -## Common Workflows - -### Migration from JavaScript - -```bash -# 1. Rename .js to .ts -find src -name "*.js" -exec sh -c 'mv "$1" "${1%.js}.ts"' _ {} \; - -# 2. Add tsconfig.json -tsc --init - -# 3. Fix type errors gradually -tsc --noEmit --skipLibCheck - -# 4. Enable strict mode incrementally -# Start with noImplicitAny, then add others -``` - -### Type Declaration Files +type State = + | { kind: "idle" } + | { kind: "loading" } + | { kind: "loaded"; count: number } + | { kind: "failed"; message: string }; -```typescript -// types/express.d.ts -declare namespace Express { - export interface Request { - user?: { - id: string; - email: string; - }; +function describe(state: State): string { + switch (state.kind) { + case "idle": + return "Idle"; + case "loading": + return "Loading"; + case "loaded": + return `${state.count} records`; + case "failed": + return state.message; + default: + return assertNever(state); } } -// types/globals.d.ts -declare global { - interface Window { - myApp: { - version: string; - }; - } +function assertNever(value: never): never { + throw new Error(`Unhandled state: ${JSON.stringify(value)}`); } - -export {}; ``` -## Examples +A reducer that returns the old state from a default branch hides newly added actions. Exhaustive `never` handling makes the missing case fail during checking. -### Example 1: API Response Type +## Type design -```typescript -interface ApiResponse { - data: T; - status: number; - error?: string; -} - -async function fetchUser(id: string): Promise> { - const response = await fetch(`/api/users/${id}`); - return response.json(); -} - -// Usage -const result = await fetchUser("123"); -if (result.error) { - console.error(result.error); -} else { - console.log(result.data.name); -} -``` +- Let inference handle obvious local values and return types when the public contract remains clear. +- Use generics when a type parameter relates two or more values; do not introduce a type parameter used only once. +- Prefer `unknown` to `any` at an untyped boundary, then narrow it. +- `readonly` blocks reassignment during checking; it is not deep runtime immutability. +- Interfaces and type aliases overlap. Use interfaces when declaration merging or reopenable object contracts are desired; use aliases for unions, primitives, tuples, and type composition. +- Optional callback parameters mean the callback may be invoked without that argument. They do not mean the consumer may ignore a required argument. +- Use primitive `string`, `number`, and `boolean` types, not boxed `String`, `Number`, and `Boolean`. -### Example 2: Form Validation +## Modules and imports -```typescript -interface FormData { - email: string; - password: string; -} +`module` and `moduleResolution` jointly model emitted syntax and how the host resolves imports. `node10` and `classic` resolution are obsolete for new projects. -type ValidationErrors = Partial>; +Use `import type` and `export type` when an import exists only for types. With `verbatimModuleSyntax`, value imports remain and explicit type-only imports are erased. -function validate(data: FormData): ValidationErrors { - const errors: ValidationErrors = {}; +`rewriteRelativeImportExtensions` rewrites only static relative `.ts`, `.tsx`, `.mts`, and `.cts` specifiers. It does not resolve path aliases, package `imports` or `exports`, dependency specifiers, or computed dynamic imports. Keep it distinct from `allowImportingTsExtensions`. - if (!data.email.includes("@")) { - errors.email = "Invalid email"; - } +## Migration discipline - if (data.password.length < 8) { - errors.password = "Password too short"; - } +Do not mass-rename a JavaScript tree. Official migration guidance starts with `allowJs` and a separate output directory, then tightens checks and converts modules incrementally. Rename imports and configuration deliberately, exclude generated or vendor code by design, and verify each package through its real build graph. - return errors; -} -``` +For TypeScript 6 → 7, first make the project clean on 6.x with `stableTypeOrdering`, then address the removed and changed options listed in [references/releases-and-migration.md](references/releases-and-migration.md). Confirm whether framework tooling embeds the TypeScript programmatic API before selecting 7.0. -### Example 3: State Management +## Tooling -```typescript -type State = { - user: User | null; - loading: boolean; - error: string | null; -}; - -type Action = - | { type: "FETCH_START" } - | { type: "FETCH_SUCCESS"; payload: User } - | { type: "FETCH_ERROR"; payload: string }; - -function reducer(state: State, action: Action): State { - switch (action.type) { - case "FETCH_START": - return { ...state, loading: true }; - case "FETCH_SUCCESS": - return { ...state, loading: false, user: action.payload }; - case "FETCH_ERROR": - return { ...state, loading: false, error: action.payload }; - default: - return state; - } -} -``` +Current typescript-eslint setup uses flat `eslint.config.mjs`. Typed linting uses type-aware configurations and `parserOptions.projectService: true`. Keep the project compiler check separate from runtime execution: Node or Bun running a `.ts` file does not prove it type-checks. -## When to Ask for Help +Do not duplicate a generic Vite, Webpack, test-runner, or framework setup in this skill. Load the matching tool skill and use its current official configuration. -Ask the user for clarification when: +## Review checklist -- The desired type structure is ambiguous -- Multiple valid typing approaches exist -- Migration from JavaScript needs strategy decisions -- Build tool integration specifics are unclear -- Type error resolution requires code refactoring decisions +- Confirm the installed TypeScript version and whether consumers need its programmatic API. +- Invoke the project config or build graph, not a source-file bypass. +- Match module settings to Node, Bun, or the actual bundler. +- Validate every external value before assigning a domain type. +- Replace assertions and non-null promises with narrowing or schemas. +- Make discriminated unions exhaustive. +- Use strict checks intentionally and do not hide dependency errors with `skipLibCheck`. +- Keep runtime execution, transpilation, and type-checking as separate claims. +- Read migration notes for every version crossed. diff --git a/packages/dotfiles/dot_agents/skills/typescript-helper/references/configuration.md b/packages/dotfiles/dot_agents/skills/typescript-helper/references/configuration.md new file mode 100644 index 0000000000..42c3f07b72 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/typescript-helper/references/configuration.md @@ -0,0 +1,75 @@ +# TypeScript configuration + +Read this when creating a tsconfig, selecting strict flags, or wiring a multi-project build. + +## Configuration discovery + +Running `tsc` without filenames searches upward for a tsconfig. `tsc -p path` selects one explicitly. `tsc --showConfig` displays the resolved configuration. + +Source filenames on the command line historically ignore tsconfig. TypeScript 7 rejects the ambiguous config-plus-filenames case unless `--ignoreConfig` makes the bypass explicit. + +## Shared strict settings + +Use `strict` as the umbrella. Add checks according to the project contract: + +```json +{ + "compilerOptions": { + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noUncheckedSideEffectImports": true, + "types": [] + } +} +``` + +The contents of `strict` can change in future versions. Do not duplicate every current sub-flag unless the project needs a specific override. + +`skipLibCheck` skips checking declaration files. It can reduce work or bridge ecosystem incompatibility, but it can also hide conflicting or invalid declarations. Never use it as the automatic answer to an upgrade failure. + +## Host-specific examples + +Bundler or Bun application: + +```json +{ + "compilerOptions": { + "module": "preserve", + "moduleResolution": "bundler", + "noEmit": true, + "strict": true, + "verbatimModuleSyntax": true + } +} +``` + +Modern Node application: + +```json +{ + "compilerOptions": { + "module": "nodenext", + "strict": true, + "verbatimModuleSyntax": true + } +} +``` + +These are starting points, not complete universal configs. Select `target`, libraries, JSX, declaration output, source maps, and path behavior from the deployment and publishing contract. + +## Project references + +Referenced projects require `composite`. Build them with `tsc -b`; build mode performs dependency-aware incremental work. + +```json +{ + "files": [], + "references": [ + { "path": "./packages/core" }, + { "path": "./packages/web" } + ] +} +``` + +Do not invoke every referenced package separately when the repository already provides a build graph. diff --git a/packages/dotfiles/dot_agents/skills/typescript-helper/references/modules-and-runtimes.md b/packages/dotfiles/dot_agents/skills/typescript-helper/references/modules-and-runtimes.md new file mode 100644 index 0000000000..5a25758a3c --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/typescript-helper/references/modules-and-runtimes.md @@ -0,0 +1,47 @@ +# TypeScript modules and runtimes + +Read this when module resolution, import syntax, runtime execution, ESM/CommonJS interop, or extension rewriting is involved. + +## Compiler model + +`module` describes module syntax and host behavior; `moduleResolution` describes lookup. Choose them together: + +- Modern Node: `module: "nodenext"`. +- Deliberate Node 20 contract: `module: "node20"`. +- Bundler or Bun: `module: "preserve"` or `"esnext"` with `moduleResolution: "bundler"`. +- Do not start new projects with `node10` or `classic` resolution. + +Interop flags model host and transpiler behavior; they are not a substitute for the correct host mode. + +## Type-only imports + +With `verbatimModuleSyntax`, imports and exports without `type` remain in output, while explicit type-only imports are erased. + +```typescript +import { startServer } from "./server.js"; +import type { ServerOptions } from "./server.js"; +``` + +Node's built-in TypeScript stripping requires erasable syntax and ignores tsconfig. Use `import type` so a type-only dependency is not treated as a runtime import. + +## Relative extension rewriting + +`rewriteRelativeImportExtensions` rewrites static relative `.ts`, `.tsx`, `.mts`, and `.cts` specifiers to JavaScript equivalents during emit. It does not rewrite: + +- aliases, +- dependency or package specifiers, +- package `imports` and `exports`, +- dynamic expressions. + +`allowImportingTsExtensions` is a separate permission used when the runtime or resolver can interpret TypeScript extensions. Follow its documented emit constraints. + +## Runtime matrix + +| Runtime | Direct TypeScript behavior | +| --- | --- | +| Current Node | Type stripping is enabled by default in supported current releases and stable in newer lines; only erasable syntax; ignores tsconfig | +| Bun | Executes TS/TSX and recommends a bundler-oriented tsconfig; execution is separate from type-checking | +| `ts-node` | JIT transformation for Node with optional type-checking | +| `tsx` | Runtime transformer; verify semantics against its current docs and project needs | + +Do not use `--enable-source-maps` as a compile-cache switch. Node's module compile cache uses `module.enableCompileCache()` or `NODE_COMPILE_CACHE`; it may slow the first load and improve subsequent unchanged loads. diff --git a/packages/dotfiles/dot_agents/skills/typescript-helper/references/releases-and-migration.md b/packages/dotfiles/dot_agents/skills/typescript-helper/references/releases-and-migration.md new file mode 100644 index 0000000000..b3eee7f3f0 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/typescript-helper/references/releases-and-migration.md @@ -0,0 +1,91 @@ +# TypeScript releases and migration + +Read this when selecting TypeScript 6 versus 7, upgrading a project, or evaluating a performance or compatibility claim. + +## TypeScript 7.0 + +TypeScript 7.0 became stable on 2026-07-08. It is the native Go implementation of the compiler and language server. Microsoft reports typical 8–12x faster full builds, with parallel parsing, checking, and emitting. + +The central compatibility boundary is that 7.0 does not ship a programmatic API. Tools that import `typescript`—including some Angular template-checking and Vue, MDX, Astro, or Svelte integrations—may need TypeScript 6 until the native API arrives. A compiler being stable does not make every embedded-tool integration compatible. + +`--checkers` and `--builders` are experimental concurrency controls. More workers can increase memory use. `--singleThreaded` is useful for debugging or constrained environments. + +## TypeScript 6.x bridge + +TypeScript 6 is the last release from the JavaScript compiler codebase and the migration bridge from 5.9 to 7. Use `stableTypeOrdering` to approximate TypeScript 7's deterministic ordering before switching compilers. + +Before upgrading to 7.0: + +1. Make the project clean on current 6.x. +2. Enable `stableTypeOrdering` and review output or snapshot changes. +3. Remove `ignoreDeprecations` and fix each exposed configuration issue. +4. Set `rootDir` explicitly for nested source layouts. +5. List intended global `types` explicitly; 7.0 defaults to `[]`. +6. Replace `moduleResolution: "node"` / `"node10"` and `"classic"`. +7. Remove obsolete ES5/downlevel iteration, AMD/UMD/SystemJS/none, `baseUrl`, `outFile`, false interop, and false `alwaysStrict` settings. +8. Replace legacy namespace and import-assertion syntax. +9. Check command lines that combine source filenames with a config; 7.0 requires explicit `--ignoreConfig` for that bypass. +10. Confirm every framework, linter, editor, and build tool that embeds the programmatic API supports the selected compiler. + +The official package alias can keep TypeScript 6 available as `@typescript/typescript6`, with a `tsc6` binary. Tools that directly import the `typescript` package can require the official npm-alias layout rather than only the alternate binary. + +## TypeScript 5.9 context + +TypeScript 5.9 introduced `import defer`, stable `module: "node20"`, and a smaller `tsc --init` output. It is historical context for projects crossing multiple releases, not the current baseline. + +## Version selection + +- Stable application whose tooling supports the native compiler: prefer the repository-approved TypeScript 7 version. +- Tool or framework that imports the programmatic API: remain on current TypeScript 6 until compatibility is documented. +- Preview testing: use `typescript@next` only in an explicit canary path. +- Never install the old `@typescript/native-preview` package as the stable TypeScript 7 path. + +## Research ledger + +The following official pages were fetched and inspected for this refresh: + +1. [Announcing TypeScript 7.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/) +2. [TypeScript 7.0 RC](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-rc/) +3. [TypeScript 7.0 Beta](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-beta/) +4. [Announcing TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/) +5. [TypeScript 6.0 RC](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0-rc/) +6. [TypeScript 6.0 Beta](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0-beta/) +7. [Announcing TypeScript 5.9](https://devblogs.microsoft.com/typescript/announcing-typescript-5-9/) +8. [A 10x Faster TypeScript](https://devblogs.microsoft.com/typescript/typescript-native-port/) +9. [TypeScript Native Previews](https://devblogs.microsoft.com/typescript/announcing-typescript-native-previews/) +10. [Nightly builds](https://www.typescriptlang.org/docs/handbook/nightly-builds.html) +11. [Download TypeScript](https://www.typescriptlang.org/download/) +12. [What is a tsconfig.json](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) +13. [tsc CLI options](https://www.typescriptlang.org/docs/handbook/compiler-options.html) +14. [Project references](https://www.typescriptlang.org/docs/handbook/project-references.html) +15. [Modules introduction](https://www.typescriptlang.org/docs/handbook/modules/introduction.html) +16. [Modules theory](https://www.typescriptlang.org/docs/handbook/modules/theory.html) +17. [ESM/CJS interoperability](https://www.typescriptlang.org/docs/handbook/modules/appendices/esm-cjs-interop.html) +18. [Migrating from JavaScript](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html) +19. [Narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) +20. [More on functions](https://www.typescriptlang.org/docs/handbook/2/functions.html) +21. [Object types](https://www.typescriptlang.org/docs/handbook/2/objects.html) +22. [Generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) +23. [Conditional types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) +24. [Utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html) +25. [Declaration-file dos and don'ts](https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html) +26. [Type inference](https://www.typescriptlang.org/docs/handbook/type-inference.html) +27. [TypeScript 5.7 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html) +28. [rewriteRelativeImportExtensions](https://www.typescriptlang.org/tsconfig/rewriteRelativeImportExtensions.html) +29. [verbatimModuleSyntax](https://www.typescriptlang.org/tsconfig/verbatimModuleSyntax.html) +30. [allowImportingTsExtensions](https://www.typescriptlang.org/tsconfig/allowImportingTsExtensions.html) +31. [moduleResolution](https://www.typescriptlang.org/tsconfig/moduleResolution.html) +32. [module](https://www.typescriptlang.org/tsconfig/module.html) +33. [noUncheckedIndexedAccess](https://www.typescriptlang.org/tsconfig/noUncheckedIndexedAccess.html) +34. [exactOptionalPropertyTypes](https://www.typescriptlang.org/tsconfig/exactOptionalPropertyTypes.html) +35. [strict](https://www.typescriptlang.org/tsconfig/strict.html) + +Supplemental runtime and tooling pages: + +36. [Node.js TypeScript support](https://nodejs.org/api/typescript.html) +37. [Node module compile cache](https://nodejs.org/api/module.html#module-compile-cache) +38. [Bun TypeScript](https://bun.sh/docs/runtime/typescript) +39. [ts-node overview](https://typestrong.org/ts-node/docs/) +40. [typescript-eslint getting started](https://typescript-eslint.io/getting-started/) +41. [typescript-eslint typed linting](https://typescript-eslint.io/getting-started/typed-linting/) +42. [Everyday types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html) diff --git a/packages/dotfiles/dot_agents/skills/typescript-helper/references/strict-typescript.md b/packages/dotfiles/dot_agents/skills/typescript-helper/references/strict-typescript.md deleted file mode 100644 index 01c62dbda0..0000000000 --- a/packages/dotfiles/dot_agents/skills/typescript-helper/references/strict-typescript.md +++ /dev/null @@ -1,182 +0,0 @@ -# Strict TypeScript Development - -Type-safe development patterns using strict TypeScript and Zod for runtime validation, based on coding standards from scout-for-lol and homelab repositories. - -## Core Principles - -1. **Use Zod for Runtime Validation**: Prefer Zod schema validation over `typeof`, `instanceof`, or type guards -2. **No Type Assertions**: Avoid type assertions except `as unknown` or `as const` -3. **Strict TypeScript**: Use `strictTypeChecked` and `stylisticTypeChecked` configurations -4. **Type Definitions**: Use `type` instead of `interface` for consistency - -## Prefer Zod Over Type Guards - -**Avoid: typeof operator** - -```typescript -// Don't do this -function processValue(value: unknown) { - if (typeof value === "string") { - return value.toUpperCase(); - } -} -``` - -**Prefer: Zod validation** - -```typescript -import { z } from "zod"; - -function processValue(value: unknown) { - const result = z.string().safeParse(value); - if (result.success) { - return result.data.toUpperCase(); - } - return null; -} -``` - -## Replace Common Type Checks with Zod - -**Array.isArray() -> Zod** - -```typescript -// Avoid -if (Array.isArray(value)) { - value.forEach((item) => console.log(item)); -} - -// Prefer -const result = z.array(z.string()).safeParse(value); -if (result.success) { - result.data.forEach((item) => console.log(item)); -} -``` - -**instanceof -> Zod** - -```typescript -// Avoid -if (err instanceof Error) { - console.log(err.message); -} - -// Prefer -const result = z.instanceof(Error).safeParse(err); -if (result.success) { - console.log(result.data.message); -} -``` - -**Number validation -> Zod** - -```typescript -// Avoid -if (Number.isInteger(value)) { - return value * 2; -} - -// Prefer -const result = z.number().int().safeParse(value); -if (result.success) { - return result.data * 2; -} -``` - -**Type predicates -> Zod** - -```typescript -// Avoid type guard functions -function isUser(value: unknown): value is User { - return typeof value === "object" && value !== null && "email" in value; -} - -// Prefer Zod schema validation -const UserSchema = z.object({ - email: z.string().email(), - name: z.string(), -}); - -const result = UserSchema.safeParse(value); -if (result.success) { - const user = result.data; // Type-safe! -} -``` - -## Type Assertion Rules - -### Only Allow 'as unknown' and 'as const' - -```typescript -// Never do this - bypasses type safety -const user = data as User; -const id = value as string; - -// Cast to unknown first, then validate -const data = response as unknown; -const result = UserSchema.safeParse(data); -if (result.success) { - const user = result.data; -} - -// Use 'as const' for literal types -const STATUSES = ["pending", "approved", "rejected"] as const; -type Status = (typeof STATUSES)[number]; -``` - -### Why No Type Assertions? - -Type assertions are dangerous because: - -1. They bypass TypeScript's type checking -2. They don't perform runtime validation -3. They can cause runtime crashes with wrong types -4. They hide bugs instead of catching them - -Zod provides both compile-time AND runtime safety. - -## Use 'type' Instead of 'interface' - -```typescript -// Prefer type -type User = { - id: string; - email: string; -}; - -type Admin = User & { - permissions: string[]; -}; - -// Avoid interface -interface User { - id: string; - email: string; -} -``` - -**Why?** Types are more flexible (unions, intersections) and consistent with Zod's inferred types. - -## Strict tsconfig.json - -```json -{ - "compilerOptions": { - "strict": true, - "noUncheckedIndexedAccess": true, - "noPropertyAccessFromIndexSignature": true, - "exactOptionalPropertyTypes": true, - "noImplicitOverride": true, - "noFallthroughCasesInSwitch": true, - "forceConsistentCasingInFileNames": true - } -} -``` - -## Best Practices - -1. **Define schemas early**: Create Zod schemas alongside types -2. **Single source of truth**: Use `z.infer` for types -3. **Validate at boundaries**: API responses, user input, external data -4. **Fail fast**: Use `parse()` for config, `safeParse()` for runtime data -5. **Compose schemas**: Build complex schemas from simple reusable parts diff --git a/packages/dotfiles/dot_agents/skills/typescript-helper/references/tooling.md b/packages/dotfiles/dot_agents/skills/typescript-helper/references/tooling.md new file mode 100644 index 0000000000..913e63fc77 --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/typescript-helper/references/tooling.md @@ -0,0 +1,40 @@ +# TypeScript tooling + +Read this when configuring typescript-eslint, selecting a direct TypeScript executor, or separating runtime execution from type-checking. + +## typescript-eslint + +Current configuration uses flat config: + +```javascript +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + }, + }, + }, +); +``` + +Adapt this to the repository's shared ESLint package. Do not introduce a parallel standalone configuration when one already exists. + +Typed linting asks TypeScript for semantic information and costs more than syntax-only rules. Run the repository's intended lint task so caching, ignores, generated files, and project service settings remain consistent. + +## Execution is not checking + +Node, Bun, `tsx`, and `ts-node` can execute TypeScript using different transformation rules. A successful run does not prove the project passes its compiler configuration. + +- Use the repository's `typecheck` task or `tsc -p` / `tsc -b` for checking. +- Use the selected deployment runtime for execution. +- Use the build tool for output when the application bundles or publishes artifacts. + +## Tool ownership + +Load the matching framework or build-tool skill for Vite, React, Bun, test runners, or bundlers. This TypeScript skill should define their type-system boundary without copying volatile tool setup. diff --git a/packages/dotfiles/dot_agents/skills/typescript-helper/references/type-safety.md b/packages/dotfiles/dot_agents/skills/typescript-helper/references/type-safety.md new file mode 100644 index 0000000000..2747bf73fd --- /dev/null +++ b/packages/dotfiles/dot_agents/skills/typescript-helper/references/type-safety.md @@ -0,0 +1,39 @@ +# TypeScript type safety + +Read this when designing domain types, narrowing unknown values, using generics, or reviewing assertion-heavy code. + +## Boundaries + +TypeScript types disappear at runtime. Parse HTTP, JSON, database, cache, environment, file, and message-queue data before assigning a domain type. + +Use `unknown` for untrusted values and narrow it with control flow or a schema. `any` disables the checking you need most at a boundary. + +## Narrowing + +Useful built-in guards include: + +- `typeof` for primitives, +- `instanceof` for runtime classes, +- `Array.isArray` for arrays, +- equality and truthiness when those states are semantically valid, +- discriminant properties for unions, +- property checks for object shapes. + +Use an exhaustive `never` branch for closed discriminated unions. Avoid a default that returns existing state, because it hides newly introduced variants. + +## Generics + +A generic parameter should relate multiple values or preserve a relationship through the return type. Prefer inference when it stays clear. A generic used once is often a constraint that should be an ordinary type. + +Conditional types distribute over unions unless the checked type is wrapped. `infer` extracts a related component. `NoInfer` prevents an input from contributing to inference without otherwise changing `T`. + +## Object types + +- `readonly` is shallow compile-time assignment protection, not runtime freezing. +- Interfaces can reopen and merge; aliases can represent unions, primitives, tuples, and composed types. +- Use primitive lowercase types rather than boxed object types. +- Optional callback parameters mean the implementation may omit an argument when invoking the callback. + +## Assertions + +An `as` cast, non-null assertion, or assertion-style branded constructor does not validate anything. Prefer a schema that returns the validated domain value, or redesign the API so the type follows from control flow.